/* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* interface header */ #include "commands.h" /* system implementation headers */ #include "../zlib/zconf.h" #include "../zlib/zlib.h" /* common implementation headers */ #include "BZDBCache.h" #include "TextUtils.h" #include "FileManager.h" #include "DirectoryNames.h" #include "version.h" /* local implementation headers */ #include "LocalPlayer.h" #include "sound.h" #include "ComposeDefaultKey.h" #include "SilenceDefaultKey.h" #include "ServerCommandKey.h" #include "Roaming.h" #include "playing.h" #include "HUDRenderer.h" #include "HUDui.h" /** jump */ static std::string cmdJump(const std::string&, const CommandManager::ArgList& args, bool*); /** fire weapon */ static std::string cmdFire(const std::string&, const CommandManager::ArgList& args, bool*); /** drop a flag */ static std::string cmdDrop(const std::string&, const CommandManager::ArgList& args, bool*); /** toggle radar visibility */ static std::string cmdToggleRadar(const std::string&, const CommandManager::ArgList& args, bool*); /** toggle console visibility */ static std::string cmdToggleConsole(const std::string&, const CommandManager::ArgList& args, bool*); /** toggle flags */ static std::string cmdToggleFlags(const std::string&, const CommandManager::ArgList& args, bool*); /** identify to a server */ static std::string cmdIdentify(const std::string&, const CommandManager::ArgList& args, bool*); /** restart/respawn */ static std::string cmdRestart(const std::string&, const CommandManager::ArgList& args, bool*); /** self-destruct */ static std::string cmdDestruct(const std::string&, const CommandManager::ArgList& args, bool*); /** pause */ static std::string cmdPause(const std::string&, const CommandManager::ArgList& args, bool*); /** select tabbed message */ static std::string cmdMessagePanel(const std::string&, const CommandManager::ArgList& args, bool*); /** toggle auto-pilot */ static std::string cmdAutoPilot(const std::string&, const CommandManager::ArgList& args, bool*); /** change radar scale */ static std::string cmdRadarZoom(const std::string&, const CommandManager::ArgList& args, bool*); /** change view angle */ static std::string cmdViewZoom(const std::string&, const CommandManager::ArgList& args, bool*); /** send */ static std::string cmdSend(const std::string&, const CommandManager::ArgList& args, bool*); #ifdef SNAPPING /** capture a screenshot */ static std::string cmdScreenshot(const std::string&, const CommandManager::ArgList& args, bool*); #endif /** time */ static std::string cmdTime(const std::string&, const CommandManager::ArgList& args, bool*); /** roam */ static std::string cmdRoam(const std::string&, const CommandManager::ArgList& args, bool*); /** silence another player */ static std::string cmdSilence(const std::string&, const CommandManager::ArgList& args, bool*); /** perform a server command */ static std::string cmdServerCommand(const std::string&, const CommandManager::ArgList& args, bool*); /** scroll the chat panel */ static std::string cmdScrollPanel(const std::string&, const CommandManager::ArgList& args, bool*); /** hunt a player */ static std::string cmdHunt(const std::string&, const CommandManager::ArgList& args, bool*); /** hunt another player */ static std::string cmdAddHunt(const std::string&, const CommandManager::ArgList& args, bool*); /** iconify window */ static std::string cmdIconify(const std::string&, const CommandManager::ArgList& args, bool*); /** toggle mouse capture */ static std::string cmdMouseGrab(const std::string&, const CommandManager::ArgList& args, bool*); /** toggle Full Screen */ static std::string cmdToggleFS(const std::string&, const CommandManager::ArgList& args, bool*); const struct CommandListItem commandList[] = { { "fire", &cmdFire, "fire: fire a shot" }, { "jump", &cmdJump, "jump: make player jump" }, { "drop", &cmdDrop, "drop: drop the current flag" }, { "identify", &cmdIdentify, "identify: identify/lock-on-to player in view" }, { "restart", &cmdRestart, "restart: restart playing" }, { "destruct", &cmdDestruct, "destruct: self destruct" }, { "pause", &cmdPause, "pause: pause/resume" }, { "send", &cmdSend, "send {all|team|nemesis|recipient|admin}: start composing a message" }, #ifdef SNAPPING { "screenshot", &cmdScreenshot, "screenshot: take a screenshot" }, #endif { "time", &cmdTime, "time {forward|backward}: adjust the current time" }, { "roam", &cmdRoam, "roam {zoom|cycle} : roam around" }, { "silence", &cmdSilence, "silence: silence/unsilence a player" }, { "servercommand", &cmdServerCommand, "servercommand: quick admin" }, { "scrollpanel", &cmdScrollPanel, "scrollpanel {up|down}: scroll message panel" }, { "hunt", &cmdHunt, "hunt: hunt a specific player" }, { "addhunt", &cmdAddHunt, "addhunt: add/modify hunted player(s)" }, { "iconify", &cmdIconify, "iconify: iconify & pause bzflag" }, { "mousegrab", &cmdMouseGrab, "mousegrab: toggle exclusive mouse mode" }, { "fullscreen", &cmdToggleFS, "fullscreen: toggle fullscreen mode" }, { "autopilot",&cmdAutoPilot, "autopilot: set/unset autopilot bot code" }, { "radarZoom", &cmdRadarZoom, "radarZoom {in/out}: change maxRadar range"}, { "viewZoom", &cmdViewZoom, "viewZoom {in/out/toggle}: change view angle"}, { "messagepanel", &cmdMessagePanel, "messagepanel {all|chat|server|misc}: set message tab" }, { "toggleRadar", &cmdToggleRadar, "toggleRadar: toggle radar visibility"}, { "toggleConsole", &cmdToggleConsole, "toggleConsole: toggle console visibility"}, { "toggleFlags", &cmdToggleFlags, "toggleFlags {main|radar}: turn off/on field radar flags"} }; static std::string cmdToggleFS(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: fullscreen"; mainWindow->toggleFullscreen(); mainWindow->getWindow()->callResizeCallbacks(); return std::string(); } static std::string cmdMouseGrab(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: mousegrab"; const bool grabbing = !(BZDB.isTrue("mousegrab")); BZDB.set("mousegrab", grabbing ? "true" : "false"); mainWindow->enableGrabMouse(grabbing); return std::string(); } static std::string cmdIconify(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: iconify"; if (!BZDB.isTrue("Win32NoMin")) mainWindow->iconify(); return std::string(); } static std::string cmdJump(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: jump"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank != NULL) myTank->setJump(); return std::string(); } #include "TargetingUtils.h" RemotePlayer *answer; void findBestTarget() { RemotePlayer *target = NULL; LocalPlayer *myTank = LocalPlayer::getMyTank(); const float *pos = myTank->getPosition(); float myAzimuth = myTank->getAngle(); float distance = Infinity; for (int t = 0; t < curMaxPlayers; t++) { if ((t != myTank->getId()) && (player[t]) && (player[t]->isAlive()) && (!player[t]->isPaused()) && (!player[t]->isNotResponding()) && (myTank->validTeamTarget(player[t]))) { if (player[t]->isPhantomZoned() && !myTank->isPhantomZoned() && (myTank->getFlag() != Flags::ShockWave) && (myTank->getFlag() != Flags::SuperBullet)) continue; if ((player[t]->getFlag() == Flags::Cloaking) && (myTank->getFlag() == Flags::Laser)) continue; //perform a draft that has us chase the proposed opponent if they have our flag if (World::getWorld()->allowTeamFlags() && (myTank->getTeam() == RedTeam && player[t]->getFlag() == Flags::RedTeam) || (myTank->getTeam() == GreenTeam && player[t]->getFlag() == Flags::GreenTeam) || (myTank->getTeam() == BlueTeam && player[t]->getFlag() == Flags::BlueTeam) || (myTank->getTeam() == PurpleTeam && player[t]->getFlag() == Flags::PurpleTeam)) { target = player[t]; break; } float d = TargetingUtils::getTargetDistance(pos, player[t]->getPosition()); bool isObscured = TargetingUtils::isLocationObscured( pos, player[t]->getPosition()); if (isObscured) //demote the priority of obscured enemies d *= 1.25f; if (d < distance) { if ((player[t]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer) || ((!isObscured) && (TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition()) <= 30.0f))) { target = player[t]; distance = d; } } } } answer = target; } static std::string cmdToggleFlags(const std::string&, const CommandManager::ArgList& args, bool*) { findBestTarget(); const Player *Other = answer; if(Other) { const float* pos = Other->getPosition(); const float* dir = Other->getForward(); const float* vel = Other->getVelocity(); float newpos[3]; float newvel[3]; newpos[0] = pos[0] - 10 * dir[0]; newpos[1] = pos[1] - 10 * dir[1]; newpos[2] = pos[2] + 0.01; newvel[0] = vel[0]; newvel[1] = vel[1]; newvel[2] = vel[2]; LocalPlayer::getMyTank()->move(newpos, Other->getAngle()); LocalPlayer::getMyTank()->setVelocity(vel); } return std::string(); } static std::string cmdToggleRadar(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) { return "usage: toggleRadar"; } CMDMGR.run("toggle displayRadar"); warnAboutRadar(); return std::string(); } static std::string cmdToggleConsole(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) { return "usage: toggleConsole"; } CMDMGR.run("toggle displayConsole"); warnAboutConsole(); return std::string(); } static std::string cmdFire(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: fire"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (fireButton && myTank != NULL && myTank->isAlive() && myTank->getTeam() != ObserverTeam) myTank->fireShot(); return std::string(); } static std::string cmdDrop(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: drop"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank != NULL) { FlagType* flag = myTank->getFlag(); if ((flag != Flags::Null) && !myTank->isPaused() && !myTank->isPhantomZoned() && !(flag == Flags::OscillationOverthruster && myTank->getLocation() == LocalPlayer::InBuilding)) { serverLink->sendDropFlag(myTank->getPosition()); // changed: on windows it may happen the MsgDropFlag // never comes back to us, so we drop it right away handleFlagDropped(myTank); } } return std::string(); } static std::string cmdIdentify(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: identify"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank != NULL) if (myTank->isAlive() && !myTank->isPaused()) setTarget(); return std::string(); } static std::string cmdRestart(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: restart"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank != NULL) if (!gameOver && !myTank->isSpawning() && (myTank->getTeam() != ObserverTeam) && !myTank->isAlive() && !myTank->isExploding()) { serverLink->sendAlive(); myTank->setSpawning(true); CommandManager::ArgList zoomArgs; std::string resetArg = "reset"; zoomArgs.push_back(resetArg); cmdViewZoom("", zoomArgs,NULL); } return std::string(); } static std::string cmdDestruct(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: destruct"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank != NULL) { if (destructCountdown > 0.0f) { destructCountdown = 0.0f; hud->setAlert(1, "Self Destruct cancelled", 1.5f, true); } else { destructCountdown = 5.0f; char msgBuf[40]; sprintf(msgBuf, "Self Destructing in %d", (int)(destructCountdown + 0.99f)); hud->setAlert(1, msgBuf, 1.0f, false); } } return std::string(); } static std::string cmdPause(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: pause"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (!pausedByUnmap && myTank && myTank->isAlive() && !myTank->isAutoPilot()) { if (myTank->isPaused()) { // already paused, so unpause myTank->setPause(false); controlPanel->addMessage("Resumed"); // restore the sound if (savedVolume != -1) { setSoundVolume(savedVolume); savedVolume = -1; } // grab mouse if (shouldGrabMouse()) mainWindow->grabMouse(); } else if (pauseCountdown > 0.0f) { // player aborted pause pauseCountdown = 0.0f; hud->setAlert(1, "Pause cancelled", 1.5f, true); } else if (0 && myTank->getLocation() == LocalPlayer::InBuilding) { // custom message when trying to pause while in a building // (could get stuck on un-pause if flag is taken) hud->setAlert(1, "Can't pause while inside a building", 1.0f, false); } else if (0 && myTank->getLocation() == LocalPlayer::InAir) { // custom message when trying to pause when jumping/falling hud->setAlert(1, "Can't pause when you are in the air", 1.0f, false); } else if (0 && myTank->getLocation() != LocalPlayer::OnGround && myTank->getLocation() != LocalPlayer::OnBuilding) { // catch-all message when trying to pause when you should not hud->setAlert(1, "Unable to pause right now", 1.0f, false); } else { // update the pause alert message pauseCountdown = 5.0f; char msgBuf[40]; sprintf(msgBuf, "Pausing in %d", (int) (pauseCountdown + 0.99f)); hud->setAlert(1, msgBuf, 1.0f, false); } } return std::string(); } static std::string cmdAutoPilot(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: autopilot"; if (BZDB.isTrue(StateDatabase::BZDB_DISABLEBOTS)) { hud->setAlert(0, "autopilot not allowed on this server", 1.0f, true); return std::string(); } LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank != NULL && myTank->getTeam() != ObserverTeam) { if (myTank->isAutoPilot()) { myTank->activateAutoPilot(false); hud->setAlert(0, "autopilot disabled", 1.0f, true); // grab mouse if (shouldGrabMouse()) mainWindow->grabMouse(); } else { // don't enable the AutoPilot if you have within the last 5 secs static TimeKeeper LastAutoPilotEnable = TimeKeeper::getSunGenesisTime(); if ((TimeKeeper::getCurrent() - LastAutoPilotEnable) > 5) { // reset timer LastAutoPilotEnable = TimeKeeper::getCurrent(); // enable autopilot myTank->activateAutoPilot(); hud->setAlert(0, "autopilot enabled", 1.0f, true); // ungrab mouse mainWindow->ungrabMouse(); } else { controlPanel->addMessage("You may not enable the Autopilot more than once every five seconds."); return std::string(); } } } return std::string(); } static std::string cmdRadarZoom(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 1) return "usage: radarZoom {in|out}"; float range = BZDB.eval("displayRadarRange"); if (args[0] == "out") { range *= 1.05f; if (range > 2.0f) range = 2.0f; BZDB.setFloat("displayRadarRange", range); } else if (args[0] == "in") { range /= 1.05f; if (range < 0.005f) range = 0.005f; BZDB.setFloat("displayRadarRange", range); } else { return "usage: radarZoom {in|out}"; } return std::string(); } static std::string cmdViewZoom(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 1) return "usage: viewZoom {in|out|toggle}"; float fov = BZDB.eval("displayFOV"); if (args[0] == "out") { fov += 1.0f; if (fov > 60.0f) fov = 60.0f; BZDB.setFloat("displayFOV", fov); } else if (args[0] == "in") { fov -= 1.0f; if (fov < 15.0f) fov = 15.0f; BZDB.setFloat("displayFOV", fov); } else if (args[0] == "toggle") { if (fov < 15.5f) { fov = 60.0f; } else { fov = 15.0f; } BZDB.setFloat("displayFOV", fov); // also toggle the observer fov if (ROAM.getZoom() != 60.0f) { ROAM.setZoom(60.0f); } else { ROAM.setZoom(15.0f); } } else if (args[0] == "reset") { fov = 60.0f; ROAM.setZoom(60.0f); BZDB.setFloat("displayFOV", fov); } else { return "usage: viewZoom {in|out|toggle|reset}"; } return std::string(); } static std::string cmdMessagePanel(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 1) return "usage: messagepanel {all|chat|server|misc}"; int oldMode = controlPanel->getMessagesMode(); int newMode; if (args[0] == "all") newMode = 0; else if (args[0] == "chat") newMode = 1; else if (args[0] == "server") newMode = 2; else if (args[0] == "misc") newMode = 3; else return "usage: messagepanel {all|chat|server|misc}"; if (newMode == oldMode) newMode = -1; controlPanel->setMessagesMode(newMode); return std::string(); } static std::string cmdSend(const std::string&, const CommandManager::ArgList& args, bool*) { static ComposeDefaultKey composeKeyHandler; if (args.size() != 1) return "usage: send {all|team|nemesis|recipient|admin}"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (!myTank) return "use send only when connected"; std::string composePrompt; if (args[0] == "all") { void* buf = messageMessage; buf = nboPackUByte(buf, AllPlayers); composePrompt = "Send to all: "; } else if (args[0] == "team") { void* buf = messageMessage; buf = nboPackUByte(buf, TeamToPlayerId(myTank->getTeam())); composePrompt = "Send to teammates: "; } else if (args[0] == "nemesis") { const Player* nemesis = myTank->getNemesis(); if (!nemesis) return std::string(); void* buf = messageMessage; buf = nboPackUByte(buf, nemesis->getId()); composePrompt = "Send to "; composePrompt += nemesis->getCallSign(); composePrompt += ": "; } else if (args[0] == "recipient") { const Player* recipient = myTank->getRecipient(); if (!recipient) { for (int i = 0; i < curMaxPlayers; i++) { if (player[i]) { myTank->setRecipient(player[i]); break; } } } recipient = myTank->getRecipient(); if (recipient) { void* buf = messageMessage; buf = nboPackUByte(buf, recipient->getId()); composePrompt = "Send to "; composePrompt += recipient->getCallSign(); composePrompt += ": "; } } else if (args[0] == "admin") { void* buf = messageMessage; buf = nboPackUByte(buf, AdminPlayers); composePrompt = "Send to Admin : "; } else { return "usage: send {all|team|nemesis|recipient|admin}"; } messageHistoryIndex = 0; hud->setComposing(composePrompt); HUDui::setDefaultKey(&composeKeyHandler); return std::string(); } #ifdef SNAPPING static std::string cmdScreenshot(const std::string&, const CommandManager::ArgList& args, bool*) { int snap = atoi(BZDB.get(std::string("lastScreenshot")).c_str()); snap++; if (args.size() != 0) return "usage: screenshot"; std::string filename = getScreenShotDirName(); filename += TextUtils::format("bzfi%04d.png", snap); BZDB.setInt(std::string("lastScreenshot"),snap); std::ostream* f = FILEMGR.createDataOutStream (filename.c_str(), true, true); if (f != NULL) { int w = mainWindow->getWidth(), h = mainWindow->getHeight(); const unsigned long blength = h * w * 3 + h; //size of b[] and br[] unsigned char* b = new unsigned char[blength]; //screen of pixels + column for filter type required by PNG - filtered data // pause to prevent dt from accumulating until done screenshotting LocalPlayer *myTank = LocalPlayer::getMyTank(); bool temp_paused = false; if (myTank && !myTank->isPaused()) { myTank->setPause(true); temp_paused = true; } //Prepare gamma table const bool gammaAdjust = BZDB.isSet("gamma"); unsigned char gammaTable[256]; if (gammaAdjust) { float gamma = (float) atof(BZDB.get("gamma").c_str()); for (int i = 0; i < 256; i++) { float lum = ((float) i) / 256.0f; float lumadj = pow(lum, 1.0f / gamma); gammaTable[i] = (unsigned char) (lumadj * 256); } } /* Write a PNG stream. FIXME: Note that there are several issues with this code, altho it produces perfectly fine PNGs. 1. We do no filtering. Sub filters increase screenshot size, other filters would require a lot of effort to implement. 2. Gamma-correction is preapplied by BZFlag's gamma table. This ignores the PNG gAMA chunk, but so do many viewers (including Mozilla) 3. Timestamp (tIME) chunk is not added to the file, but would be a good idea. */ int temp = 0; //temporary values for binary file writing char tempByte = 0; int crc = 0; //used for running CRC values // Write PNG headers (*f) << "\211PNG\r\n\032\n"; #define PNGTAG(t_) ((((int)t_[0]) << 24) | \ (((int)t_[1]) << 16) | \ (((int)t_[2]) << 8) | \ (int)t_[3]) // IHDR chunk temp = htonl((int) 13); //(length) IHDR is always 13 bytes long f->write((char*) &temp, 4); temp = htonl(PNGTAG("IHDR")); //(tag) IHDR f->write((char*) &temp, 4); crc = crc32(crc, (unsigned char*) &temp, 4); temp = htonl(w); //(data) Image width f->write((char*) &temp, 4); crc = crc32(crc, (unsigned char*) &temp, 4); temp = htonl(h); //(data) Image height f->write((char*) &temp, 4); crc = crc32(crc, (unsigned char*) &temp, 4); tempByte = 8; //(data) Image bitdepth (8 bits/sample = 24 bits/pixel) f->write(&tempByte, 1); crc = crc32(crc, (unsigned char*) &tempByte, 1); tempByte = 2; //(data) Color type: RGB = 2 f->write(&tempByte, 1); crc = crc32(crc, (unsigned char*) &tempByte, 1); tempByte = 0; int i; for (i = 0; i < 3; i++) { //(data) Last three tags are compression (only 0 allowed), filtering (only 0 allowed), and interlacing (we don't use it, so it's 0) f->write(&tempByte, 1); crc = crc32(crc, (unsigned char*) &tempByte, 1); } crc = htonl(crc); f->write((char*) &crc, 4); //(crc) write crc // IDAT chunk for (i = h - 1; i >= 0; i--) { const unsigned long line = (h - (i + 1)) * (w * 3 + 1); //beginning of this line b[line] = 0; //filter type byte at the beginning of each scanline (0 = no filter, 1 = sub filter) glReadPixels(0, i, w, 1, GL_RGB, GL_UNSIGNED_BYTE, b + line + 1); //capture line // apply gamma correction if necessary if (gammaAdjust) { unsigned char *ptr = b + line + 1; for (int j = 1; j < w * 3 + 1; j++) { *ptr = gammaTable[*ptr]; ptr++; } } } // let the user know we're paused while saving the file if (temp_paused) { drawFrame(0.0f); } unsigned long zlength = blength + 15; //length of bz[], will be changed by zlib to the length of the compressed string contained therein unsigned char* bz = new unsigned char[zlength]; //just like b, but compressed; might get bigger, so give it room // compress b into bz compress2(bz, &zlength, b, blength, 5); temp = htonl(zlength); //(length) IDAT length after compression f->write((char*) &temp, 4); temp = htonl(PNGTAG("IDAT")); //(tag) IDAT f->write((char*) &temp, 4); crc = crc32(crc = 0, (unsigned char*) &temp, 4); f->write(reinterpret_cast(bz), zlength); //(data) This line of pixels, compressed crc = htonl(crc32(crc, bz, zlength)); f->write((char*) &crc, 4); //(crc) write crc // tEXt chunk containing bzflag build/version temp = htonl((int) 9 + strlen(getAppVersion()));//(length) tEXt is 9 + strlen(getAppVersion()) f->write((char*) &temp, 4); temp = htonl(PNGTAG("tEXt")); //(tag) tEXt f->write((char*) &temp, 4); crc = crc32(crc = 0, (unsigned char*) &temp, 4); strcpy(reinterpret_cast(b), "Software"); //(data) Keyword f->write(reinterpret_cast(b), strlen(reinterpret_cast(b))); crc = crc32(crc, b, strlen(reinterpret_cast(b))); tempByte = 0; //(data) Null character separator f->write(&tempByte, 1); crc = crc32(crc, (unsigned char*) &tempByte, 1); strcpy((char*) b, getAppVersion()); //(data) Text contents (build/version) f->write(reinterpret_cast(b), strlen(reinterpret_cast(b))); crc = htonl(crc32(crc, b, strlen(reinterpret_cast(b)))); f->write((char*) &crc, 4); //(crc) write crc // IEND chunk temp = htonl((int) 0); //(length) IEND is always 0 bytes long f->write((char*) &temp, 4); temp = htonl(PNGTAG("IEND")); //(tag) IEND f->write((char*) &temp, 4); crc = htonl(crc32(crc = 0, (unsigned char*) &temp, 4)); //(data) IEND has no data field f->write((char*) &crc, 4); //(crc) write crc delete [] bz; delete [] b; delete f; char notify[128]; snprintf(notify, 128, "%s: %dx%d", filename.c_str(), w, h); controlPanel->addMessage(notify); // unpause to prevent dt from accumulating until done screenshotting if (temp_paused) { myTank->setPause(false); } // update the display regardless of pausing drawFrame(0.0f); } return std::string(); } #endif static std::string cmdTime(const std::string&, const CommandManager::ArgList& args, bool*) { // FIXME - time should be moved into BZDB if (args.size() != 1) return "usage: time {forward|backward}"; if (args[0] == "forward") { clockAdjust += 5.0f * 60.0f; } else if (args[0] == "backward") { clockAdjust -= 5.0f * 60.0f; } else { return "usage: time {forward|backward}"; } return std::string(); } static std::string cmdRoam(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() == 0) return "usage: roam {zoom|cycle} "; if (!ROAM.isRoaming()) return std::string(); if (args[0] == "zoom") { if (args.size() != 2) return "usage: roam zoom {in|out|normal|stop}"; if (!roamButton || args[1] == "stop") { roamDZoom = 0.0f; } else if (args[1] == "out") { roamDZoom = +2.0f * BZDBCache::tankSpeed; } else if (args[1] == "in") { roamDZoom = -2.0f * BZDBCache::tankSpeed; } else if (args[1] == "normal") { ROAM.setZoom(60.0f); } else { return "usage: roam zoom {in|out|normal|stop}"; } } else if (args[0] == "cycle") { if (args.size() != 3) return "usage: roam cycle {type|subject} {forward|backward}"; if (args[1] == "type") { if (args[2] == "forward") { ROAM.setMode(Roaming::RoamingView((ROAM.getMode() + 1) % Roaming::roamViewCount)); } else if (args[2] == "backward") { int setto = (ROAM.getMode() - 1) % Roaming::roamViewCount; if (setto < 0) setto += Roaming::roamViewCount; ROAM.setMode(Roaming::RoamingView(setto)); } else { return "usage: roam cycle {type|subject} {forward|backward}"; } } else if (args[1] == "subject") { if (args[2] == "forward") { ROAM.changeTarget(Roaming::next); } else if (args[2] == "backward") { ROAM.changeTarget(Roaming::previous); } else { return "usage: roam cycle {type|subject} {forward|backward}"; } } else { return "usage: roam cycle {type|subject} {forward|backward}"; } } else { return "usage: roam {zoom|cycle} "; } return std::string(); } static std::string cmdSilence(const std::string&, const CommandManager::ArgList& args, bool*) { static SilenceDefaultKey silenceKeyHandler; if (args.size() != 0) return "usage: silence"; messageHistoryIndex = 0; hud->setComposing("[Un]Silence: "); HUDui::setDefaultKey(&silenceKeyHandler); return std::string(); } static std::string cmdServerCommand(const std::string&, const CommandManager::ArgList& args, bool*) { static ServerCommandKey serverCommandKeyHandler; if (args.size() != 0) return "usage: servercommand"; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (!myTank) return "use only when connected"; static bool prevAdmin = myTank->isAdmin(); if (!prevAdmin && myTank->isAdmin()) serverCommandKeyHandler.adminInit(); if (prevAdmin && !myTank->isAdmin()) serverCommandKeyHandler.nonAdminInit(); prevAdmin = myTank->isAdmin(); messageHistoryIndex = 0; serverCommandKeyHandler.init(); HUDui::setDefaultKey(&serverCommandKeyHandler); return std::string(); } static std::string cmdScrollPanel(const std::string&, const CommandManager::ArgList& args, bool*) { if ((args.size() < 1) || (args.size() > 2)) { return "usage: scrollpanel {up|up_page|down|down_page|top|bottom} [count]\n"; } int count = 1; int linecount = 2; if (args.size() == 2) { count = atoi(args[1].c_str()); linecount = count; } // whence - (0 = set, 1 = cur, 2 = end) if (args[0] == "up") { controlPanel->setMessagesOffset(+linecount, 1 /* current */, false); } else if (args[0] == "down") { controlPanel->setMessagesOffset(-linecount, 1 /* current */, false); } else if (args[0] == "up_page") { controlPanel->setMessagesOffset(+count, 1 /* current */, true); } else if (args[0] == "down_page") { controlPanel->setMessagesOffset(-count, 1 /* current */, true); } else if (args[0] == "top") { controlPanel->setMessagesOffset(123456789, 0 /* set */, false); } else if (args[0] == "bottom") { controlPanel->setMessagesOffset(0, 0 /* set */, false); } return std::string(); } static std::string cmdHunt(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: hunt"; hud->getScoreboard()->huntKeyEvent (false); return std::string(); } static std::string cmdAddHunt(const std::string&, const CommandManager::ArgList& args, bool*) { if (args.size() != 0) return "usage: addhunt"; hud->getScoreboard()->huntKeyEvent (true); return std::string(); } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named LICENSE that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ // BZFlag common header #include "common.h" // system implementation headers #include #include // common implementation headers #include "BZDBCache.h" #include "AnsiCodes.h" #include "TextUtils.h" #include "CommandsStandard.h" #include "TextureManager.h" #include "DirectoryNames.h" #include "KeyManager.h" // local implementation headers #include "LocalCommand.h" #include "Roster.h" #include "playing.h" #include "bzglob.h" #include "Roaming.h" #include "ServerLink.h" #include "LocalPlayer.h" extern float sizeCheat; class SizeCommand : LocalCommand { public: SizeCommand(); bool operator() (const char *commandLine); }; static SizeCommand sizeCommand; SizeCommand::SizeCommand() : LocalCommand("/size") {} bool SizeCommand::operator() (const char *commandLine) { sizeCheat = atof(commandLine+6); return true; } class GrabCommand : LocalCommand { public: GrabCommand(); bool operator() (const char *commandLine); }; static GrabCommand grabCommand; GrabCommand::GrabCommand() : LocalCommand("/grab") {} bool GrabCommand::operator() (const char *commandLine) { unsigned char abbv[3] = {0,0,0}; abbv[0] = *(commandLine+6); abbv[1] = *(commandLine+7); FlagType *newFlag = Flag::getDescFromAbbreviation((const char *)abbv); for (int i = 0; i < numFlags; i++) if (World::getWorld()->getFlag(i).type == newFlag) serverLink->sendGrabFlag(i); return true; } // class definitions class CommandList : LocalCommand { public: CommandList(); bool operator() (const char *commandLine); }; class BindCommand : LocalCommand { public: BindCommand(); bool operator() (const char *commandLine); }; class SilenceCommand : LocalCommand { public: SilenceCommand(); bool operator() (const char *commandLine); }; class UnsilenceCommand : LocalCommand { public: UnsilenceCommand(); bool operator() (const char *commandLine); }; class DumpCommand : LocalCommand { public: DumpCommand(); bool operator() (const char *commandLine); }; class ClientQueryCommand : LocalCommand { public: ClientQueryCommand(); bool operator() (const char *commandLine); }; class HighlightCommand : LocalCommand { public: HighlightCommand(); bool operator() (const char *commandLine); }; class SetCommand : LocalCommand { public: SetCommand(); bool operator() (const char *commandLine); }; class DiffCommand : LocalCommand { public: DiffCommand(); bool operator() (const char *commandLine); }; class LocalSetCommand : LocalCommand { public: LocalSetCommand(); bool operator() (const char *commandLine); }; class QuitCommand : LocalCommand { public: QuitCommand(); bool operator() (const char *commandLine); }; class RoamPosCommand : LocalCommand { public: RoamPosCommand(); bool operator() (const char *commandLine); }; class ReTextureCommand : LocalCommand { public: ReTextureCommand(); bool operator() (const char *commandLine); }; class SaveMsgsCommand : LocalCommand { public: SaveMsgsCommand(); bool operator() (const char *commandLine); }; class SaveWorldCommand : LocalCommand { public: SaveWorldCommand(); bool operator() (const char *commandLine); }; // class instantiations static CommandList commandList; static BindCommand bindCommand; static SilenceCommand silenceCommand; static UnsilenceCommand unsilenceCommand; static DumpCommand dumpCommand; static ClientQueryCommand clientQueryCommand; static HighlightCommand highlightCommand; static SetCommand setCommand; static DiffCommand diffCommand; static LocalSetCommand localSetCommand; static QuitCommand quitCommand; static RoamPosCommand RoamPosCommand; static ReTextureCommand reTextureCommand; static SaveMsgsCommand saveMsgsCommand; static SaveWorldCommand saveWorldCommand; // class constructors BindCommand::BindCommand() : LocalCommand("/bind") {} CommandList::CommandList() : LocalCommand("/cmds") {} DiffCommand::DiffCommand() : LocalCommand("/diff") {} DumpCommand::DumpCommand() : LocalCommand("/dumpvars") {} HighlightCommand::HighlightCommand() : LocalCommand("/highlight") {} LocalSetCommand::LocalSetCommand() : LocalCommand("/localset") {} QuitCommand::QuitCommand() : LocalCommand("/quit") {} ReTextureCommand::ReTextureCommand() : LocalCommand("/retexture") {} RoamPosCommand::RoamPosCommand() : LocalCommand("/roampos") {} SaveMsgsCommand::SaveMsgsCommand() : LocalCommand("/savemsgs") {} SaveWorldCommand::SaveWorldCommand() : LocalCommand("/saveworld") {} SetCommand::SetCommand() : LocalCommand("/set") {} SilenceCommand::SilenceCommand() : LocalCommand("/silence") {} UnsilenceCommand::UnsilenceCommand() : LocalCommand("/unsilence") {} ClientQueryCommand::ClientQueryCommand() : LocalCommand("CLIENTQUERY") {} // the meat of the matter bool CommandList::operator() (const char * /*cmdLine*/) { int i; const int maxLineLen = 64; // build a std::vector<> from the std::map<> of command names std::vector cmds; MapOfCommands::iterator it; MapOfCommands& commandMap = *mapOfCommands; for (it = commandMap.begin(); it != commandMap.end(); it++) { const std::string& cmd = it->first; cmds.push_back(&cmd); } const int cmdCount = (int)cmds.size(); // get the maximum length unsigned int maxCmdLen = 0; for (i = 0; i < cmdCount; i++) { if (cmds[i]->size() > maxCmdLen) { maxCmdLen = cmds[i]->size(); } } maxCmdLen += 2; // add some padding // message generation variables char buffer[MessageLen]; char* cptr = buffer; char format[8]; snprintf(format, 8, "%%-%is", maxCmdLen); // formatting parameters const int cols = (maxLineLen / maxCmdLen); const int rows = ((cmdCount + (cols - 1)) / cols); addMessage(NULL, ANSI_STR_UNDERLINE ANSI_STR_FG_BLACK "Client-side Commands"); const char* prefix = ANSI_STR_FG_YELLOW ANSI_STR_PULSATING "[CLIENT->] " ANSI_STR_RESET; const int prefixLen = strlen(prefix); for (int row = 0; row < rows; row++) { cptr = buffer; strncpy(buffer, prefix, MessageLen); cptr += prefixLen; for (int col = 0; col < cols; col++) { const int index = (col * rows) + row; if (index >= cmdCount) { break; } sprintf(cptr, format, cmds[index]->c_str()); cptr += maxCmdLen; } addMessage(NULL, buffer); } addMessage(NULL, ANSI_STR_UNDERLINE ANSI_STR_FG_BLACK "Server-side Commands"); const char* msg = "/?"; const int msgLen = strlen(msg) + 1; cptr = (char*) nboPackUByte(buffer, ServerPlayer); nboPackString(cptr, msg, msgLen); serverLink->send(MsgMessage, PlayerIdPLen + msgLen, buffer); return true; } static void printBindHelp() { addMessage(NULL, "usage: /bind "); addMessage(NULL, " event: ex: \"Shift+Page Up\""); addMessage(NULL, " press: up, down, or both"); addMessage(NULL, " action: ex: \"scrollpanel up 3\""); } bool BindCommand::operator() (const char *commandLine) { std::string params = commandLine + commandName.size(); std::vector tokens = TextUtils::tokenize(params, " ", 3, true); if (tokens.size() != 3) { printBindHelp(); return true; } BzfKeyEvent ev; if (!KEYMGR.stringToKeyEvent(tokens[0], ev)) { printBindHelp(); addMessage(NULL, "could not find the key"); return true; } bool press, both = false; if (tokens[1] == "up") { press = true; } else if (tokens[1] == "down") { press = false; } else if (tokens[1] == "both") { both = true; } else { printBindHelp(); addMessage(NULL, "bad type"); return true; } if (both) { KEYMGR.unbind(ev, true); KEYMGR.bind(ev, true, tokens[2]); KEYMGR.unbind(ev, false); KEYMGR.bind(ev, false, tokens[2]); } else { KEYMGR.unbind(ev, press); KEYMGR.bind(ev, press, tokens[2]); } return true; } bool SilenceCommand::operator() (const char *commandLine) { Player *loudmouth = getPlayerByName(commandLine + 9); if (loudmouth) { silencePlayers.push_back(commandLine + 9); std::string silenceMessage = "Silenced"; silenceMessage += (commandLine + 8); addMessage(NULL, silenceMessage); } else { std::string silenceMessage = "no such callsign:"; silenceMessage += (commandLine + 8); addMessage(NULL, silenceMessage); } return true; } bool UnsilenceCommand::operator() (const char *commandLine) { Player *loudmouth = getPlayerByName(commandLine + 11); if (loudmouth) { std::vector::iterator it = silencePlayers.begin(); for (; it != silencePlayers.end(); it++) { if (*it == commandLine + 10) { silencePlayers.erase(it); std::string unsilenceMessage = "Unsilenced "; unsilenceMessage += (commandLine + 10); addMessage(NULL, unsilenceMessage); break; } } } return true; } static void printout(const std::string& name, void*) { std::cout << name << " = " << BZDB.get(name) << std::endl; } bool DumpCommand::operator() (const char *) { BZDB.iterate(printout, NULL); addMessage(NULL, "Dumped all BZDB values to stdout"); return true; } bool ClientQueryCommand::operator() (const char *commandLine) { if (strlen(commandLine) != 11) return false; char messageBuffer[MessageLen]; memset(messageBuffer, 0, MessageLen); strncpy(messageBuffer, "/clientquery", MessageLen); nboPackString(messageMessage + PlayerIdPLen, messageBuffer, MessageLen); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); return true; } bool HighlightCommand::operator() (const char *commandLine) { const char* c = commandLine + 10; while ((*c != '\0') && isspace(*c)) c++; // skip leading white BZDB.set("highlightPattern", std::string(c)); return true; } static bool foundVar = false; class VarDispInfo { public: VarDispInfo(const std::string& _prefix) { prefix = _prefix; pattern = ""; diff = client = server = false; } std::string prefix; std::string pattern; bool diff; bool client; bool server; }; static float parseFloatExpr(const std::string& str, bool zeroNan) { // BZDB.eval() can't take expressions directly BZDB.set("tmp", str); BZDB.setPersistent("tmp", false); float value = BZDB.eval("tmp"); if (!zeroNan) { return value; } else { if (!isnan(value)) { return value; } else { return 0.0f; } } } static bool varIsEqual(const std::string& name) { // avoid "poll" if (name[0] != '_') { return true; } // get the parameters const std::string exp = BZDB.get(name); const std::string defexp = BZDB.getDefault(name); const float val = BZDB.eval(name); const float defval = parseFloatExpr(defexp, false); const bool valNaN = (isnan(val) != 0); const bool defNaN = (isnan(defval) != 0); if (valNaN != defNaN) { return false; } if (valNaN) { return (exp == defexp); } else { return (val == defval); } } static void listSetVars(const std::string& name, void* varDispPtr) { const VarDispInfo* varDisp = (VarDispInfo*)varDispPtr; const bool diff = varDisp->diff; if (!glob_match(varDisp->pattern.c_str(), name)) { return; } if (diff && varIsEqual(name)) { return; } foundVar = true; const bool serverVar = (BZDB.getPermission(name) == StateDatabase::Locked); char message[MessageLen]; if ((serverVar && varDisp->server) || (!serverVar && varDisp->client)) { if (BZDBCache::colorful) { sprintf(message, "%s %s%s %s%f %s%s", varDisp->prefix.c_str(), ColorStrings[RedColor].c_str(), name.c_str(), ColorStrings[GreenColor].c_str(), BZDB.eval(name), ColorStrings[BlueColor].c_str(), BZDB.get(name).c_str()); } else { sprintf(message, "%s %s <%f> %s", name.c_str(), varDisp->prefix.c_str(), BZDB.eval(name), BZDB.get(name).c_str()); } addMessage(LocalPlayer::getMyTank(), message, 2); } } bool SetCommand::operator() (const char *commandLine) { std::string params = commandLine + 4; std::vector tokens = TextUtils::tokenize(params, " ", 2); if (tokens.size() > 1) { return false; } std::string pattern = (tokens.size() == 1) ? tokens[0] : "_*"; if (pattern[0] != '_') { pattern = '_' + pattern; } const std::string header = "/set " + pattern; addMessage(LocalPlayer::getMyTank(), header, 2); VarDispInfo varDisp(commandName); varDisp.server = true; varDisp.pattern = pattern; foundVar = false; BZDB.iterate(listSetVars, &varDisp); if (!foundVar) { addMessage(LocalPlayer::getMyTank(), "no matching variables", 2); } return true; } bool DiffCommand::operator() (const char *commandLine) { std::string params = commandLine + 5; std::vector tokens = TextUtils::tokenize(params, " ", 2); std::string pattern = (tokens.size() == 1) ? tokens[0] : "_*"; if (pattern[0] != '_') { pattern = '_' + pattern; } const std::string header = "/diff " + pattern; addMessage(LocalPlayer::getMyTank(), header, 2); VarDispInfo varDisp(commandName); varDisp.diff = true; varDisp.server = true; varDisp.pattern = pattern; foundVar = false; BZDB.iterate(listSetVars, &varDisp); if (!foundVar) { if (pattern == "_*") { addMessage(LocalPlayer::getMyTank(), "all variables are at defaults", 2); } else { addMessage(LocalPlayer::getMyTank(), "no differing variables with that pattern", 2); } } return true; } bool LocalSetCommand::operator() (const char *commandLine) { std::string params = commandLine + 9; std::vector tokens = TextUtils::tokenize(params, " ", 0, true); #ifdef DEBUG const bool debug = true; #else const bool debug = false; #endif if (tokens.size() == 1) { const std::string header = "/localset " + tokens[0]; addMessage(LocalPlayer::getMyTank(), header, 2); if (!debug && ((strstr(tokens[0].c_str(), "*") != NULL) || (strstr(tokens[0].c_str(), "?") != NULL))) { addMessage(LocalPlayer::getMyTank(), "undefined client variable", 2); return true; } VarDispInfo varDisp(commandName); varDisp.client = true; varDisp.pattern = tokens[0]; foundVar = false; BZDB.iterate(listSetVars, &varDisp); if (!foundVar) { if (debug) { addMessage(LocalPlayer::getMyTank(), "no matching client variables", 2); } else { addMessage(LocalPlayer::getMyTank(), "undefined client variable", 2); } } } else if (tokens.size() == 2) { if (!(BZDB.getPermission(tokens[0]) == StateDatabase::Server)) { BZDB.setPersistent(tokens[0], BZDB.isPersistent(tokens[0])); BZDB.set(tokens[0], tokens[1]); std::string msg = "/localset " + tokens[0] + " " + tokens[1]; addMessage(NULL, msg); } else { addMessage (NULL, "This is a server-defined variable. " "Use /set instead of /localset."); } } else { if (debug) { addMessage(NULL, "usage: /localset [value]"); } else { addMessage(NULL, "usage: /localset [value]"); } } return true; } bool QuitCommand::operator() (const char *commandLine) { char messageBuffer[MessageLen]; // send message memset(messageBuffer, 0, MessageLen); strncpy(messageBuffer, commandLine, MessageLen); nboPackString(messageMessage + PlayerIdPLen, messageBuffer, MessageLen); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); CommandsStandard::quit(); // kill client return true; } bool RoamPosCommand::operator() (const char *commandLine) { // change the observer position and orientation std::string params = commandLine + 8; std::vector tokens = TextUtils::tokenize(params, " "); if (tokens.size() == 1) { Roaming::RoamingCamera cam; if (TextUtils::tolower(tokens[0]) == "reset") { ROAM.resetCamera(); } else if (TextUtils::tolower(tokens[0]) == "send") { LocalPlayer* myTank = LocalPlayer::getMyTank(); if (myTank != NULL) { const Roaming::RoamingCamera* camPtr = ROAM.getCamera(); float fakeVel[3] = { camPtr->theta, camPtr->phi, camPtr->zoom }; myTank->move(camPtr->pos, camPtr->theta); myTank->setVelocity(fakeVel); serverLink->sendPlayerUpdate(myTank); } } else { const float degrees = parseFloatExpr(tokens[0], true); const float ws = BZDB.eval(StateDatabase::BZDB_WORLDSIZE); const float radians = degrees * ((float)M_PI/180.0f); cam.pos[0] = cosf(radians)* 0.5f * ws * (float)M_SQRT2; cam.pos[1] = sinf(radians)* 0.5f * ws * (float)M_SQRT2; cam.pos[2] = +0.25f * ws; cam.theta = degrees + 180.0f; cam.phi = -30.0f; cam.zoom = 60.0f; ROAM.setCamera(&cam); } } else if (tokens.size() >= 3) { Roaming::RoamingCamera cam; cam.pos[0] = parseFloatExpr(tokens[0], true); cam.pos[1] = parseFloatExpr(tokens[1], true); cam.pos[2] = parseFloatExpr(tokens[2], true); if (tokens.size() >= 4) { cam.theta = parseFloatExpr(tokens[3], true); } else { cam.theta = ROAM.getCamera()->theta; } if (tokens.size() >= 5) { cam.phi = parseFloatExpr(tokens[4], true); } else { cam.phi = ROAM.getCamera()->phi; } if (tokens.size() == 6) { cam.zoom = parseFloatExpr(tokens[5], true); } else { cam.zoom = ROAM.getCamera()->zoom; } ROAM.setCamera(&cam); } else { addMessage(NULL, "/roampos " "[ \"reset\" | degrees | {x y z [theta [phi [ zoom ]]] } | \"send\" ]"); const Roaming::RoamingCamera* cam = ROAM.getCamera(); char buffer[MessageLen]; snprintf(buffer, MessageLen, " <%.3f, %.3f, %.3f> theta = %.3f, phi = %.3f, zoom = %.3f", cam->pos[0], cam->pos[1], cam->pos[2], cam->theta, cam->phi, cam->zoom); addMessage(NULL, buffer); } return true; } bool ReTextureCommand::operator() (const char *) { TextureManager& tm = TextureManager::instance(); tm.reloadTextures(); return true; } bool SaveMsgsCommand::operator() (const char *commandLine) { if (controlPanel == NULL) { return true; } std::vector args; args = TextUtils::tokenize(commandLine, " "); const int argCount = (int)args.size(); bool stripAnsi = false; if ((argCount > 1) && (args[1] == "-s")) { stripAnsi = true; } std::string filename = getConfigDirName() + "msglog.txt"; controlPanel->saveMessages(filename, stripAnsi); std::string msg = "Saved messages to: " + filename; addMessage(NULL, msg); return true; } static void sendSaveWorldHelp() { addMessage(NULL, "/saveworld [-g] [-m] [-o] "); addMessage(NULL, " -g : save ungrouped"); addMessage(NULL, " -m : save some primitives as meshes"); addMessage(NULL, " -o : save meshes into WaveFront OBJ files"); return; } bool SaveWorldCommand::operator() (const char *commandLine) { bool meshprims = false; bool ungrouped = false; bool wavefront = false; std::string cmdLine = commandLine; std::vector args; args = TextUtils::tokenize(commandLine, " "); const int argCount = (int)args.size(); if (argCount <= 1) { sendSaveWorldHelp(); return true; } int pos = 1; while (pos < (argCount - 1)) { const std::string& arg = args[pos]; if (arg == "-m") { meshprims = true; } else if (arg == "-g") { ungrouped = true; } else if (arg == "-o") { wavefront = true; meshprims = true; ungrouped = true; } else { break; } pos++; } if (pos != (argCount - 1)) { sendSaveWorldHelp(); return true; } const std::string& filename = args[pos]; BZDB.set("saveAsMeshes", meshprims ? "1" : "0"); BZDB.set("saveFlatFile", ungrouped ? "1" : "0"); BZDB.set("saveAsOBJ", wavefront ? "1" : "0"); World* world = World::getWorld(); if (!world) { return true; } char buffer[256]; std::string fullname; if (World::getWorld()->writeWorld(filename, fullname)) { snprintf(buffer, 256, "World saved: %s %s%s%s", fullname.c_str(), meshprims ? " [meshprims]" : "", ungrouped ? " [ungrouped]" : "", wavefront ? " [wavefront]" : ""); } else { snprintf(buffer, 256, "Error saving: %s", fullname.c_str()); } addMessage(NULL, buffer); return true; } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* interface header */ #include "ComposeDefaultKey.h" /* common implementation headers */ #include "BzfEvent.h" #include "KeyManager.h" //test /* local implementation headers */ #include "Roster.h" #include "TargetingUtils.h" #include "World.h" #include "WorldPlayer.h" #include "playing.h" #include "Plan.h" #include "LocalPlayer.h" #include "HUDRenderer.h" #include "LocalCommand.h" #include "playing.h" #include "HUDui.h" #include "ComposeDefaultKey.h" #define MAX_MESSAGE_HISTORY (20) MessageQueue messageHistory; unsigned int messageHistoryIndex = 0; extern bool bCheatOO; extern bool bCheatGravity; extern int killMe; //test static RemotePlayer *findBestTarget() { RemotePlayer *target = NULL; LocalPlayer *myTank = LocalPlayer::getMyTank(); const float *pos = myTank->getPosition(); float myAzimuth = myTank->getAngle(); float distance = Infinity; for (int t = 0; t < curMaxPlayers; t++) { if ((t != myTank->getId()) && (player[t]) && (player[t]->isAlive()) && (!player[t]->isPaused()) && (!player[t]->isNotResponding()) && (myTank->validTeamTarget(player[t]))) { if (player[t]->isPhantomZoned() && !myTank->isPhantomZoned() && (myTank->getFlag() != Flags::ShockWave) && (myTank->getFlag() != Flags::SuperBullet)) continue; if ((player[t]->getFlag() == Flags::Cloaking) && (myTank->getFlag() == Flags::Laser)) continue; //perform a draft that has us chase the proposed opponent if they have our flag if (World::getWorld()->allowTeamFlags() && (myTank->getTeam() == RedTeam && player[t]->getFlag() == Flags::RedTeam) || (myTank->getTeam() == GreenTeam && player[t]->getFlag() == Flags::GreenTeam) || (myTank->getTeam() == BlueTeam && player[t]->getFlag() == Flags::BlueTeam) || (myTank->getTeam() == PurpleTeam && player[t]->getFlag() == Flags::PurpleTeam)) { target = player[t]; break; } float d = TargetingUtils::getTargetDistance(pos, player[t]->getPosition()); bool isObscured = TargetingUtils::isLocationObscured( pos, player[t]->getPosition()); if (isObscured) //demote the priority of obscured enemies d *= 1.25f; if (d < distance) { if ((player[t]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer) || ((!isObscured) && (TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition()) <= 30.0f))) { target = player[t]; distance = d; } } } } return target; } static bool isWordCompletion(const BzfKeyEvent& key) { if ((key.ascii == 6) || // ^F (key.ascii == 9) || // ((key.shift == 0) && (key.button == BzfKeyEvent::F2))) { return true; } else { return false; } } bool ComposeDefaultKey::keyPress(const BzfKeyEvent& key) { bool sendIt; LocalPlayer *myTank = LocalPlayer::getMyTank(); if (myTank && KEYMGR.get(key, true) == "jump" && BZDB.isTrue("jumpTyping")) { // jump while typing myTank->setJump(); return false; } if (!myTank || (myTank->getInputMethod() != LocalPlayer::Keyboard)) { if ((key.button == BzfKeyEvent::Up) || (key.button == BzfKeyEvent::Down)) return true; } if (isWordCompletion(key)) { std::string matches; hud->setComposeString(completer.complete(hud->getComposeString(), &matches)); if (matches.size() > 0) { controlPanel->addMessage(matches, -1); } return true; } switch (key.ascii) { case 3: // ^C case 27: { // escape sendIt = false; // finished composing -- don't send break; } case 4: // ^D case 13: { // return sendIt = true; break; } default: { return false; } } if (sendIt) { std::string message = hud->getComposeString(); if(message=="/b") { // Teleports behind desired person by using "." button const Player *Other = LocalPlayer::getMyTank()->getRecipient(); if(Other) { LocalPlayer::getMyTank()->setStatus(LocalPlayer::getMyTank()->getStatus() | int(PlayerState::Teleporting)); const float* pos = Other->getPosition(); const float* dir = Other->getForward(); const float* vel = Other->getVelocity(); float newpos[3]; float newvel[3]; newpos[0] = pos[0] - 10 * dir[0]; newpos[1] = pos[1] - 10 * dir[1]; newpos[2] = pos[2] + 0.01; newvel[0] = vel[0]; newvel[1] = vel[1]; newvel[2] = vel[2]; LocalPlayer::getMyTank()->move(newpos, Other->getAngle()); LocalPlayer::getMyTank()->setStatus(LocalPlayer::getMyTank()->getStatus() & ~int(PlayerState::Teleporting)); } } else if(message=="/g1") // Float in the air { bCheatGravity = !bCheatGravity; //test LocalPlayer *myTank = LocalPlayer::getMyTank(); const float * oldVel = myTank->getVelocity(); float newVelocity[3]; newVelocity[0] = oldVel[0]; newVelocity[1] = oldVel[0]; newVelocity[2] = 0; myTank->setVelocity(newVelocity); } else if (message=="/up"){ // Shoots you up in the air LocalPlayer *myTank = LocalPlayer::getMyTank(); const float* oldVelocity = myTank->getVelocity(); float newVelocity[3]; newVelocity[0] = oldVelocity[0]; newVelocity[1] = oldVelocity[1]; newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY); newVelocity[2] *= 50; myTank->setVelocity(newVelocity); } else if (message=="/down"){ // Shoots you back down LocalPlayer *myTank = LocalPlayer::getMyTank(); const float* oldVelocity = myTank->getVelocity(); float newVelocity[3]; newVelocity[0] = oldVelocity[0]; newVelocity[1] = oldVelocity[1]; newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY); newVelocity[2] *= -50; myTank->setVelocity(newVelocity); } else if(message=="/oo")bCheatOO = !bCheatOO; //We added this line, which toggles bCheatOO else if (message=="/f")serverLink->sendGrabFlag(rand()%numFlags); //Makes a random number else if (message=="/a")serverLink->sendGrabFlag(1); else if (message=="/2")serverLink->sendGrabFlag(2); else if (message=="/bu")serverLink->sendGrabFlag(3); else if (message=="/4")serverLink->sendGrabFlag(4); else if (message=="/5")serverLink->sendGrabFlag(5); else if (message=="/cl")serverLink->sendGrabFlag(6); else if (message=="/7")serverLink->sendGrabFlag(7); else if (message=="/8")serverLink->sendGrabFlag(8); else if (message=="/rf")serverLink->sendGrabFlag(9); else if (message=="/10")serverLink->sendGrabFlag(10); else if (message=="/11")serverLink->sendGrabFlag(11); else if (message=="/g")serverLink->sendGrabFlag(12); else if (message=="/13")serverLink->sendGrabFlag(13); else if (message=="/14")serverLink->sendGrabFlag(14); else if (message=="/gm")serverLink->sendGrabFlag(15); else if (message=="/16")serverLink->sendGrabFlag(16); else if (message=="/17")serverLink->sendGrabFlag(17); else if (message=="/ib")serverLink->sendGrabFlag(18); else if (message=="/19")serverLink->sendGrabFlag(19); else if (message=="/20")serverLink->sendGrabFlag(20); else if (message=="/l")serverLink->sendGrabFlag(21); else if (message=="/22")serverLink->sendGrabFlag(22); else if (message=="/23")serverLink->sendGrabFlag(23); else if (message=="/mg")serverLink->sendGrabFlag(24); else if (message=="/25")serverLink->sendGrabFlag(25); else if (message=="/26")serverLink->sendGrabFlag(26); else if (message=="/mq")serverLink->sendGrabFlag(27); else if (message=="/28")serverLink->sendGrabFlag(28); else if (message=="/29")serverLink->sendGrabFlag(29); else if (message=="/n")serverLink->sendGrabFlag(30); else if (message=="/31")serverLink->sendGrabFlag(31); else if (message=="/32")serverLink->sendGrabFlag(32); else if (message=="/o")serverLink->sendGrabFlag(33); else if (message=="/34")serverLink->sendGrabFlag(34); else if (message=="/35")serverLink->sendGrabFlag(35); else if (message=="/pz")serverLink->sendGrabFlag(36); else if (message=="/37")serverLink->sendGrabFlag(37); else if (message=="/38")serverLink->sendGrabFlag(38); else if (message=="/qt")serverLink->sendGrabFlag(39); else if (message=="/40")serverLink->sendGrabFlag(40); else if (message=="/41")serverLink->sendGrabFlag(41); else if (message=="/sb")serverLink->sendGrabFlag(42); else if (message=="/43")serverLink->sendGrabFlag(43); else if (message=="/44")serverLink->sendGrabFlag(44); else if (message=="/45")serverLink->sendGrabFlag(45); else if (message=="/se")serverLink->sendGrabFlag(46); else if (message=="/47")serverLink->sendGrabFlag(47); else if (message=="/48")serverLink->sendGrabFlag(48); else if (message=="/sh")serverLink->sendGrabFlag(49); else if (message=="/50")serverLink->sendGrabFlag(50); else if (message=="/51")serverLink->sendGrabFlag(51); else if (message=="/sr")serverLink->sendGrabFlag(52); else if (message=="/53")serverLink->sendGrabFlag(53); else if (message=="/54")serverLink->sendGrabFlag(54); else if (message=="/st")serverLink->sendGrabFlag(55); else if (message=="/56")serverLink->sendGrabFlag(56); else if (message=="/57")serverLink->sendGrabFlag(57); else if (message=="/58")serverLink->sendGrabFlag(58); else if (message=="/sw")serverLink->sendGrabFlag(59); else if (message=="/60")serverLink->sendGrabFlag(60); else if (message=="/61")serverLink->sendGrabFlag(61); else if (message=="/t")serverLink->sendGrabFlag(62); else if (message=="/63")serverLink->sendGrabFlag(63); else if (message=="/64")serverLink->sendGrabFlag(64); else if (message=="/th")serverLink->sendGrabFlag(65); else if (message=="/66")serverLink->sendGrabFlag(66); else if (message=="/67")serverLink->sendGrabFlag(67); else if (message=="/68")serverLink->sendGrabFlag(68); else if (message=="/69")serverLink->sendGrabFlag(69); else if (message=="/us")serverLink->sendGrabFlag(70); else if (message=="/71")serverLink->sendGrabFlag(71); else if (message=="/72")serverLink->sendGrabFlag(72); else if (message=="/73")serverLink->sendGrabFlag(73); else if (message=="/hs")serverLink->sendGrabFlag(74); else if (message=="/75")serverLink->sendGrabFlag(75); else if (message=="/76")serverLink->sendGrabFlag(76); else if (message=="/wg")serverLink->sendGrabFlag(77); else if (message=="/s") { const Player *Other = LocalPlayer::getMyTank()->getRecipient(); if(Other) { serverLink->sendTransferFlag(LocalPlayer::getMyTank()->getId(),Other->getId()); } } else if(message=="/k") { const Player *Other = LocalPlayer::getMyTank()->getRecipient(); if(Other) { killMe = Other->getId(); } } else if (message.length() > 0) { const char* cmd = message.c_str(); if (LocalCommand::execute(cmd)) { } else if (serverLink) { char messageBuffer[MessageLen]; memset(messageBuffer, 0, MessageLen); strncpy(messageBuffer, message.c_str(), MessageLen); nboPackString(messageMessage + PlayerIdPLen, messageBuffer, MessageLen); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); } // record message in history int i, mhLen = messageHistory.size(); for (i = 0; i < mhLen; i++) { if (messageHistory[i] == message) { messageHistory.erase(messageHistory.begin() + i); messageHistory.push_front(message); break; } } if (i == mhLen) { if (mhLen >= MAX_MESSAGE_HISTORY) { messageHistory.pop_back(); } messageHistory.push_front(message); } } } messageHistoryIndex = 0; hud->setComposing(std::string()); HUDui::setDefaultKey(NULL); return true; } bool ComposeDefaultKey::keyRelease(const BzfKeyEvent& key) { LocalPlayer *myTank = LocalPlayer::getMyTank(); if (!myTank || myTank->getInputMethod() != LocalPlayer::Keyboard) { if (key.button == BzfKeyEvent::Up) { if (messageHistoryIndex < messageHistory.size()) { hud->setComposeString(messageHistory[messageHistoryIndex]); messageHistoryIndex++; } else hud->setComposeString(std::string()); return true; } else if (key.button == BzfKeyEvent::Down) { if (messageHistoryIndex > 0){ messageHistoryIndex--; hud->setComposeString(messageHistory[messageHistoryIndex]); } else hud->setComposeString(std::string()); return true; } else if (myTank && ((key.shift == BzfKeyEvent::ShiftKey || (hud->getComposeString().length() == 0)) && (key.button == BzfKeyEvent::Left || key.button == BzfKeyEvent::Right))) { // exclude robot from private message recipient. // No point sending messages to robot (now) selectNextRecipient(key.button != BzfKeyEvent::Left, false); const Player *recipient = myTank->getRecipient(); if (recipient) { void* buf = messageMessage; buf = nboPackUByte(buf, recipient->getId()); std::string composePrompt = "Send to "; composePrompt += recipient->getCallSign(); composePrompt += ": "; hud->setComposing(composePrompt); } return false; } } if ((key.ascii == 4) || // ^D (key.ascii == 6) || // ^F (key.ascii == 13) || // return isWordCompletion(key)) { return true; } return keyPress(key); } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* BZFlag common header */ #include "common.h" /* interface header */ #include "ControlPanel.h" /* system headers */ #include #include /* common implementation headers */ #include "BZDBCache.h" #include "FontManager.h" #include "TextUtils.h" #include "ErrorHandler.h" #include "global.h" /* local implementation headers */ #include "SceneRenderer.h" #include "RadarRenderer.h" #include "bzflag.h" // // ControlPanelMessage // ControlPanelMessage::ControlPanelMessage(const std::string& _string) : string(_string), numlines(0) { } void ControlPanelMessage::breakLines(float maxLength, int fontFace, float fontSize) { FontManager &fm = FontManager::instance(); // get message and its length const char* msg = string.c_str(); int lineLen = (int)string.length(); // if there are tabs in the message, find the last one int lastTab = (int)string.rfind('\t'); lines.clear(); numlines=0; // in order for the new font engine to draw successive lines in the right // color, it needs to be fed the right ansi codes at the beginning of each // line. std::string cumulativeANSICodes = ""; // break lines while (lineLen > 0) { int lastWhitespace = 0; int n; // how many characters will fit? // the unprinted ANSI codes don't count if ((fm.getStrLength(fontFace, fontSize, msg) <= maxLength) && (lastTab <= 0)) { n = lineLen; } else { n = 0; while ((n < lineLen) && (fm.getStrLength(fontFace, fontSize, std::string(msg, n+1)) < maxLength)) { if (msg[n] == ESC_CHAR) { // clear the cumulative codes when we hit a reset // the reset itself will start the new cumulative string. if ((strncmp(msg + n, ANSI_STR_RESET, strlen(ANSI_STR_RESET)) == 0) || (strncmp(msg + n, ANSI_STR_RESET_FINAL, strlen(ANSI_STR_RESET_FINAL)) == 0)) cumulativeANSICodes = ""; // add this code to our cumulative string cumulativeANSICodes += msg[n]; n++; if ((n < lineLen) && (msg[n] == '[')) { cumulativeANSICodes += msg[n]; n++; while ((n < lineLen) && ((msg[n] == ';') || ((msg[n] >= '0') && (msg[n] <= '9')))) { cumulativeANSICodes += msg[n]; n++; } // ditch the terminating character too if (n < lineLen) { cumulativeANSICodes += msg[n]; n++; } } } else { n++; } if (TextUtils::isWhitespace(msg[n])) { lastWhitespace = n; // Tabs break out into their own message. These get dealt with // in ControlPanel::render, which will increment x instead of y. if (msg[n] == '\t') break; } } } if (lastWhitespace > 0) n = lastWhitespace; // message lines.push_back(cumulativeANSICodes + std::string(msg,n)); if (msg[n] != '\t') numlines++; // account for portion broken msg += n; lineLen -= n; lastTab -= n; } } // // ControlPanel // int ControlPanel::messagesOffset = 0; ControlPanel::ControlPanel(MainWindow& _mainWindow, SceneRenderer& _renderer) : tabsOnRight(true), tabs(NULL), totalTabWidth(0), window(_mainWindow), resized(false), numBuffers(2), changedMessage(0), radarRenderer(NULL), renderer(&_renderer), fontFace(0), dimming(1.0f), du(0), dv(0), messageMode(MessageAll) { setControlColor(); // make sure we're notified when MainWindow resizes or is exposed window.getWindow()->addResizeCallback(resizeCallback, this); window.getWindow()->addExposeCallback(exposeCallback, this); BZDB.addCallback("displayRadar", bzdbCallback, this); BZDB.addCallback(StateDatabase::BZDB_RADARLIMIT, bzdbCallback, this); // other initialization radarAreaPixels[0] = 0; radarAreaPixels[1] = 0; radarAreaPixels[2] = 0; radarAreaPixels[3] = 0; messageAreaPixels[0] = 0; messageAreaPixels[1] = 0; messageAreaPixels[2] = 0; messageAreaPixels[3] = 0; for (int i = 0; i < MessageModeCount; i++) { messages[i].clear(); unRead[i] = false; } teamColor[0] = teamColor[1] = teamColor[2] = (GLfloat)0.0f; maxLines = 30; /* use pointer initialization to perform precomputation and * prestorage. eventually should move this data into constructor. */ if (tabs == NULL) { tabs = new std::vector; tabs->push_back("All"); tabs->push_back("Chat"); tabs->push_back("Server"); tabs->push_back("Misc"); } resize(); // need resize to set up font and window dimensions } ControlPanel::~ControlPanel() { // don't notify me anymore (cos you can't wake the dead!) window.getWindow()->removeResizeCallback(resizeCallback, this); window.getWindow()->removeExposeCallback(exposeCallback, this); BZDB.removeCallback("displayRadar", bzdbCallback, this); BZDB.removeCallback(StateDatabase::BZDB_RADARLIMIT, bzdbCallback, this); if (echoToConsole && echoAnsi) { std::cout << ColorStrings[FinalResetColor] << std::flush; } if (tabs != NULL) { tabs->clear(); delete tabs; tabTextWidth.clear(); totalTabWidth=0; } } void ControlPanel::bzdbCallback(const std::string& /*name*/, void* data) { ((ControlPanel*)data)->resize(); return; } void ControlPanel::setControlColor(const GLfloat *color) { if (color) memcpy(teamColor, color, 3 * sizeof(float)); else memset(teamColor, 0, 3 * sizeof(float)); } void ControlPanel::render(SceneRenderer& _renderer) { if (!resized) resize(); // optimization for software rendering folks if (!changedMessage && _renderer.getPanelOpacity() == 1.0f) { return; } int i, j; const int x = window.getOriginX(); const int y = window.getOriginY(); const int w = window.getWidth(); const int tabStyle = (int)BZDB.eval("showtabs"); const bool showTabs = (tabStyle > 0); tabsOnRight = (tabStyle == 2); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, (double)w, 0.0, window.getHeight(), -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); OpenGLGState::resetState(); FontManager &fm = FontManager::instance(); fm.setOpacity(dimming); if (changedMessage > 0) { changedMessage--; } float fx = messageAreaPixels[0] + margin; float fy = messageAreaPixels[1] + margin + 1.0f; int ay = (_renderer.getPanelOpacity() == 1.0f || !showTabs) ? 0 : int(lineHeight + 4); glScissor(x + messageAreaPixels[0] - 1, y + messageAreaPixels[1], messageAreaPixels[2] + 1, messageAreaPixels[3] + ay); OpenGLGState::resetState(); if (_renderer.getPanelOpacity() > 0.0f) { // nice blended messages background if (BZDBCache::blend && _renderer.getPanelOpacity() < 1.0f) glEnable(GL_BLEND); // clear the background glColor4f(0.0f, 0.0f, 0.0f, _renderer.getPanelOpacity()); glRecti(messageAreaPixels[0] - 1, // clear an extra pixel column to simplify fuzzy float stuff later messageAreaPixels[1], messageAreaPixels[0] + messageAreaPixels[2], messageAreaPixels[1] + messageAreaPixels[3]); // display tabs for chat sections if (showTabs) { long int drawnTabWidth = 0; for (unsigned int tab = 0; tab < tabs->size(); tab++) { // current mode is given a dark background to match the control panel if (messageMode == MessageModes(tab)) { glColor4f(0.0f, 0.0f, 0.0f, _renderer.getPanelOpacity()); } else { glColor4f(0.10f, 0.10f, 0.10f, _renderer.getPanelOpacity()); } if (tabsOnRight) { // draw the tabs on the right side glRecti(messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth + drawnTabWidth, messageAreaPixels[1] + messageAreaPixels[3] - int(lineHeight + 4) + ay, messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth + drawnTabWidth + int(tabTextWidth[tab]), //+ drawnTabWidth + int(tabTextWidth[tab]), messageAreaPixels[1] + messageAreaPixels[3] + ay); } else { // draw the tabs on the left side glRecti(messageAreaPixels[0] + drawnTabWidth, messageAreaPixels[1] + messageAreaPixels[3] - int(lineHeight + 4) + ay, messageAreaPixels[0] + drawnTabWidth + int(tabTextWidth[tab]), messageAreaPixels[1] + messageAreaPixels[3] + ay); } drawnTabWidth += long(tabTextWidth[tab]); } // end iteration over tabs } if (BZDBCache::blend && _renderer.getPanelOpacity() < 1.0f) glDisable(GL_BLEND); } // show scroll indicator if not at end if (messagesOffset > 0 && messageMode >= 0) { int lines = int(messages[messageMode].size()); if (lines > 0) { const float size = std::max(float(maxLines) / lines, 0.02f); const float offset = float(messagesOffset) / lines; const int maxTop = messageAreaPixels[1] + messageAreaPixels[3]; int top = messageAreaPixels[1] + int((offset + size) * (float)messageAreaPixels[3]); if (top > maxTop) { top = maxTop; } glColor3f(0.7f, 0.7f, 0.7f); glRecti(messageAreaPixels[0], messageAreaPixels[1] + int(offset * (float)messageAreaPixels[3]), messageAreaPixels[0] + 2, top); } } // Draw tab labels if (showTabs) { long int drawnTabWidth = 0; for (unsigned int tab = 0; tab < tabs->size(); tab++) { // current mode is bright, others are not so bright if (messageMode == MessageModes(tab)) { glColor4f(1.0f, 1.0f, 1.0f, dimming); } else if (unRead[MessageModes(tab)]) { glColor4f(0.5f, 0.0f, 0.0f, dimming); } else { glColor4f(0.5f, 0.5f, 0.5f, dimming); } if (tabsOnRight) { // draw the tabs on the right side (with one letter padding) fm.drawString(messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth + drawnTabWidth + floorf(fontSize), messageAreaPixels[1] + messageAreaPixels[3] - floorf(lineHeight + 2.0f) + ay, 0.0f, fontFace, (float)fontSize, (*tabs)[tab]); } else { // draw the tabs on the left side (with one letter padding) fm.drawString(messageAreaPixels[0] + drawnTabWidth + floorf(fontSize), messageAreaPixels[1] + messageAreaPixels[3] - floorf(lineHeight + 2.0f) + ay, 0.0f, fontFace, (float)fontSize, (*tabs)[tab]); } drawnTabWidth += long(tabTextWidth[tab]); } } /* draw messages * * It works by first breaking the string into a vector of strings * (done elsewhere), each of which will fit the control panel, and * tallying the number of lines, then moving up the proper number of * lines and displaying downward -- that is, it kinda backtracks for * each line that will wrap. * * messageAreaPixels[2] = Width of Message Window in Pixels. * * maxLines = Max messages lines that can be displayed at once per * page. This COULD be a BZDB parameter (but isn't). * * maxScrollPages = This number * maxLines is the total maximum * lines of messages (and scrollback). It is stored as a BZDB * parameter. */ glScissor(x + messageAreaPixels[0], y + messageAreaPixels[1], messageAreaPixels[2], messageAreaPixels[3] - (showTabs ? int(lineHeight + 4) : 0) + ay); if (messageMode >= 0) { i = (int)messages[messageMode].size() - 1; } else { i = -1; } if (i >= 0 && messagesOffset > 0) { i -= messagesOffset; if (i < 0) i = 0; } const std::string highlightPattern = BZDB.get("highlightPattern"); bool useHighlight = (highlightPattern.size() > 0); regex_t re; if (useHighlight) { if (regcomp(&re, highlightPattern.c_str(), REG_EXTENDED | REG_ICASE) != 0) { useHighlight = false; // bad regex } } for (j = 0; i >= 0 && j < maxLines; i--) { // draw each line of text int numLines = messages[messageMode][i].numlines; int numStrings = (int)messages[messageMode][i].lines.size(); int msgy = numLines - 1; int msgx = 0; // see if this message need to be highlighted (check each line) bool highlight = false; if (useHighlight) { for (int l = 0; l < numStrings; l++) { const std::string &msg = messages[messageMode][i].lines[l]; std::string raw = stripAnsiCodes(msg); if (regexec(&re, raw.c_str(), 0, NULL, 0) == 0) { highlight = true; } } } // default to drawing text in white GLfloat whiteColor[4] = {1.0f, 1.0f, 1.0f, dimming}; glColor4fv(whiteColor); bool isTab = false; for (int l = 0; l < numStrings; l++) { const std::string &msg = messages[messageMode][i].lines[l]; // Tab chars move horizontally instead of vertically // It doesn't matter where in the string the tab char is // Usually it will be like \ttext // We use 1 tabstop spaced 1/3 of the way across the controlpanel isTab = (msg.find('\t') != std::string::npos); if (isTab) { msgx += messageAreaPixels[2] / 3; msgy++; } else { msgx = 0; } assert(msgy >= 0); // only draw message if inside message area if (j + msgy < maxLines) { if (!highlight) { fm.drawString(fx + msgx, fy + msgy * lineHeight, 0, fontFace, fontSize, msg); } else { // highlight this line std::string newMsg = ANSI_STR_PULSATING; newMsg += ANSI_STR_UNDERLINE; newMsg += ANSI_STR_FG_CYAN; newMsg += stripAnsiCodes(msg); fm.drawString(fx + msgx, fy + msgy * lineHeight, 0, fontFace, fontSize, newMsg); } } // next line msgy--; } j += numLines; fy += int(lineHeight * numLines); } // free the regex memory if (useHighlight) { regfree(&re); } glScissor(x + messageAreaPixels[0] - 2, y + messageAreaPixels[1] - 2, messageAreaPixels[2] + 3, messageAreaPixels[3] + 33); OpenGLGState::resetState(); // draw the lines around the console panel long xpos; long ypos; float outlineOpacity = RENDERER.getPanelOpacity(); float fudgeFactor = BZDBCache::hudGUIBorderOpacityFactor; // bzdb cache this manybe? if ( outlineOpacity < 1.0f ) outlineOpacity = (outlineOpacity*fudgeFactor) + (1.0f - fudgeFactor); if (BZDBCache::blend) glEnable(GL_BLEND); // nice border glColor4f(teamColor[0], teamColor[1], teamColor[2],outlineOpacity ); glBegin(GL_LINE_LOOP); { // bottom left xpos = x + messageAreaPixels[0] - 1; ypos = y + messageAreaPixels[1] - 1; glVertex2f((float) xpos, (float) ypos); // bottom right xpos += messageAreaPixels[2] + 1; glVertex2f((float) xpos, (float) ypos); // top right ypos += messageAreaPixels[3] + 1; glVertex2f((float) xpos, (float) ypos); // over to panel on left if (!tabsOnRight) { xpos = x + messageAreaPixels[0] + totalTabWidth; glVertex2f((float) xpos, (float) ypos); } // across the top from right to left long int drawnTabWidth = 0; for (int tab = (int)tabs->size() - 1; tab >= 0; tab--) { if (messageMode == MessageModes(tab)) { ypos += ay; glVertex2f((float) xpos, (float) ypos); xpos -= long(tabTextWidth[tab]) + 1; glVertex2f((float) xpos, (float) ypos); ypos -= ay; glVertex2f((float) xpos, (float) ypos); } else { xpos -= long(tabTextWidth[tab]); glVertex2f((float) xpos, (float) ypos); } drawnTabWidth += long(tabTextWidth[tab]); } // over from panel on right // if (tabsOnRight) { xpos = x + messageAreaPixels[0] - 1; glVertex2f((float) xpos, (float) ypos); // } } glEnd(); // some engines miss the corners glBegin(GL_POINTS); { glVertex2f((float) (x + messageAreaPixels[0] - 1), (float) (y + messageAreaPixels[1] - 1)); glVertex2f((float) (x + messageAreaPixels[0] + messageAreaPixels[2]), (float) (y + messageAreaPixels[1] - 1)); glVertex2f((float) (x + messageAreaPixels[0] + messageAreaPixels[2]), (float) (y + messageAreaPixels[1] + messageAreaPixels[3])); glVertex2f((float) (x + messageAreaPixels[0] - 1), (float) (y + messageAreaPixels[1] + messageAreaPixels[3])); long int tabPosition = 0; for (int tab = 0; tab < (int)tabs->size(); tab++) { if (messageMode == MessageModes(tab)) { if (tabsOnRight) { glVertex2f((float) (x + messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth + tabPosition), (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay)); glVertex2f((float) (x + messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth + tabPosition + long(tabTextWidth[tab])), (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay)); } else { glVertex2f((float) (x + messageAreaPixels[0] + tabPosition), (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay)); glVertex2f((float) (x + messageAreaPixels[0] + tabPosition + long(tabTextWidth[tab])), (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay)); } } tabPosition += long(tabTextWidth[tab]); } } glEnd(); if (BZDBCache::blend) glDisable(GL_BLEND); glColor4f(teamColor[0], teamColor[1], teamColor[2],1.0f ); glPopMatrix(); fm.setOpacity(1.0f); } void ControlPanel::resize() { float radarSpace, radarSize; // get important metrics const float w = (float)window.getWidth(); const float h = (float)window.getHeight(); const float opacity = RENDERER.getPanelOpacity(); radarSize = float(window.getHeight() - window.getViewHeight()); if (opacity == 1.0f) { radarSize = float(window.getHeight() - window.getViewHeight()); radarSpace = 0.0f; } else { radarSize = h * (14 + RENDERER.getRadarSize()) / 60.0f; radarSpace = 3.0f * w / MinY; } // compute areas in pixels x,y,w,h // leave off 1 pixel for the border radarAreaPixels[0] = radarAreaPixels[1] = (int)radarSpace + 1; radarAreaPixels[2] = radarAreaPixels[3] = (int)(radarSize - (radarSpace * 2.0f)) - 2; messageAreaPixels[0] = (int)radarSize + 1; // X coord messageAreaPixels[1] = radarAreaPixels[1]; // Y coord messageAreaPixels[2] = (int)(w - radarSize - radarSpace) - 2; // Width messageAreaPixels[3] = radarAreaPixels[3]; // Height // if radar connected then resize it if (radarRenderer) radarRenderer->setShape(radarAreaPixels[0], radarAreaPixels[1], radarAreaPixels[2], radarAreaPixels[3]); switch (static_cast(BZDB.eval("cpanelfontsize"))) { case 0: { // auto const bool useBigFont = (messageAreaPixels[2] / 100.0f) > 10.0f; fontSize = useBigFont ? 12.0f : 8.0f; break; } case 1: // tiny fontSize = 6; break; case 2: // small fontSize = 8; break; case 3: // medium fontSize = 12; break; case 4: // big fontSize = 16; break; } FontManager &fm = FontManager::instance(); fontFace = fm.getFaceID(BZDB.get("consoleFont")); // tab widths may have changed if (tabs) { tabTextWidth.clear(); totalTabWidth = 0; const float charWidth = fm.getStrLength(fontFace, fontSize, "-"); for (unsigned int tab = 0; tab < tabs->size(); tab++) { // add space for about 2-chars on each side for padding tabTextWidth.push_back(fm.getStrLength(fontFace, fontSize, (*tabs)[tab]) + (4.0f * charWidth)); totalTabWidth += long(tabTextWidth[tab]); } } lineHeight = fm.getStrHeight(fontFace, fontSize, " "); maxLines = int(messageAreaPixels[3] / lineHeight); margin = lineHeight / 4.0f; // rewrap all the lines for (int i = 0; i < MessageModeCount; i++) { for (int j = 0; j < (int)messages[i].size(); j++) { messages[i][j].breakLines(messageAreaPixels[2] - 2 * margin, fontFace, fontSize); } } // note that we've been resized at least once resized = true; invalidate(); } void ControlPanel::resizeCallback(void* self) { ((ControlPanel*)self)->resize(); } void ControlPanel::setNumberOfFrameBuffers(int n) { numBuffers = n; } void ControlPanel::invalidate() { if (numBuffers) { changedMessage = numBuffers; } else { changedMessage++; } } void ControlPanel::exposeCallback(void* self) { ((ControlPanel*)self)->invalidate(); } void ControlPanel::setMessagesOffset(int offset, int whence, bool paged) { if (paged) { if (abs(offset) <= 1) { offset = offset * (maxLines - 1); } else { offset = offset * maxLines; } } // offset = offset from whence (offset of 0 is the bottom/most recent) // whence = 0, 1, or 2 (akin to SEEK_SET, SEEK_CUR, SEEK_END) switch (whence) { case 0: if (offset < (int)messages[messageMode].size()) messagesOffset = offset; else messagesOffset = (int)messages[messageMode].size() - 1; break; case 1: if (offset > 0) { if (messagesOffset + offset < (int)messages[messageMode].size()) messagesOffset += offset; else messagesOffset = (int)messages[messageMode].size() - 1; } else if (offset < 0) { if (messagesOffset + offset >= 0) messagesOffset += offset; else messagesOffset = 0; } break; case 2: if (offset < 0) { if ((int)messages[messageMode].size() >= offset) messagesOffset += offset; else messagesOffset = 0; } break; } invalidate(); } void ControlPanel::setMessagesMode(int _messageMode) { messageMode = _messageMode; if (messageMode == MessageAll) for (int i = 0; i < MessageModeCount; i++) unRead[i] = false; else if (messageMode >= MessageChat) unRead[messageMode] = false; invalidate(); } void ControlPanel::addMessage(const std::string& line, const int realmode) { ControlPanelMessage item(line); item.breakLines(messageAreaPixels[2] - 2 * margin, fontFace, fontSize); int _maxScrollPages = BZDB.evalInt("scrollPages"); if (_maxScrollPages <= 0) { _maxScrollPages = atoi(BZDB.getDefault("scrollPages").c_str()); BZDB.setInt("scrollPages", _maxScrollPages); } // the effective tab const int tabmode = (realmode == MessageCurrent) ? messageMode : realmode; // add to the appropriate tabs for (int tab = MessageAll; tab < MessageModeCount; tab++) { if ((tab == tabmode) // add to its own mode // add to the All tab, unless using Current mode || ((tab == MessageAll) && (realmode != MessageCurrent)) // always add to all tabs || (realmode == MessageAllTabs)) { // insert the message into the tab if ((int)messages[tab].size() < maxLines * _maxScrollPages) { // not full yet so just append it messages[tab].push_back(item); } else { // rotate list and replace oldest (in newest position after rotate) messages[tab].pop_front(); messages[tab].push_back(item); } // visible changes, force a console refresh if (messageMode == tab) { invalidate(); } // mark the tab as unread (if viewing tabs) const bool showTabs = (BZDB.evalInt("showtabs") > 0); if (showTabs && (messageMode != tab) && (messageMode != MessageAll)) { unRead[tab] = true; } } } if (echoToConsole){ #ifdef _WIN32 // this is cheap but it will work on windows FILE *fp = fopen ("stdout.txt","a+"); if (fp){ fprintf(fp,"%s\n", stripAnsiCodes(line).c_str()); fclose(fp); } #else if (echoAnsi) { std::cout << line << ColorStrings[ResetColor] << std::endl; } else { std::cout << stripAnsiCodes(line) << std::endl; } fflush(stdout); #endif } } void ControlPanel::saveMessages(const std::string& filename, bool stripAnsi) const { FILE* file = fopen(filename.c_str(), "a+"); if (!file) { return; } const time_t nowTime = time (NULL); fprintf(file, "\n----------------------------------------\n"); fprintf(file, "Messages saved: %s", ctime(&nowTime)); fprintf(file, "----------------------------------------\n\n"); // add to the appropriate tabs std::deque::const_iterator msg = messages[MessageAll].begin()++; for (; msg != messages[MessageAll].end(); ++msg) { const std::string& line = msg->string; if (stripAnsi) { fprintf(file, "%s\n", stripAnsiCodes(line).c_str()); } else { fprintf(file, "%s%s\n", line.c_str(), ColorStrings[ResetColor].c_str()); } } fclose(file); return; } void ControlPanel::setRadarRenderer(RadarRenderer* rr) { radarRenderer = rr; } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* interface header */ #include "GuidedMissleStrategy.h" /* common implementation headers */ #include "BZDBCache.h" #include "TextureManager.h" #include "Intersect.h" /* local implementation headers */ #include "LocalPlayer.h" #include "World.h" #include "playing.h" static float limitAngle(float a) { if (a < -M_PI) a += (float)(2.0 * M_PI); else if (a >= M_PI) a -= (float)(2.0 * M_PI); return a; } GuidedMissileStrategy::GuidedMissileStrategy(ShotPath* _path) : ShotStrategy(_path), renderTimes(0), needUpdate(true) { ptSceneNode = new BoltSceneNode(_path->getPosition()); TextureManager &tm = TextureManager::instance(); int texture = tm.getTextureID("missile"); if (texture >= 0) { ptSceneNode->setTexture(texture); ptSceneNode->setTextureAnimation(4, 4); ptSceneNode->setColor(1.0f, 0.2f, 0.0f); ptSceneNode->setFlares(true); } // test // get initial shot info FiringInfo& f = getFiringInfo(_path); f.lifetime *= BZDB.eval(StateDatabase::BZDB_GMADLIFE); const float* vel = getPath().getVelocity(); const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2])); float dir[3]; dir[0] = vel[0] * d; dir[1] = vel[1] * d; dir[2] = vel[2] * d; azimuth = limitAngle(atan2f(dir[1], dir[0])); elevation = limitAngle(atan2f(dir[2], hypotf(dir[1], dir[0]))); // initialize segments currentTime = getPath().getStartTime(); Ray ray = Ray(f.shot.pos, dir); ShotPathSegment segment(currentTime, currentTime, ray); segments.push_back(segment); segments.push_back(segment); segments.push_back(segment); segments.push_back(segment); // setup shot float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED); f.shot.vel[0] = shotSpeed * dir[0]; f.shot.vel[1] = shotSpeed * dir[1]; f.shot.vel[2] = shotSpeed * dir[2]; // set next position to starting position nextPos[0] = f.shot.pos[0]; nextPos[1] = f.shot.pos[1]; nextPos[2] = f.shot.pos[2]; // check that first segment doesn't start inside a building float startPos[3]; float muzzleFront = BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT); startPos[0] = f.shot.pos[0] - muzzleFront * dir[0]; startPos[1] = f.shot.pos[1] - muzzleFront * dir[1]; startPos[2] = f.shot.pos[2] - muzzleFront * dir[2]; Ray firstRay = Ray(startPos, dir); prevTime = currentTime; prevTime += -muzzleFront / BZDB.eval(StateDatabase::BZDB_SHOTSPEED); checkBuildings(firstRay); prevTime = currentTime; // no last target lastTarget = NoPlayer; lastPuff = currentTime; puffTime = BZDB.eval("gmPuffTime"); } GuidedMissileStrategy::~GuidedMissileStrategy() { delete ptSceneNode; } // NOTE -- ray is base of shot segment and normalized direction of flight. // distance traveled is ShotSpeed * dt. void GuidedMissileStrategy::update(float dt) { const bool isRemote = (getPath().getPlayer() != LocalPlayer::getMyTank()->getId()); // ignore packets that arrive out of order if (isRemote && dt < 0.0f) return; // update time prevTime = currentTime; currentTime += dt; // if shot life ran out then send notification and expire shot. // only local shots are expired. if (!isRemote && currentTime - getPath().getStartTime() >= getPath().getLifetime()) { /* NOTE -- comment out to not explode when shot expires */ addShotExplosion(nextPos); setExpiring(); return; } // get target const Player* target = NULL; if (isRemote) { if (lastTarget != NoPlayer) target = lookupPlayer(lastTarget); } else { if (lastTarget == NoPlayer) { setTarget(); LocalPlayer* myTank = LocalPlayer::getMyTank(); if (myTank) target = myTank->getTarget(); } else { target = lookupPlayer(lastTarget); } // see if the target changed if (target) { if (lastTarget != target->getId()) { needUpdate = true; lastTarget = target->getId(); } } else { if (lastTarget != NoPlayer) { needUpdate = true; lastTarget = NoPlayer; } } } if ((target != NULL) && ((target->getFlag() == Flags::Stealth) || ((target->getStatus() & short(PlayerState::Alive)) == 0))) { target = NULL; lastTarget = NoPlayer; needUpdate = true; } // compute next segment's ray if (target) { // turn towards target // find desired direction const float* targetPos = target->getPosition(); float desiredDir[3]; desiredDir[0] = targetPos[0] - nextPos[0]; desiredDir[1] = targetPos[1] - nextPos[1]; desiredDir[2] = targetPos[2] - nextPos[2]; desiredDir[2] += target->getMuzzleHeight(); // right between the eyes // compute desired angles float newAzimuth = atan2f(desiredDir[1], desiredDir[0]); float newElevation = atan2f(desiredDir[2], hypotf(desiredDir[1], desiredDir[0])); float gmissileAng = BZDB.eval (StateDatabase::BZDB_GMTURNANGLE); if (!isRemote) gmissileAng *= 1000; // compute new azimuth float deltaAzimuth = limitAngle(newAzimuth - azimuth); if (fabsf(deltaAzimuth) <= dt * gmissileAng) azimuth = limitAngle(newAzimuth); else if (deltaAzimuth > 0.0f) azimuth = limitAngle(azimuth + dt * gmissileAng); else azimuth = limitAngle(azimuth - dt * gmissileAng); // compute new elevation float deltaElevation = limitAngle(newElevation - elevation); if (fabsf(deltaElevation) <= dt * gmissileAng) elevation = limitAngle(newElevation); else if (deltaElevation > 0.0f) elevation = limitAngle(elevation + dt * gmissileAng); else elevation = limitAngle(elevation - dt * gmissileAng); } float newDirection[3]; newDirection[0] = cosf(azimuth) * cosf(elevation); newDirection[1] = sinf(azimuth) * cosf(elevation); newDirection[2] = sinf(elevation); Ray ray = Ray(nextPos, newDirection); renderTimes++; // Changed: GM smoke trail, leave it every seconds, none of this per frame crap if (currentTime.getSeconds() - lastPuff.getSeconds() > puffTime ) { lastPuff = currentTime; addShotPuff(nextPos,azimuth,elevation); } // get next position float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED); ray.getPoint(dt * shotSpeed, nextPos); // see if we hit something TimeKeeper segmentEndTime(currentTime); /* if (!isRemote) */ { if (nextPos[2] <= 0.0f) { // hit ground -- expire it and shorten life of segment to time of impact setExpiring(); float t = ray.getOrigin()[2] / (ray.getOrigin()[2] - nextPos[2]); segmentEndTime = prevTime; segmentEndTime += t * (currentTime - prevTime); ray.getPoint(t / shotSpeed, nextPos); addShotExplosion(nextPos); } else { // see if we hit a building const float t = checkBuildings(ray); if (t >= 0.0f) { segmentEndTime = prevTime; segmentEndTime += t; } } } // throw out old segment and add new one ShotPathSegment nextSegment(prevTime, segmentEndTime, ray); segments.insert(segments.begin(), nextSegment); segments.pop_back(); // update shot newDirection[0] *= shotSpeed; newDirection[1] *= shotSpeed; newDirection[2] *= shotSpeed; setPosition(nextPos); setVelocity(newDirection); } float GuidedMissileStrategy::checkBuildings(const Ray& ray) { float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED); float t = float((currentTime - prevTime) * shotSpeed); int face; const Obstacle* building = getFirstBuilding(ray, Epsilon, t); const Teleporter* teleporter = getFirstTeleporter(ray, Epsilon, t, face); // check in reverse order to see what we hit first if (teleporter) { // entered teleporter -- teleport it unsigned int seed = getPath().getShotId(); int source = World::getWorld()->getTeleporter(teleporter, face); int target = World::getWorld()->getTeleportTarget(source, seed); int outFace; const Teleporter* outTeleporter = World::getWorld()->getTeleporter(target, outFace); teleporter->getPointWRT(*outTeleporter, face, outFace, nextPos, NULL, azimuth, nextPos, NULL, &azimuth); return t / shotSpeed; } else if (building) { // expire on next update setExpiring(); float pos[3]; ray.getPoint(t / shotSpeed, pos); addShotExplosion(pos); return t / shotSpeed; } return -1.0f; } float GuidedMissileStrategy::checkHit(const BaseLocalPlayer* tank, float position[3]) const { float minTime = Infinity; if (getPath().isExpired()) return minTime; // GM is not active until activation time passes (for any tank) const float activationTime = BZDB.eval(StateDatabase::BZDB_GMACTIVATIONTIME); if ((TimeKeeper::getTick() - getPath().getStartTime()) < activationTime) { return minTime; } // get tank radius float radius = tank->getRadius(); const float radius2 = radius * radius; float shotRadius = BZDB.eval(StateDatabase::BZDB_SHOTRADIUS); // tank is positioned from it's bottom so shift position up by // half a tank height. const float tankHeight = tank->getDimensions()[2]; Ray tankLastMotionRaw = tank->getLastMotion(); float lastTankPositionRaw[3]; lastTankPositionRaw[0] = tankLastMotionRaw.getOrigin()[0]; lastTankPositionRaw[1] = tankLastMotionRaw.getOrigin()[1]; lastTankPositionRaw[2] = tankLastMotionRaw.getOrigin()[2] + 0.5f * tankHeight; Ray tankLastMotion(lastTankPositionRaw, tankLastMotionRaw.getDirection()); // check each segment const int numSegments = segments.size(); int i = 0; // only test most recent segment if shot is from my tank if (numSegments > 1 && tank->getId() == getPath().getPlayer()) i = numSegments - 1; for (; i < numSegments; i++) { const Ray& ray = segments[i].ray; // construct ray with correct velocity float speed[3]; const float* dir = ray.getDirection(); float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED); speed[0] = shotSpeed * dir[0]; speed[1] = shotSpeed * dir[1]; speed[2] = shotSpeed * dir[2]; Ray speedRay(ray.getOrigin(), speed); // construct relative shot ray: origin and velocity relative to // my tank as a function of time (t=0 is start of the interval). Ray relativeRay(rayMinusRay(speedRay, 0.0, tankLastMotion, 0.0)); // get closest approach time float t; if (tank->getFlag() == Flags::Narrow) { // find closest approach to narrow box around tank. width of box // is shell radius so you can actually hit narrow tank head on. static float tankBase[3] = { 0.0f, 0.0f, -0.5f * tankHeight }; t = timeRayHitsBlock(relativeRay, tankBase, tank->getAngle(), 0.5f * BZDBCache::tankLength, shotRadius, tankHeight); } else { // find time when shot hits sphere around tank t = rayAtDistanceFromOrigin(relativeRay, 0.99f * radius); } if (t > minTime) continue; // if not in shot segment times then no hit if (t < 0.0f || t > segments[i].end - segments[i].start) continue; // check if shot hits tank -- get position at time t, see if in radius float closestPos[3]; relativeRay.getPoint(t, closestPos); if (closestPos[0] * closestPos[0] + closestPos[1] * closestPos[1] + closestPos[2] * closestPos[2] < radius2) { // save best time so far minTime = t; // compute location of tank at time of hit float tankPos[3]; tank->getLastMotion().getPoint(t, tankPos); // compute position of intersection position[0] = tankPos[0] + closestPos[0]; position[1] = tankPos[1] + closestPos[1]; position[2] = tankPos[2] + closestPos[2]; } } return minTime; } void GuidedMissileStrategy::sendUpdate(const FiringInfo& firingInfo) const {y ((GuidedMissileStrategy*)this)->needUpdate = false; // construct and send packet char packet[ShotUpdatePLen + PlayerIdPLen]; void *buf = (void*)packet; buf = firingInfo.shot.pack(buf); buf = nboPackUByte(buf, lastTarget); ServerLink::getServer()->send(MsgGMUpdate, sizeof(packet), packet); } void GuidedMissileStrategy::readUpdate(uint16_t code, void* msg) { // ignore non-guided missile messages (we shouldn't get them) if (code != MsgGMUpdate) return; // position and velocity have been replaced by the remote system's // concept of the position and velocity. this may cause a discontinuity // in the shot's position but it's probably better to have the shot in // the right place than to maintain smooth motion. these updates ought // to be rare anyway. // read the lastTarget nboUnpackUByte(msg, lastTarget); // fix up dependent variables const float* vel = getPath().getVelocity(); const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2])); float dir[3]; dir[0] = vel[0] * d; dir[1] = vel[1] * d; dir[2] = vel[2] * d; azimuth = limitAngle(atan2f(dir[1], dir[0])); elevation = limitAngle(atan2f(dir[2], hypotf(dir[1], dir[0]))); const float* pos = getPath().getPosition(); nextPos[0] = pos[0]; nextPos[1] = pos[1]; nextPos[2] = pos[2]; // note that we do not call update(float). let that happen on the // next time step. } void GuidedMissileStrategy::addShot(SceneDatabase* scene, bool) { ptSceneNode->move(getPath().getPosition(), getPath().getVelocity()); scene->addDynamicNode(ptSceneNode); } void GuidedMissileStrategy::expire() { if (getPath().getPlayer() == LocalPlayer::getMyTank()->getId()) { const ShotPath& shot = getPath(); /* NOTE -- change 0 to 1 to not explode when shot expires (I think) */ ServerLink::getServer()->sendEndShot(shot.getPlayer(), shot.getShotId(), 0); } } void GuidedMissileStrategy::radarRender() const { const float *orig = getPath().getPosition(); const int length = (int)BZDBCache::linedRadarShots; const int size = (int)BZDBCache::sizedRadarShots; float shotTailLength = BZDB.eval(StateDatabase::BZDB_SHOTTAILLENGTH); // Display leading lines if (length > 0) { const float* vel = getPath().getVelocity(); const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2])); float dir[3]; dir[0] = vel[0] * d * shotTailLength * length; dir[1] = vel[1] * d * shotTailLength * length; dir[2] = vel[2] * d * shotTailLength * length; glBegin(GL_LINES); glVertex2fv(orig); if (BZDBCache::leadingShotLine) { glVertex2f(orig[0] + dir[0], orig[1] + dir[1]); } else { glVertex2f(orig[0] - dir[0], orig[1] - dir[1]); } glEnd(); // draw a "bright reddish" missle tip if (size > 0) { glColor3f(1.0f, 0.75f, 0.75f); glPointSize((float)size); glBegin(GL_POINTS); glVertex2f(orig[0], orig[1]); glEnd(); glPointSize(1.0f); } } else { if (size > 0) { // draw a sized missle glPointSize((float)size); glBegin(GL_POINTS); glVertex2fv(orig); glEnd(); glPointSize(1.0f); } else { // draw the tiny missle glBegin(GL_POINTS); glVertex2fv(orig); glEnd(); } } } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* interface header */ #include "LocalPlayer.h" /* common implementation headers */ #include "CommandManager.h" #include "BZDBCache.h" #include "FlagSceneNode.h" #include "CollisionManager.h" #include "PhysicsDriver.h" #include "BzfEvent.h" #include "WallObstacle.h" #include "MeshObstacle.h" /* local implementation headers */ #include "World.h" #include "sound.h" #include "ForceFeedback.h" #include "effectsRenderer.h" #include "TargetingUtils.h" extern bool bCheatOO; extern bool bCheatGravity; LocalPlayer* LocalPlayer::mainPlayer = NULL; LocalPlayer::LocalPlayer(const PlayerId& _id, const char* name, const char* _email) : BaseLocalPlayer(_id, name, _email), gettingSound(true), server(ServerLink::getServer()), location(Dead), firingStatus(Deceased), flagShakingTime(0.0f), flagShakingWins(0), antidoteFlag(NULL), desiredSpeed(0.0f), desiredAngVel(0.0f), lastSpeed(0.0f), anyShotActive(false), target(NULL), nemesis(NULL), recipient(NULL), inputChanged(false), stuckFrameCount(0), spawning(false), wingsFlapCount(0), handicap(0.0f), left(false), right(false), up(false), down(false), entryDrop(true), wantJump(false), jumpPressed(false), deathPhyDrv(-1) { // initialize shots array to no shots fired const int numShots = World::getWorld()->getMaxShots(); shots = new LocalShotPath*[numShots]; for (int i = 0; i < numShots; i++) shots[i] = NULL; // set input method if (BZDB.isTrue("allowInputChange")) { inputMethod = Mouse; } else { setInputMethod(BZDB.get("activeInputDevice")); } } LocalPlayer::~LocalPlayer() { // free shots const int numShots = World::getWorld()->getMaxShots(); for (int i = 0; i < numShots; i++) if (shots[i]) delete shots[i]; delete[] shots; // free antidote flag delete antidoteFlag; } void LocalPlayer::setMyTank(LocalPlayer* player) { mainPlayer = player; } void LocalPlayer::doUpdate(float dt) { const bool hadShotActive = anyShotActive; const int numShots = World::getWorld()->getMaxShots(); static TimeKeeper pauseTime = TimeKeeper::getNullTime(); static bool wasPaused = false; // if paused then boost the reload times by dt (so that, effectively, // reloading isn't performed) int i; if (0 && isPaused()) { for (i = 0; i < numShots; i++) { if (shots[i]) { shots[i]->boostReloadTime(dt); } } // if we've been paused for a long time, drop our flag if (!wasPaused) { pauseTime = TimeKeeper::getCurrent(); wasPaused = true; } if (TimeKeeper::getCurrent() - pauseTime > BZDB.eval(StateDatabase::BZDB_PAUSEDROPTIME)) { server->sendDropFlag(getPosition()); setStatus(getStatus() & ~PlayerState::FlagActive); pauseTime = TimeKeeper::getSunExplodeTime(); } } else { pauseTime = TimeKeeper::getNullTime(); wasPaused = false; } // reap dead (reloaded) shots for (i = 0; i < numShots; i++) if (shots[i] && shots[i]->isReloaded()) { if (!shots[i]->isExpired()) shots[i]->setExpired(); delete shots[i]; shots[i] = NULL; } // update shots anyShotActive = false; for (i = 0; i < numShots; i++) if (shots[i]) { shots[i]->update(dt); if (!shots[i]->isExpired()) anyShotActive = true; } // if no shots now out (but there had been) then reset target if (!anyShotActive && hadShotActive) target = NULL; // drop bad flag if timeout has expired if (!isPaused() && dt > 0.0f && World::getWorld()->allowShakeTimeout() && getFlag() != Flags::Null && getFlag()->endurance == FlagSticky && flagShakingTime > 0.0f) { flagShakingTime -= dt; if (flagShakingTime <= 0.0f) { flagShakingTime = 0.0f; server->sendDropFlag(getPosition()); } } } void LocalPlayer::doSlideMotion(float dt, float slideTime, float newAngVel, float* newVelocity) { const float oldAzimuth = getAngle(); const float* oldVelocity = getVelocity(); const float angle = oldAzimuth + (0.5f * dt * newAngVel); const float cos_val = cosf(angle); const float sin_val = sinf(angle); const float scale = (dt / slideTime); const float speedAdj = desiredSpeed * scale; const float* ov = oldVelocity; const float oldSpeed = sqrtf((ov[0] * ov[0]) + (ov[1] * ov[1])); float* nv = newVelocity; nv[0] = ov[0] + (cos_val * speedAdj); nv[1] = ov[1] + (sin_val * speedAdj); const float newSpeed = sqrtf((nv[0] * nv[0]) + (nv[1] * nv[1])); float maxSpeed = getMaxSpeed(); if (newSpeed > maxSpeed) { float adjSpeed; if (oldSpeed > maxSpeed) { adjSpeed = oldSpeed - (dt * (maxSpeed / slideTime)); if (adjSpeed < 0.0f) { adjSpeed = 0.0f; } } else { adjSpeed = maxSpeed; } const float speedScale = adjSpeed / newSpeed; nv[0] *= speedScale; nv[1] *= speedScale; } return; } float LocalPlayer::getNewAngVel(float old, float desired) { float newAngVel; if ((inputMethod != Keyboard) || (getPhysicsDriver() >= 0)) { // mouse and joystick users newAngVel = desired; } else { /* keybaord users * the larger the oldAngVel contribution, the more slowly an * angular velocity converges to the desired "max" velocity; the * contribution of the desired and old velocity should add up to * one for a linear convergence rate. */ newAngVel = (old * 0.8f) + (desired * 0.2f); // instant stop if ((old * desired < 0.0f) || (NEAR_ZERO(desired, ZERO_TOLERANCE))) { newAngVel = desired; } } return newAngVel; } void LocalPlayer::doUpdateMotion(float dt) { static const float MinSearchStep = 0.0001f; static const int MaxSearchSteps = 7; static const int MaxSteps = 4; static const float TinyDistance = 0.001f; // save old state const Location oldLocation = location; const float* oldPosition = getPosition(); const float oldAzimuth = getAngle(); const float oldAngVel = getAngularVelocity(); const float* oldVelocity = getVelocity(); // prepare new state float newVelocity[3]; newVelocity[0] = oldVelocity[0]; newVelocity[1] = oldVelocity[1]; newVelocity[2] = oldVelocity[2]; float newAngVel = 0.0f; clearRemoteSounds(); // if was teleporting and exceeded teleport time then not teleporting anymore if (isTeleporting() && ((lastTime - getTeleportTime()) >= BZDB.eval(StateDatabase::BZDB_TELEPORTTIME))) setStatus(getStatus() & ~short(PlayerState::Teleporting)); // phased means we can pass through buildings const bool phased = ((location == Dead) || (location == Exploding) || (getFlag() == Flags::OscillationOverthruster) || isPhantomZoned()|| bCheatOO); float groundLimit = 0.0f; if (getFlag() == Flags::Burrow) groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH); // get linear and angular speed at start of time step if (!NEAR_ZERO(dt,ZERO_TOLERANCE)) { if (location == Dead || isPaused()) { // can't move if paused or dead -- set dt to zero instead of // clearing velocity and newAngVel for when we resume (if paused) dt = 0.0f; newAngVel = oldAngVel; } else if (location == Exploding) { // see if explosing time has expired if (lastTime - getExplodeTime() >= BZDB.eval(StateDatabase::BZDB_EXPLODETIME)) { dt -= float((lastTime - getExplodeTime()) - BZDB.eval(StateDatabase::BZDB_EXPLODETIME)); if (dt < 0.0f) { dt = 0.0f; } setStatus(PlayerState::DeadStatus); location = Dead; if (isAutoPilot()) { CMDMGR.run("restart"); } } // can't control explosion motion newVelocity[2] += BZDBCache::gravity * dt; newAngVel = 0.0f; // or oldAngVel to spin while exploding } else if ((location == OnGround) || (location == OnBuilding) || (location == InBuilding && oldPosition[2] == groundLimit)) { // full control float speed = desiredSpeed; // angular velocity newAngVel = getNewAngVel(oldAngVel, desiredAngVel); // limit acceleration doMomentum(dt, speed, newAngVel); // compute velocity so far const float angle = oldAzimuth + 0.5f * dt * newAngVel; newVelocity[0] = speed * cosf(angle); newVelocity[1] = speed * sinf(angle); newVelocity[2] = 0.0f; // now friction, if any doFriction(dt, oldVelocity, newVelocity); // reset our flap count if we have wings if (getFlag() == Flags::Wings) wingsFlapCount = (int) BZDB.eval(StateDatabase::BZDB_WINGSJUMPCOUNT); if ((oldPosition[2] < 0.0f) && (getFlag() == Flags::Burrow)) newVelocity[2] += 4 * BZDBCache::gravity * dt; else if (oldPosition[2] > groundLimit) newVelocity[2] += BZDBCache::gravity * dt; // save speed for next update lastSpeed = speed; } else { // can't control motion in air unless have wings if (getFlag() == Flags::Wings || !BZDB.isTrue("displayRadar")) { float speed = desiredSpeed; // angular velocity newAngVel = getNewAngVel(oldAngVel, desiredAngVel); // compute horizontal velocity so far const float slideTime = BZDB.eval(StateDatabase::BZDB_WINGSSLIDETIME); if (slideTime > 0.0) { doSlideMotion(dt, slideTime, newAngVel, newVelocity); } else { const float angle = oldAzimuth + 0.5f * dt * newAngVel; newVelocity[0] = speed * cosf(angle); newVelocity[1] = speed * sinf(angle); } if(bCheatGravity && oldVelocity[2] <= 0.0)newVelocity[2] = 0; else newVelocity[2] += BZDB.eval(StateDatabase::BZDB_WINGSGRAVITY) * dt; lastSpeed = speed; } else { //newVelocity[2] += BZDBCache::gravity * dt; if(bCheatGravity && oldVelocity[2] <= 0.0 )newVelocity[2] = 0; else newVelocity[2] += BZDBCache::gravity * dt; newAngVel = oldAngVel; } } // now apply outside forces doForces(dt, newVelocity, newAngVel); // below the ground: however I got there, creep up if (oldPosition[2] < groundLimit) { newVelocity[2] = std::max(newVelocity[2], -oldPosition[2] / 2.0f + 0.5f); } } // jump here, we allow a little change in horizontal motion if (wantJump) { doJump(); if (!wantJump) { newVelocity[2] = oldVelocity[2]; if ((lastObstacle != NULL) && !lastObstacle->isFlatTop() && BZDB.isTrue(StateDatabase::BZDB_NOCLIMB)) { newVelocity[0] = 0.0f; newVelocity[1] = 0.0f; } } } // do the physics driver stuff const int driverId = getPhysicsDriver(); const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driverId); if (phydrv != NULL) { const float* v = phydrv->getLinearVel(); newVelocity[2] += v[2]; if (phydrv->getIsSlide()) { const float slideTime = phydrv->getSlideTime(); doSlideMotion(dt, slideTime, newAngVel, newVelocity); } else { // adjust the horizontal velocity newVelocity[0] += v[0]; newVelocity[1] += v[1]; const float av = phydrv->getAngularVel(); const float* ap = phydrv->getAngularPos(); if (av != 0.0f) { // the angular velocity is in radians/sec newAngVel += av; const float dx = oldPosition[0] - ap[0]; const float dy = oldPosition[1] - ap[1]; newVelocity[0] -= av * dy; newVelocity[1] += av * dx; } } } lastObstacle = NULL; // get new position so far (which is just the current position) float newPos[3]; newPos[0] = oldPosition[0]; newPos[1] = oldPosition[1]; newPos[2] = oldPosition[2]; float newAzimuth = oldAzimuth; // move tank through the time step. if there's a collision then // move the tank up to the collision, adjust the velocity to // prevent interpenetration, and repeat. avoid infinite loops // by only allowing a maximum number of repeats. bool expelled; const Obstacle* obstacle; float timeStep = dt; int stuck = false; if (location != Dead && location != Exploding) { location = OnGround; // anti-stuck code is useful only when alive // then only any 100 frames while stuck, take an action // try to see if we are stuck on a building obstacle = getHitBuilding(newPos, newAzimuth, newPos, newAzimuth, phased, expelled); if (obstacle && expelled) { stuckFrameCount++; stuck = true; } else { stuckFrameCount = 0; } if (stuckFrameCount > 100) { stuckFrameCount = 0; // we are using a maximum value on time for frame to avoid lagging problem setDesiredSpeed(0.25f); float delta = dt > 0.1f ? 0.1f : dt; float normalStuck[3]; obstacle->getNormal(newPos, normalStuck); // use all the given speed to exit float movementMax = desiredSpeed * delta; newVelocity[0] = movementMax * normalStuck[0]; newVelocity[1] = movementMax * normalStuck[1]; if ((World::getWorld()->allowJumping() || (getFlag() == Flags::Jumping) || (getFlag() == Flags::Wings)) && (getFlag() != Flags::NoJumping)) newVelocity[2] = movementMax * normalStuck[2]; else newVelocity[2] = 0.0f; // exit will be in the normal direction newPos[0] += newVelocity[0]; newPos[1] += newVelocity[1]; newPos[2] += newVelocity[2]; // compute time for all other kind of movements timeStep -= delta; } } for (int numSteps = 0; numSteps < MaxSteps; numSteps++) { // record position at beginning of time step float tmpPos[3], tmpAzimuth; tmpAzimuth = newAzimuth; tmpPos[0] = newPos[0]; tmpPos[1] = newPos[1]; tmpPos[2] = newPos[2]; // get position at end of time step newAzimuth = tmpAzimuth + timeStep * newAngVel; newPos[0] = tmpPos[0] + timeStep * newVelocity[0]; newPos[1] = tmpPos[1] + timeStep * newVelocity[1]; newPos[2] = tmpPos[2] + timeStep * newVelocity[2]; if ((newPos[2] < groundLimit) && (newVelocity[2] < 0)) { // Hit lower limit, stop falling newPos[2] = groundLimit; if (location == Exploding) { // tank pieces reach the ground, friction // stop them, & mainly player view newPos[0] = tmpPos[0]; newPos[1] = tmpPos[1]; } } // see if we hit anything. if not then we're done. obstacle = getHitBuilding(tmpPos, tmpAzimuth, newPos, newAzimuth, phased, expelled); if (!obstacle || !expelled) break; float obstacleTop = obstacle->getPosition()[2] + obstacle->getHeight(); if ((oldLocation != InAir) && obstacle->isFlatTop() && (obstacleTop != tmpPos[2]) && (obstacleTop < (tmpPos[2] + BZDB.eval(StateDatabase::BZDB_MAXBUMPHEIGHT)))) { newPos[0] = oldPosition[0]; newPos[1] = oldPosition[1]; newPos[2] = obstacleTop; // drive over bumps const Obstacle* bumpObstacle = getHitBuilding(newPos, tmpAzimuth, newPos, newAzimuth, phased, expelled); if (bumpObstacle == NULL) { move(newPos, getAngle()); newPos[0] += newVelocity[0] * (dt * 0.5f); newPos[1] += newVelocity[1] * (dt * 0.5f); break; } } // record position when hitting float hitPos[3], hitAzimuth; hitAzimuth = newAzimuth; hitPos[0] = newPos[0]; hitPos[1] = newPos[1]; hitPos[2] = newPos[2]; // find the latest time before the collision float searchTime = 0.0f, searchStep = 0.5f * timeStep; for (int i = 0; searchStep > MinSearchStep && i < MaxSearchSteps; searchStep *= 0.5f, i++) { // get intermediate position const float t = searchTime + searchStep; newAzimuth = tmpAzimuth + (t * newAngVel); newPos[0] = tmpPos[0] + (t * newVelocity[0]); newPos[1] = tmpPos[1] + (t * newVelocity[1]); newPos[2] = tmpPos[2] + (t * newVelocity[2]); if ((newPos[2] < groundLimit) && (newVelocity[2] < 0)) { newPos[2] = groundLimit; } // see if we hit anything bool searchExpelled; const Obstacle* searchObstacle = getHitBuilding(tmpPos, tmpAzimuth, newPos, newAzimuth, phased, searchExpelled); if (!searchObstacle || !searchExpelled) { // if no hit then search latter half of time step searchTime = t; } else if (searchObstacle) { // if we hit a building then record which one and where obstacle = searchObstacle; expelled = searchExpelled; hitAzimuth = newAzimuth; hitPos[0] = newPos[0]; hitPos[1] = newPos[1]; hitPos[2] = newPos[2]; } } // get position just before impact newAzimuth = tmpAzimuth + (searchTime * newAngVel); newPos[0] = tmpPos[0] + (searchTime * newVelocity[0]); newPos[1] = tmpPos[1] + (searchTime * newVelocity[1]); newPos[2] = tmpPos[2] + (searchTime * newVelocity[2]); if (oldPosition[2] < groundLimit) { newVelocity[2] = std::max(newVelocity[2], -oldPosition[2] / 2.0f + 0.5f); } // record how much time is left in time step timeStep -= searchTime; // get normal at intersection. sometimes fancy test says there's // no intersection but we're expecting one so, in that case, fall // back to simple normal calculation. float normal[3]; if (!getHitNormal(obstacle, newPos, newAzimuth, hitPos, hitAzimuth, normal)) { obstacle->getNormal(newPos, normal); } // check for being on a building if ((newPos[2] > 0.0f) && (normal[2] > 0.001f)) { if (location != Dead && location != Exploding && expelled) { location = OnBuilding; lastObstacle = obstacle; } newVelocity[2] = 0.0f; } else { // get component of velocity in normal direction (in horizontal plane) float mag = (normal[0] * newVelocity[0]) + (normal[1] * newVelocity[1]); // handle upward normal component to prevent an upward force if (!NEAR_ZERO(normal[2], ZERO_TOLERANCE)) { // if going down then stop falling if (newVelocity[2] < 0.0f && newVelocity[2] - (mag + normal[2] * newVelocity[2]) * normal[2] > 0.0f) { newVelocity[2] = 0.0f; } // normalize force magnitude in horizontal plane float horNormal = normal[0] * normal[0] + normal[1] * normal[1]; if (!NEAR_ZERO(horNormal, ZERO_TOLERANCE)) { mag /= horNormal; } } // cancel out component in normal direction (if velocity and // normal point in opposite directions). also back off a tiny // amount to prevent a spurious collision against the same // obstacle. if (mag < 0.0f) { newVelocity[0] -= mag * normal[0]; newVelocity[1] -= mag * normal[1]; newPos[0] -= TinyDistance * mag * normal[0]; newPos[1] -= TinyDistance * mag * normal[1]; } if (mag > -0.01f) { // assume we're not allowed to turn anymore if there's no // significant velocity component to cancel out. newAngVel = 0.0f; } } } // pick new location if we haven't already done so if (location == OnGround) { if (obstacle && (!expelled || stuck)) { location = InBuilding; } else if (newPos[2] > 0.0f) { location = InAir; } } // see if we're crossing a wall if (location == InBuilding && (getFlag() == Flags::OscillationOverthruster || bCheatOO)) { if (obstacle->isCrossing(newPos, newAzimuth, 0.5f * BZDBCache::tankLength, 0.5f * BZDBCache::tankWidth, BZDBCache::tankHeight, NULL)) { setStatus(getStatus() | int(PlayerState::CrossingWall)); } else { setStatus(getStatus() & ~int(PlayerState::CrossingWall)); } } else if (World::getWorld()->crossingTeleporter( newPos, newAzimuth, 0.5f * BZDBCache::tankLength, 0.5f * BZDBCache::tankWidth, BZDBCache::tankHeight, crossingPlane)) { setStatus(getStatus() | int(PlayerState::CrossingWall)); } else { setStatus(getStatus() & ~int(PlayerState::CrossingWall)); } // compute actual velocities. do this before teleportation. if (!NEAR_ZERO(dt, ZERO_TOLERANCE)) { const float oodt = 1.0f / dt; newAngVel = (newAzimuth - oldAzimuth) * oodt; newVelocity[0] = (newPos[0] - oldPosition[0]) * oodt; newVelocity[1] = (newPos[1] - oldPosition[1]) * oodt; newVelocity[2] = (newPos[2] - oldPosition[2]) * oodt; } // see if we teleported int face; const Teleporter* teleporter; if (!isAlive()) { teleporter = NULL; } else { teleporter = World::getWorld()->crossesTeleporter(oldPosition, newPos, face); } if (teleporter) { if (getFlag() == Flags::PhantomZone) { // change zoned state setStatus(getStatus() ^ PlayerState::FlagActive); if (gettingSound) { playLocalSound(SFX_PHANTOM); } } else { // teleport const int source = World::getWorld()->getTeleporter(teleporter, face); int targetTele = World::getWorld()->getTeleportTarget(source); int outFace; const Teleporter* outPort = World::getWorld()->getTeleporter(targetTele, outFace); teleporter->getPointWRT(*outPort, face, outFace, newPos, newVelocity, newAzimuth, newPos, newVelocity, &newAzimuth); // check for a hit on the other side const Obstacle* teleObs = getHitBuilding(newPos, newAzimuth, newPos, newAzimuth, phased, expelled); if (teleObs != NULL) { // revert memcpy (newPos, oldPosition, sizeof(float[3])); newVelocity[0] = newVelocity[1] = 0.0f; newVelocity[2] = oldVelocity[2]; newAzimuth = oldAzimuth; } else { // save teleport info setTeleport(lastTime, source, targetTele); server->sendTeleport(source, targetTele); if (gettingSound) { playLocalSound(SFX_TELEPORT); } } } } // setup the physics driver setPhysicsDriver(-1); if ((lastObstacle != NULL) && (lastObstacle->getType() == MeshFace::getClassName())) { const MeshFace* meshFace = (const MeshFace*) lastObstacle; int driverIdent = meshFace->getPhysicsDriver(); const PhysicsDriver* phydriver = PHYDRVMGR.getDriver(driverIdent); if (phydriver != NULL) { setPhysicsDriver(driverIdent); } } // deal with drop sounds and effects if (entryDrop) { // because the starting position that the server sends can result // in an initial InAir condition, we use this bool to avoid having // a false jump mess with the spawnEffect() // FIXME: this isn't a clean way to do it if ((oldLocation == InAir) == (location == InAir)) { entryDrop = false; } } const bool justLanded = (oldLocation == InAir) && ((location == OnGround) || (location == OnBuilding)); if (justLanded) { setLandingSpeed(oldVelocity[2]); EFFECTS.addLandEffect(getColor(),newPos,getAngle()); } if (gettingSound) { const PhysicsDriver* phydriver = PHYDRVMGR.getDriver(getPhysicsDriver()); if ((phydriver != NULL) && (phydriver->getLinearVel()[2] > 0.0f)) { playLocalSound(SFX_BOUNCE); addRemoteSound(PlayerState::BounceSound); } else if (justLanded && !entryDrop) { playLocalSound(SFX_LAND); } else if ((location == OnGround) && (oldPosition[2] == 0.0f) && (newPos[2] < 0.f)) { playLocalSound(SFX_BURROW); } } // set falling status if (location == OnGround || location == OnBuilding || (location == InBuilding && newPos[2] == 0.0f)) { setStatus(getStatus() & ~short(PlayerState::Falling)); } else if (location == InAir || location == InBuilding) { setStatus(getStatus() | short(PlayerState::Falling)); } // set UserInput status (determines how animated treads are drawn) const PhysicsDriver* phydrv2 = PHYDRVMGR.getDriver(getPhysicsDriver()); if (((phydrv2 != NULL) && phydrv2->getIsSlide()) || ((getFlag() == Flags::Wings) && (location == InAir) && !BZDB.isTrue("displayRadar") && (BZDB.eval(StateDatabase::BZDB_WINGSSLIDETIME) > 0.0f))){ setStatus(getStatus() | short(PlayerState::UserInputs)); } else { setStatus(getStatus() & ~short(PlayerState::UserInputs)); } // compute firing status switch (location) { case Dead: case Exploding: firingStatus = Deceased; break; case InBuilding: firingStatus = (getFlag() == Flags::PhantomZone) ? Zoned : Sealed; break; default: if (isPhantomZoned()) firingStatus = Zoned; else if ((getReloadTime() > 0.0f) && (getFlag() != Flags::TriggerHappy)) firingStatus = Loading; else firingStatus = Ready; break; } // burrowed and oscillating tanks get some resistance in their joystick // if they have ff on if ((location == InBuilding) || (newPos[2] < -0.5f)) ForceFeedback::solidMatterFriction(); // calculate the list of inside buildings insideBuildings.clear(); if (location == InBuilding) { collectInsideBuildings(); } // move tank move(newPos, newAzimuth); setVelocity(newVelocity); setAngularVelocity(newAngVel); setRelativeMotion(); newAzimuth = getAngle(); // pickup the limited angle range from move() // see if I'm over my antidote if (antidoteFlag && location == OnGround) { float dist = ((flagAntidotePos[0] - newPos[0]) * (flagAntidotePos[0] - newPos[0])) + ((flagAntidotePos[1] - newPos[1]) * (flagAntidotePos[1] - newPos[1])); const float twoRads = getRadius() + BZDBCache::flagRadius; if (dist < (twoRads * twoRads)) { server->sendDropFlag(getPosition()); } } if ((getFlag() == Flags::Bouncy) && ((location == OnGround) || (location == OnBuilding))) { if (oldLocation != InAir) { if ((TimeKeeper::getCurrent() - bounceTime) > 0) { doJump(); } } else { bounceTime = TimeKeeper::getCurrent(); bounceTime += 0.2f; } } if (gettingSound) { if (oldPosition[0] != newPos[0] || oldPosition[1] != newPos[1] || oldPosition[2] != newPos[2] || oldAzimuth != newAzimuth) { moveSoundReceiver(newPos[0], newPos[1], newPos[2], newAzimuth, NEAR_ZERO(dt, ZERO_TOLERANCE) || ((teleporter != NULL) && (getFlag() != Flags::PhantomZone))); } if (NEAR_ZERO(dt, ZERO_TOLERANCE)) { speedSoundReceiver(newVelocity[0], newVelocity[1], newVelocity[2]); } else { speedSoundReceiver((newPos[0] - oldPosition[0]) / dt, (newPos[1] - oldPosition[1]) / dt, (newPos[2] - oldPosition[2]) / dt); } } } const Obstacle* LocalPlayer::getHitBuilding(const float* p, float a, bool phased, bool& expelled) const { const float* dims = getDimensions(); const Obstacle* obstacle = World::getWorld()->hitBuilding(p, a, dims[0], dims[1], dims[2]); expelled = (obstacle != NULL); if (expelled && phased) expelled = (obstacle->getType() == WallObstacle::getClassName() || obstacle->getType() == Teleporter::getClassName() || (getFlag() == Flags::OscillationOverthruster && !bCheatOO && desiredSpeed < 0.0f && p[2] == 0.0f)); return obstacle; } const Obstacle* LocalPlayer::getHitBuilding(const float* oldP, float oldA, const float* p, float a, bool phased, bool& expelled) { const bool hasOOflag = getFlag() == Flags::OscillationOverthruster; const float* dims = getDimensions(); const Obstacle* obstacle = World::getWorld()-> hitBuilding(oldP, oldA, p, a, dims[0], dims[1], dims[2], !hasOOflag); expelled = (obstacle != NULL); if (expelled && phased) expelled = ( (!bCheatOO && obstacle->getType() == Teleporter::getClassName()) || (hasOOflag && desiredSpeed < 0.0f && !bCheatOO && p[2] == 0.0f)); if (obstacle != NULL) { if (obstacle->getType() == MeshFace::getClassName()) { const MeshFace* face = (const MeshFace*) obstacle; const int driver = face->getPhysicsDriver(); const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver); if ((phydrv != NULL) && phydrv->getIsDeath()) { deathPhyDrv = driver; } } } return obstacle; } bool LocalPlayer::getHitNormal(const Obstacle* o, const float* pos1, float azimuth1, const float* pos2, float azimuth2, float* normal) const { const float* dims = getDimensions(); return o->getHitNormal(pos1, azimuth1, pos2, azimuth2, dims[0], dims[1], dims[2], normal); } static bool notInObstacleList(const Obstacle* obs, const std::vector& list) { for (unsigned int i = 0; i < list.size(); i++) { if (obs == list[i]) { return false; } } return true; } void LocalPlayer::collectInsideBuildings() { const float* pos = getPosition(); const float angle = getAngle(); const float* dims = getDimensions(); // get the list of possible inside buildings const ObsList* olist = COLLISIONMGR.boxTest (pos, angle, dims[0], dims[1], dims[2]); for (int i = 0; i < olist->count; i++) { const Obstacle* obs = olist->list[i]; if (obs->inBox(pos, angle, dims[0], dims[1], dims[2])) { if (obs->getType() == MeshFace::getClassName()) { const MeshFace* face = (const MeshFace*) obs; const MeshObstacle* mesh = (const MeshObstacle*) face->getMesh(); // check it for the death touch if (deathPhyDrv < 0) { const int driver = face->getPhysicsDriver(); const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver); if ((phydrv != NULL) && (phydrv->getIsDeath())) { deathPhyDrv = driver; } } // add the mesh if not already present if (!obs->isDriveThrough() && notInObstacleList(mesh, insideBuildings)) { insideBuildings.push_back(mesh); } } else if (!obs->isDriveThrough()) { if (obs->getType() == MeshObstacle::getClassName()) { // add the mesh if not already present if (notInObstacleList(obs, insideBuildings)) { insideBuildings.push_back(obs); } } else { insideBuildings.push_back(obs); } } } } return; } float LocalPlayer::getFlagShakingTime() const { return flagShakingTime; } int LocalPlayer::getFlagShakingWins() const { return flagShakingWins; } const GLfloat* LocalPlayer::getAntidoteLocation() const { return (const GLfloat*)(antidoteFlag ? antidoteFlag->getSphere() : NULL); } ShotPath* LocalPlayer::getShot(int index) const { index &= 0x00FF; if ((index < 0) || (index >= World::getWorld()->getMaxShots())) return NULL; return shots[index]; } void LocalPlayer::restart(const float* pos, float _azimuth) { // put me in limbo setStatus(short(PlayerState::DeadStatus)); // can't have a flag setFlag(Flags::Null); // get rid of existing shots const int numShots = World::getWorld()->getMaxShots(); // get rid of existing shots for (int i = 0; i < numShots; i++) if (shots[i]) { delete shots[i]; shots[i] = NULL; } anyShotActive = false; // no target target = NULL; // no death deathPhyDrv = -1; // initialize position/speed state static const float zero[3] = { 0.0f, 0.0f, 0.0f }; location = (pos[2] > 0.0f) ? OnBuilding : OnGround; lastObstacle = NULL; lastSpeed = 0.0f; desiredSpeed = 0.0f; desiredAngVel = 0.0f; move(pos, _azimuth); setVelocity(zero); setAngularVelocity(0.0f); left = false; right = false; up = false; down = false; wantJump = false; doUpdateMotion(0.0f); updateHandicap(); entryDrop = true; // make me alive now setStatus(getStatus() | short(PlayerState::Alive)); } void LocalPlayer::setTeam(TeamColor _team) { changeTeam(_team); } void LocalPlayer::setDesiredSpeed(float fracOfMaxSpeed) { FlagType* flag = getFlag(); // can't go faster forward than at top speed, and backward at half speed if (fracOfMaxSpeed > 1.0f) fracOfMaxSpeed = 1.0f; else if (fracOfMaxSpeed < -0.5f) fracOfMaxSpeed = -0.5f; // oscillation overthruster tank in building can't back up if (fracOfMaxSpeed < 0.0f && getLocation() == InBuilding && flag == Flags::OscillationOverthruster && !bCheatOO) { fracOfMaxSpeed = 0.0f; } // boost speed for certain flags if (flag == Flags::Velocity) { fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD); } else if (flag == Flags::Thief) { fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD); } else if ((flag == Flags::Burrow) && (getPosition()[2] < 0.0f)) { fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_BURROWSPEEDAD); } else if ((flag == Flags::ForwardOnly) && (fracOfMaxSpeed < 0.0)) { fracOfMaxSpeed = 0.0f; } else if ((flag == Flags::ReverseOnly) && (fracOfMaxSpeed > 0.0)) { fracOfMaxSpeed = 0.0f; } else if (flag == Flags::Agility) { if ((TimeKeeper::getCurrent() - agilityTime) < BZDB.eval(StateDatabase::BZDB_AGILITYTIMEWINDOW)) { fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL); } else { float oldFrac = desiredSpeed / BZDBCache::tankSpeed; if (oldFrac > 1.0f) oldFrac = 1.0f; else if (oldFrac < -0.5f) oldFrac = -0.5f; float limit = BZDB.eval(StateDatabase::BZDB_AGILITYVELDELTA); if (fracOfMaxSpeed < 0.0f) limit /= 2.0f; if (fabs(fracOfMaxSpeed - oldFrac) > limit) { fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL); agilityTime = TimeKeeper::getCurrent(); } } } if (!BZDB.isTrue("displayConsole")) fracOfMaxSpeed *= 50; // apply handicap advantage to tank speed fracOfMaxSpeed *= (1.0f + (handicap * (BZDB.eval(StateDatabase::BZDB_HANDICAPVELAD) - 1.0f))); // set desired speed desiredSpeed = BZDBCache::tankSpeed * fracOfMaxSpeed; Player::setUserSpeed(desiredSpeed); return; } void LocalPlayer::setDesiredAngVel(float fracOfMaxAngVel) { FlagType* flag = getFlag(); // limit turn speed to maximum if (fracOfMaxAngVel > 1.0f) fracOfMaxAngVel = 1.0f; else if (fracOfMaxAngVel < -1.0f) fracOfMaxAngVel = -1.0f; // further limit turn speed for certain flags if (fracOfMaxAngVel < 0.0f && getFlag() == Flags::LeftTurnOnly) fracOfMaxAngVel = 0.0f; else if (fracOfMaxAngVel > 0.0f && getFlag() == Flags::RightTurnOnly) fracOfMaxAngVel = 0.0f; // boost turn speed for other flags if (flag) { fracOfMaxAngVel *= BZDB.eval(StateDatabase::BZDB_ANGULARAD); } else if ((flag == Flags::Burrow) && (getPosition()[2] < 0.0f)) { fracOfMaxAngVel *= BZDB.eval(StateDatabase::BZDB_BURROWANGULARAD); } // apply handicap advantage to tank speed fracOfMaxAngVel *= (1.0f + (handicap * (BZDB.eval(StateDatabase::BZDB_HANDICAPANGAD) - 1.0f))); // set desired turn speed desiredAngVel = fracOfMaxAngVel * BZDB.eval(StateDatabase::BZDB_TANKANGVEL); Player::setUserAngVel(desiredAngVel); return; } void LocalPlayer::setPause(bool pause) { if (isAlive()) { if (pause && !isPaused()) { setStatus(getStatus() | short(PlayerState::Paused)); server->sendPaused(true); } else if (!pause && isPaused()) { setStatus(getStatus() & ~short(PlayerState::Paused)); server->sendPaused(false); } } } void LocalPlayer::activateAutoPilot(bool autopilot) { if (autopilot && !isAutoPilot()) { setAutoPilot(); server->sendAutoPilot(true); } else if (!autopilot && isAutoPilot()) { setAutoPilot(false); server->sendAutoPilot(false); } } bool LocalPlayer::fireShot() { if (! (firingStatus == Ready || bCheatOO || firingStatus == Zoned)) return false; // find an empty slot const int numShots = World::getWorld()->getMaxShots(); int i; for (i = 0; i < numShots; i++) if (!shots[i]) break; if (i == numShots) return false; // make sure we're allowed to shoot if (!isAlive() || isPaused() || ((location == InBuilding) && !isPhantomZoned() && !bCheatOO)) { return false; } // prepare shot FiringInfo firingInfo(*this, i + getSalt()); // FIXME team coloring of shot is never used; it was meant to be used // for rabbit mode to correctly calculate team kills when rabbit changes firingInfo.shot.team = getTeam(); if (firingInfo.flagType == Flags::ShockWave) { // move shot origin under tank and make it stationary const float* pos = getPosition(); firingInfo.shot.pos[0] = pos[0]; firingInfo.shot.pos[1] = pos[1]; firingInfo.shot.pos[2] = pos[2]; firingInfo.shot.vel[0] = 0.0f; firingInfo.shot.vel[1] = 0.0f; firingInfo.shot.vel[2] = 0.0f; } else { // apply any handicap advantage to shot speed if (handicap > 0.0f) { const float speedAd = 1.0f + (handicap * (BZDB.eval(StateDatabase::BZDB_HANDICAPSHOTAD) - 1.0f)); const float* dir = getForward(); const float* tankVel = getVelocity(); const float shotSpeed = speedAd * BZDB.eval(StateDatabase::BZDB_SHOTSPEED); firingInfo.shot.vel[0] = tankVel[0] + shotSpeed * dir[0]; firingInfo.shot.vel[1] = tankVel[1] + shotSpeed * dir[1]; firingInfo.shot.vel[2] = tankVel[2] + shotSpeed * dir[2]; } bool LockedOn = false; const Player * target = getTarget(); if(target){ //const float* tankVel = getVelocity(); const float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED); float newAz = TargetingUtils::getTargetAzimuth( getPosition(), target->getPosition() ); float newPos[3]; const float * tarPos = target->getPosition(); float enemyPos[3]; const float *tv = target->getVelocity(); enemyPos[0] = tarPos[0] + (0.3f * tv[0]); //Lag adjustment enemyPos[1] = tarPos[1] + (0.3f * tv[1]); enemyPos[2] = tarPos[2] + (0.3f * tv[2]); const float * myPos = getPosition(); const float * myDir = getForward(); newPos[0] = enemyPos[0] - ( myPos[0] + (myDir[0] * 0.1)); //Muzzlefront? newPos[1] = enemyPos[1] - ( myPos[1] + (myDir[1] * 0.1)); //Muzzlefront? newPos[2] = enemyPos[2] - myPos[2]; //newPos[2] += target->getMuzzleHeight(); // right between the eyes float newElevation = atan2f(newPos[2], hypotf(newPos[1], newPos[0])); float newDir[3] = {cosf(newAz), sinf(newAz), sinf(newElevation)}; firingInfo.shot.vel[0] = /*tankVel[0] +*/ shotSpeed * newDir[0]; firingInfo.shot.vel[1] = /*tankVel[1] +*/ shotSpeed * newDir[1]; firingInfo.shot.vel[2] = /*tankVel[2] +*/ shotSpeed * newDir[2]; LockedOn = true; } // Set _shotsKeepVerticalVelocity on the server if you want shots // to have the same vertical velocity as the tank when fired. // keeping shots moving horizontally makes the game more playable. if (!LockedOn && !BZDB.isTrue(StateDatabase::BZDB_SHOTSKEEPVERTICALV)) firingInfo.shot.vel[2] = 0.0f; } // make shot and put it in the table shots[i] = new LocalShotPath(firingInfo); // Insert timestamp, useful for dead reckoning jitter fixing const float timeStamp = float(TimeKeeper::getCurrent() - TimeKeeper::getNullTime()); firingInfo.timeSent = timeStamp; server->sendBeginShot(firingInfo); if (BZDB.isTrue("enableLocalShotEffect") && SceneRenderer::instance().useQuality() >= 2) EFFECTS.addShotEffect(getColor(), firingInfo.shot.pos, getAngle(), getVelocity()); if (gettingSound) { if (firingInfo.flagType == Flags::ShockWave) { playLocalSound(SFX_SHOCK); ForceFeedback::shockwaveFired(); } else if (firingInfo.flagType == Flags::Laser) { playLocalSound(SFX_LASER); ForceFeedback::laserFired(); } else if (firingInfo.flagType == Flags::GuidedMissile) { playLocalSound(SFX_MISSILE); ForceFeedback::shotFired(); } else if (firingInfo.flagType == Flags::Thief) { playLocalSound(SFX_THIEF); ForceFeedback::shotFired(); } else { playLocalSound(SFX_FIRE); ForceFeedback::shotFired(); } } shotStatistics.recordFire(firingInfo.flagType,getForward(),firingInfo.shot.vel); if (getFlag() == Flags::TriggerHappy) { // make sure all the shots don't go off at once forceReload(BZDB.eval(StateDatabase::BZDB_RELOADTIME) / numShots); } return true; } float LocalPlayer::getReloadTime() const { const int numShots = World::getWorld()->getMaxShots(); if (numShots <= 0) { return 0.0f; } float time = float(jamTime - TimeKeeper::getCurrent()); if (time > 0.0f) { return time; } // look for an empty slot int i; for (i = 0; i < numShots; i++) { if (!shots[i]) { return 0.0f; } } // look for the shot fired least recently float minTime = float(shots[0]->getReloadTime() - (shots[0]->getCurrentTime() - shots[0]->getStartTime())); for (i = 1; i < numShots; i++) { const float t = float(shots[i]->getReloadTime() - (shots[i]->getCurrentTime() - shots[i]->getStartTime())); if (t < minTime) { minTime = t; } } if (minTime < 0.0f) { minTime = 0.0f; } return minTime; } bool LocalPlayer::doEndShot(int ident, bool isHit, float* pos) { const int index = ident & 255; const int slt = (ident >> 8) & 127; // special id used in some messages (and really shouldn't be sent here) if (ident == -1) return false; // ignore bogus shots (those with a bad index or for shots that don't exist) if (index < 0 || index >= World::getWorld()->getMaxShots() || !shots[index]) return false; // ignore shots that already ending if (shots[index]->isExpired() || shots[index]->isExpiring()) return false; // ignore shots that have the wrong salt. since we reuse shot indices // it's possible for an old MsgShotEnd to arrive after we've started a // new shot. that's where the salt comes in. it changes for each shot // so we can identify an old shot from a new one. if (slt != ((shots[index]->getShotId() >> 8) & 127)) return false; // keep shot statistics shotStatistics.recordHit(shots[index]->getFlag()); // don't stop if it's because were hitting something and we don't stop // when we hit something. if (isHit && !shots[index]->isStoppedByHit()) return false; // end it const float* shotPos = shots[index]->getPosition(); pos[0] = shotPos[0]; pos[1] = shotPos[1]; pos[2] = shotPos[2]; shots[index]->setExpired(); return true; } void LocalPlayer::setJump() { wantJump = jumpPressed; } void LocalPlayer::setJumpPressed(bool value) { jumpPressed = value; } void LocalPlayer::doJump() { FlagType* flag = getFlag(); // can't jump while burrowed if (getPosition()[2] < 0.0f) { return; } if(BZDB.isTrue("displayRadar")){ if (flag == Flags::Wings) { if (wingsFlapCount <= 0) { return; } wingsFlapCount--; } else if ((location != OnGround) && (location != OnBuilding)) { // can't jump unless on the ground or a building if (flag != Flags::Wings) return; if (wingsFlapCount <= 0) return; wingsFlapCount--; } else if ((flag != Flags::Bouncy) && ((flag != Flags::Jumping && !World::getWorld()->allowJumping()) || (flag == Flags::NoJumping))) { return; } } // add jump velocity (actually, set the vertical component since you // can only jump if resting on something) const float* oldVelocity = getVelocity(); float newVelocity[3]; newVelocity[0] = oldVelocity[0]; newVelocity[1] = oldVelocity[1]; if (flag != Flags::Wings ){ newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY); } else if (flag == Flags::Bouncy) { const float factor = 0.25f + ((float)bzfrand() * 0.75f); newVelocity[2] = factor * BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY); } else { newVelocity[2] = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY); } setVelocity(newVelocity); location = InAir; // setup the graphics fireJumpJets(); // setup the sound if (gettingSound) { if (flag == Flags::Wings) { playLocalSound(SFX_FLAP); addRemoteSound(PlayerState::WingsSound); } else { playLocalSound(SFX_JUMP); addRemoteSound(PlayerState::JumpSound); } } wantJump = false; } void LocalPlayer::setTarget(const Player* _target) { target = _target; } void LocalPlayer::setNemesis(const Player* _nemesis) { if ((_nemesis == NULL) || _nemesis->getPlayerType() == TankPlayer) nemesis = _nemesis; } void LocalPlayer::setRecipient(const Player* _recipient) { if ((_recipient == NULL) || (_recipient->getId() <= LastRealPlayer)) recipient = _recipient; } void LocalPlayer::explodeTank() { if (location == Dead || location == Exploding) return; float gravity = BZDBCache::gravity; float explodeTim = BZDB.eval(StateDatabase::BZDB_EXPLODETIME); // Limiting max height increment to this value (the old default value) const float zMax = 49.0f; setExplode(TimeKeeper::getTick()); const float* oldVelocity = getVelocity(); float newVelocity[3]; float maxSpeed; newVelocity[0] = oldVelocity[0]; newVelocity[1] = oldVelocity[1]; if (gravity < 0) { // comparing 2 speed: // to have a simmetric path (ending at same height as starting) // to reach the acme of parabola, under the max height established // take the less newVelocity[2] = - 0.5f * gravity * explodeTim; maxSpeed = sqrtf(- 2.0f * zMax * gravity); if (newVelocity[2] > maxSpeed) newVelocity[2] = maxSpeed; } else { newVelocity[2] = oldVelocity[2]; } setVelocity(newVelocity); location = Exploding; } //testing123 void LocalPlayer::doMomentum(float dt, float& speed, float& angVel) { // get maximum linear and angular accelerations float linearAcc = (getFlag() == Flags::Momentum) ? BZDB.eval(StateDatabase::BZDB_MOMENTUMLINACC) : World::getWorld()->getLinearAcceleration(); float angularAcc = (getFlag() == Flags::Momentum) ? BZDB.eval(StateDatabase::BZDB_MOMENTUMANGACC) : World::getWorld()->getAngularAcceleration(); // limit linear acceleration if (linearAcc > 0.0f) { const float acc = (speed - lastSpeed) / dt; if (acc > 20.0f * linearAcc) speed = lastSpeed + dt * 20.0f*linearAcc; else if (acc < -20.0f * linearAcc) speed = lastSpeed - dt * 20.0f*linearAcc; } // limit angular acceleration if (angularAcc > 0.0f) { const float oldAngVel = getAngularVelocity(); const float angAcc = (angVel - oldAngVel) / dt; if (angAcc > angularAcc) angVel = oldAngVel + dt * angularAcc; else if (angAcc < -angularAcc) angVel = oldAngVel - dt * angularAcc; } } void LocalPlayer::doFriction(float dt, const float *oldVelocity, float *newVelocity) { const float friction = (getFlag() == Flags::Momentum) ? BZDB.eval(StateDatabase::BZDB_MOMENTUMFRICTION) : BZDB.eval(StateDatabase::BZDB_FRICTION); if (friction > 0.0f) { // limit vector acceleration float delta[2] = {newVelocity[0] - oldVelocity[0], newVelocity[1] - oldVelocity[1]}; float acc2 = (delta[0] * delta[0] + delta[1] * delta[1]) / (dt*dt); float accLimit = 20.0f * friction; if (acc2 > accLimit*accLimit) { float ratio = accLimit / sqrtf(acc2); newVelocity[0] = oldVelocity[0] + delta[0]*ratio; newVelocity[1] = oldVelocity[1] + delta[1]*ratio; } } } void LocalPlayer::doForces(float /*dt*/, float* /*velocity*/, float& /*angVel*/) { // apply external forces // do nothing -- no external forces right now } // NOTE -- minTime should be initialized to Infinity by the caller bool LocalPlayer::checkHit(const Player* source, const ShotPath*& hit, float& minTime) const { bool goodHit = false; // if firing tank is paused then it doesn't count if (source->isPaused()) return goodHit; const int maxShots = source->getMaxShots(); for (int i = 0; i < maxShots; i++) { // get shot const ShotPath* shot = source->getShot(i); if (!shot || shot->isExpired()) continue; // my own shock wave cannot kill me if (source == this && ((shot->getFlag() == Flags::ShockWave) || (shot->getFlag() == Flags::Thief))) continue; // short circuit test if shot can't possibly hit. // only superbullet or shockwave can kill zoned dude const FlagType* shotFlag = shot->getFlag(); if (isPhantomZoned() && (shotFlag != Flags::ShockWave) && (shotFlag != Flags::SuperBullet) && (shotFlag != Flags::PhantomZone)) continue; // laser can't hit a cloaked tank if ((getFlag() == Flags::Cloaking) && (shotFlag == Flags::Laser)) continue; // zoned shots only kill zoned tanks if ((shotFlag == Flags::PhantomZone) && !isPhantomZoned()) { continue; } // test myself against shot float position[3]; const float t = shot->checkHit(this, position); if (t >= minTime) continue; // test if shot hit a part of my tank that's through a teleporter. // hit is no good if hit point is behind crossing plane. if (isCrossingWall() && position[0] * crossingPlane[0] + position[1] * crossingPlane[1] + position[2] * crossingPlane[2] + crossingPlane[3] < 0.0) continue; // okay, shot hit goodHit = true; hit = shot; minTime = t; } return goodHit; } void LocalPlayer::setFlag(FlagType* flag) { Player::setFlag(flag); float worldSize = BZDBCache::worldSize; // if it's bad then reset countdowns and set antidote flag if (getFlag() != Flags::Null && getFlag()->endurance == FlagSticky) { if (World::getWorld()->allowShakeTimeout()) flagShakingTime = World::getWorld()->getFlagShakeTimeout(); if (World::getWorld()->allowShakeWins()) flagShakingWins = World::getWorld()->getFlagShakeWins(); if (World::getWorld()->allowAntidote()) { float tankRadius = BZDBCache::tankRadius; float baseSize = BZDB.eval(StateDatabase::BZDB_BASESIZE); do { if (World::getWorld()->allowTeamFlags()) { flagAntidotePos[0] = 0.5f * worldSize * ((float)bzfrand() - 0.5f); flagAntidotePos[1] = 0.5f * worldSize * ((float)bzfrand() - 0.5f); flagAntidotePos[2] = 0.0f; } else { flagAntidotePos[0] = (worldSize - baseSize) * ((float)bzfrand() - 0.5f); flagAntidotePos[1] = (worldSize - baseSize) * ((float)bzfrand() - 0.5f); flagAntidotePos[2] = 0.0f; } } while (World::getWorld()->inBuilding(flagAntidotePos, tankRadius, BZDBCache::tankHeight)); antidoteFlag = new FlagSceneNode(flagAntidotePos); antidoteFlag->setColor(1.0f, 1.0f, 0.0f); World::setFlagTexture(antidoteFlag); } } else { delete antidoteFlag; antidoteFlag = NULL; flagShakingTime = 0.0f; flagShakingWins = 0; } } void LocalPlayer::changeScore(short deltaWins, short deltaLosses, short deltaTks) { Player::changeScore(deltaWins, deltaLosses, deltaTks); if (deltaWins > 0 && World::getWorld()->allowShakeWins() && flagShakingWins > 0) { flagShakingWins -= deltaWins; if (flagShakingWins <= 0) { flagShakingWins = 0; server->sendDropFlag(getPosition()); } } } // calculate overall relative score to other players int LocalPlayer::getHandicapScoreBase() const { int winners = 0; int loosers = 0; const int maxplayers = World::getWorld()->getMaxPlayers(); // compute overall wins and losses in relation to other players for (int i = 0; i < maxplayers; i++) { RemotePlayer *player = World::getWorld()->getPlayer(i); if ((player == NULL) || (player->getId() == LocalPlayer::getMyTank()->getId())) { continue; } winners += player->getLocalWins(); loosers += player->getLocalLosses(); } return loosers - winners; } float LocalPlayer::updateHandicap() { /* compute and return the handicap */ if (World::getWorld()->allowHandicap()) { // a relative score of -50 points will provide maximum handicap handicap = float(getHandicapScoreBase()) / BZDB.eval(StateDatabase::BZDB_HANDICAPSCOREDIFF); /* limit how much of a handicap is afforded, and only provide * handicap advantages instead of disadvantages. */ if (handicap > 1.0f) { // advantage handicap = 1.0f; } else if (handicap < 0.0f) { // disadvantage handicap = 0.0f; } return handicap; } // no handicap gameplay return 0.0f; } void LocalPlayer::addAntidote(SceneDatabase* scene) { if (antidoteFlag) scene->addDynamicNode(antidoteFlag); } std::string LocalPlayer::getInputMethodName(InputMethod whatInput) { switch (whatInput) { case Keyboard: return std::string("Keyboard"); break; case Mouse: return std::string("Mouse"); break; case Joystick: return std::string("Joystick"); break; default: return std::string("Unnamed Device"); } } void LocalPlayer::setKey(int button, bool pressed) { switch (button) { case BzfKeyEvent::Left: left = pressed; break; case BzfKeyEvent::Right: right = pressed; break; case BzfKeyEvent::Up: up = pressed; break; case BzfKeyEvent::Down: down = pressed; break; } } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* interface header */ #include "Player.h" // common interface headers #include "TankSceneNode.h" #include "SceneRenderer.h" #include "SphereSceneNode.h" #include "OpenGLMaterial.h" #include "BZDBCache.h" #include "TextureManager.h" #include "CollisionManager.h" #include "ObstacleMgr.h" #include "PhysicsDriver.h" #include "ObstacleList.h" #include "WallObstacle.h" // local implementation headers #include "playing.h" #include "World.h" #include "TrackMarks.h" #include "sound.h" #include "effectsRenderer.h" #include "Roaming.h" #include "LocalPlayer.h" float sizeCheat = 1.0f; // for dead reckoning static const float MaxUpdateTime = 1.0f; // seconds // // Player // int Player::tankTexture = -1; Player::Player(const PlayerId& _id, TeamColor _team, const char* name, const char* _email, const PlayerType _type) : lastObstacle(NULL), pauseMessageState(false), notResponding(false), hunted(false), id(_id), admin(false), registered(false), verified(false), playerList(false), lastVisualTeam(NoTeam), team(_team), type(_type), flagType(Flags::Null), fromTeleporter(0), toTeleporter(0), teleporterProximity(0.0f), wins(0), losses(0), tks(0), localWins(0), localLosses(0), localTks(0), autoPilot(false), deltaTime(0.0), offset(0.0), deadReckoningState(0), oldStatus(0), oldZSpeed(0.0f) { static const float zero[3] = { 0.0f, 0.0f, 0.0f }; move(zero, 0.0f); setVelocity(zero); setAngularVelocity(0.0f); setPhysicsDriver(-1); setDeadReckoning(); setRelativeMotion(); setUserSpeed(0.0f); setUserAngVel(0.0f); reportedHits = 0; computedHits = 0; // set call sign ::strncpy(callSign, name, CallSignLen); callSign[CallSignLen-1] = '\0'; // set email address ::strncpy(email, _email, EmailLen); email[EmailLen-1] = '\0'; if (id != ServerPlayer) { // make scene nodes tankNode = new TankSceneNode(state.pos, forward); tankIDLNode = new TankIDLSceneNode(tankNode); changeTeam(team); const float sphereRad = (1.5f * BZDBCache::tankRadius); if (RENDERER.useQuality() >= 3) { pausedSphere = new SphereLodSceneNode(state.pos, sphereRad); } else { pausedSphere = new SphereBspSceneNode(state.pos, sphereRad); } pausedSphere->setColor(0.0f, 0.0f, 0.0f, 0.5f); } // setup the dimension properties dimensions[0] = 0.5f * BZDBCache::tankLength; dimensions[1] = 0.5f * BZDBCache::tankWidth; dimensions[2] = BZDBCache::tankHeight; for (int i = 0; i < 3; i++) { dimensionsRate[i] = 0.0f; dimensionsScale[i] = 1.0f; dimensionsTarget[i] = 1.0f; } useDimensions = false; // setup alpha properties alpha = 1.0f; alphaRate = 0.0f; alphaTarget = 1.0f; teleAlpha = 1.0f; haveIpAddr = false; // no IP address yet lastTrackDraw = TimeKeeper::getCurrent(); spawnTime = TimeKeeper::getCurrent(); return; } Player::~Player() { if (id != ServerPlayer) { delete tankIDLNode; delete tankNode; delete pausedSphere; } } // Take into account the quality of player wins/(wins+loss) // Try to penalize winning casuality static float rabbitRank (int wins, int losses) { // otherwise do score-based ranking int sum = wins + losses; if (sum == 0) return 0.5; float average = (float)wins/(float)sum; // IIRC that is how wide is the gaussian float penalty = (1.0f - 0.5f / sqrt((float)sum)); return average * penalty; } float Player::getTKRatio() const { if (wins == 0) { // well, we have to return *something* if they have no wins return (float)tks/1.0f; } else { return (float)tks/(float)wins; } } // returns a value between 1.0 and -1.0 float Player::getNormalizedScore() const { return ((float)wins - losses) / ((wins+losses>20) ? wins+losses : 20); } float Player::getLocalNormalizedScore() const { return ((float)localWins - localLosses) / ((localWins+localLosses>5) ? localWins+localLosses : 5); } short Player::getRabbitScore() const { return (int)(rabbitRank(wins, losses) * 100.0); } float Player::getRadius() const { // NOTE: this encompasses everything but Narrow // the Obese, Tiny, and Thief flags adjust // the radius, but Narrow does not. return (dimensionsScale[0] * BZDBCache::tankRadius); } float Player::getMaxSpeed ( void ) const { // BURROW and AGILITY will not be taken into account const FlagType* flag = getFlag(); float maxSpeed = BZDBCache::tankSpeed; if (flag == Flags::Velocity) { maxSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD); } else if (flag == Flags::Thief) { maxSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD); } return maxSpeed; } void Player::getMuzzle(float* m) const { // NOTE: like getRadius(), we only use dimensionsScale[0]. // as well, we do not use BZDB_MUZZLEFRONT, but the // 0.1f value listed in global.cxx is added on to the // scaled version of tankRadius. float front = (dimensionsScale[0] * BZDBCache::tankRadius); if (dimensionsRate[0] > 0.0f) { front = front + (dimensionsRate[0] * 0.1f); } front = front + 0.1f; m[0] = state.pos[0] + (front * forward[0]); m[1] = state.pos[1] + (front * forward[1]); const float height = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); m[2] = state.pos[2] + (height * dimensionsScale[2]); return; } float Player::getMuzzleHeight() const { return (dimensionsScale[2] * BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT)); } void Player::forceReload(float time) { jamTime = TimeKeeper::getCurrent(); jamTime+= time; } void Player::move(const float* _pos, float _azimuth) { // update the speed of the state float currentTime = (float)TimeKeeper::getCurrent().getSeconds(); if ( state.lastUpdateTime >= 0 ) { float delta = currentTime - state.lastUpdateTime; state.aperantVelocity[0] = (_pos[0]-state.pos[0])/delta; state.aperantVelocity[1] = (_pos[1]-state.pos[1])/delta; state.aperantVelocity[2] = (_pos[2]-state.pos[2])/delta; } state.lastUpdateTime = currentTime; // assumes _forward is normalized state.pos[0] = _pos[0]; state.pos[1] = _pos[1]; state.pos[2] = _pos[2]; state.azimuth = _azimuth; // limit angle if (state.azimuth < 0.0f) { state.azimuth = (float)((2.0 * M_PI) - fmodf(-state.azimuth, (float)(2.0 * M_PI))); } else if (state.azimuth >= (2.0f * M_PI)) { state.azimuth = fmodf(state.azimuth, (float)(2.0 * M_PI)); } // update forward vector (always in horizontal plane) forward[0] = cosf(state.azimuth); forward[1] = sinf(state.azimuth); forward[2] = 0.0f; // compute teleporter proximity if (World::getWorld()) { teleporterProximity = World::getWorld()->getProximity(state.pos, BZDBCache::tankRadius); } } void Player::setVelocity(const float* _velocity) { state.velocity[0] = _velocity[0]; state.velocity[1] = _velocity[1]; state.velocity[2] = _velocity[2]; } void Player::setAngularVelocity(float _angVel) { state.angVel = _angVel; } void Player::setPhysicsDriver(int driver) { state.phydrv = driver; const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver); if (phydrv != NULL) { state.status |= PlayerState::OnDriver; } else { state.status &= ~PlayerState::OnDriver; } return; } void Player::setRelativeMotion() { bool falling = (state.status & short(PlayerState::Falling)) != 0; if (falling && (getFlag() != Flags::Wings)) { // no adjustments while falling return; } // set 'relativeSpeed' and 'relativeAngVel' float relativeVel[2]; Player::calcRelativeMotion(relativeVel, relativeSpeed, relativeAngVel); return; } void Player::setUserSpeed(float speed) { state.userSpeed = speed; return; } void Player::setUserAngVel(float angVel) { state.userAngVel = angVel; return; } void Player::calcRelativeMotion(float vel[2], float& speed, float& angVel) { vel[0] = state.velocity[0]; vel[1] = state.velocity[1]; angVel = state.angVel; const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(state.phydrv); if (phydrv != NULL) { const float* v = phydrv->getLinearVel(); const float av = phydrv->getAngularVel(); const float* ap = phydrv->getAngularPos(); // adjust for driver velocity vel[0] -= v[0]; vel[1] -= v[1]; // adjust for driver angular velocity if (av != 0.0f) { const float dx = state.pos[0] - ap[0]; const float dy = state.pos[1] - ap[1]; vel[0] += av * dy; vel[1] -= av * dx; angVel = state.angVel - av; } } // speed relative to the tank's direction // (could use forward[] instead of re-doing the trig, but this is // used in the setDeadReckoning(), when forward[] is not yet set) speed = (vel[0] * cosf(state.azimuth)) + (vel[1] * sinf(state.azimuth)); return; } void Player::changeTeam(TeamColor _team) { // set team team = _team; // set the scene node setVisualTeam(team); } void Player::setStatus(short _status) { state.status = _status; } void Player::setExplode(const TimeKeeper& t) { if (!isAlive()) return; explodeTime = t; setStatus((getStatus() | short(PlayerState::Exploding) | short(PlayerState::Falling)) & ~(short(PlayerState::Alive) | short(PlayerState::Paused))); tankNode->rebuildExplosion(); // setup the flag effect to revert to normal updateFlagEffect(Flags::Null); } void Player::setTeleport(const TimeKeeper& t, short from, short to) { if (!isAlive()) return; teleportTime = t; fromTeleporter = from; toTeleporter = to; setStatus(getStatus() | short(PlayerState::Teleporting)); } void Player::updateTank(float dt, bool local) { updateDimensions(dt, local); updateTranslucency(dt); updateTreads(dt); updateJumpJets(dt); updateTrackMarks(); return; } void Player::updateJumpJets(float dt) { float jumpVel; if (getFlag() == Flags::Wings) { jumpVel = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY); } else { jumpVel = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY); } const float jetTime = 0.5f * (jumpVel / -BZDBCache::gravity); state.jumpJetsScale -= (dt / jetTime); if (state.jumpJetsScale < 0.0f) { state.jumpJetsScale = 0.0f; state.status &= ~PlayerState::JumpJets; } return; } void Player::updateTrackMarks() { const float minSpeed = 0.1f; // relative speed slop if (isAlive() && !isFalling() && !isPhantomZoned()) { const float lifeTime = float(TimeKeeper::getCurrent() - lastTrackDraw); if (lifeTime > TrackMarks::updateTime) { bool drawMark = true; float markPos[3]; markPos[2] = state.pos[2]; // FIXME - again, this should be pulled for TankGeometryMgr const float fullLength = 6.0f; const float treadHeight = 1.2f; const float dist = dimensions[0] * ((fullLength - treadHeight) / fullLength); if (relativeSpeed > +minSpeed) { // draw the mark at the back of the treads markPos[0] = state.pos[0] - (forward[0] * dist); markPos[1] = state.pos[1] - (forward[1] * dist); } else if (relativeSpeed < -minSpeed) { // draw the mark at the front of the treads markPos[0] = state.pos[0] + (forward[0] * dist); markPos[1] = state.pos[1] + (forward[1] * dist); } else { drawMark = false; } if (drawMark) { TrackMarks::addMark(markPos, dimensionsScale[1], state.azimuth, state.phydrv); lastTrackDraw = TimeKeeper::getCurrent(); } } } return; } void Player::updateDimensions(float dt, bool local) { // copy the current information float oldRates[3]; float oldScales[3]; float oldDimensions[3]; memcpy (oldRates, dimensionsRate, sizeof(float[3])); memcpy (oldScales, dimensionsScale, sizeof(float[3])); memcpy (oldDimensions, dimensions, sizeof(float[3])); // update the dimensions bool resizing = false; for (int i = 0; i < 3; i++) { if (dimensionsRate[i] != 0.0f) { resizing = true; dimensionsScale[i] += (dt * dimensionsRate[i]); if (dimensionsRate[i] < 0.0f) { if (dimensionsScale[i] < dimensionsTarget[i]) { dimensionsScale[i] = dimensionsTarget[i]; dimensionsRate[i] = 0.0f; } } else { if (dimensionsScale[i] > dimensionsTarget[i]) { dimensionsScale[i] = dimensionsTarget[i]; dimensionsRate[i] = 0.0f; } } } else { // safety play, should not be required dimensionsScale[i] = dimensionsTarget[i]; } } // set the actual dimensions based on the scale dimensions[0] = dimensionsScale[0] * (0.5f * BZDBCache::tankLength); dimensions[1] = dimensionsScale[1] * (0.5f * BZDBCache::tankWidth); dimensions[2] = dimensionsScale[2] * BZDBCache::tankHeight; // do not resize if it will cause a collision // only checked for the local player, remote is computationally expensive if (local) { // also do not bother with collision checking if we are not resizing if (resizing && hitObstacleResizing()) { // copy the old information memcpy (dimensions, oldDimensions, sizeof(float[3])); memcpy (dimensionsScale, oldScales, sizeof(float[3])); memcpy (dimensionsRate, oldRates, sizeof(float[3])); } } // check if the dimensions are at a steady state if ((dimensionsScale[0] == dimensionsTarget[0]) && (dimensionsScale[1] == dimensionsTarget[1]) && (dimensionsScale[2] == dimensionsTarget[2])) { useDimensions = false; } else { useDimensions = true; } return; } bool Player::hitObstacleResizing() { const float* dims = dimensions; // check walls const World* world = World::getWorld(); if (world) { const ObstacleList& walls = OBSTACLEMGR.getWalls(); for (unsigned int i = 0; i < walls.size(); i++) { const WallObstacle* wall = (const WallObstacle*) walls[i]; if (wall->inBox(getPosition(), getAngle(), dims[0], dims[1], dims[2])) { return true; } } } // check everything else const ObsList* olist = COLLISIONMGR.boxTest(getPosition(), getAngle(), dims[0], dims[1], dims[2]); for (int i = 0; i < olist->count; i++) { const Obstacle* obs = olist->list[i]; const bool onTop = obs->isFlatTop() && ((obs->getPosition()[2] + obs->getHeight()) <= getPosition()[2]); if (!obs->isDriveThrough() && !onTop && obs->inBox(getPosition(), getAngle(), dims[0], dims[1], dims[2])) { return true; } } return false; } void Player::updateTranslucency(float dt) { // update the alpha value if (alphaRate != 0.0f) { alpha += dt * alphaRate; if (alphaRate < 0.0f) { if (alpha < alphaTarget) { alpha = alphaTarget; alphaRate = 0.0f; } } else { if (alpha > alphaTarget) { alpha = alphaTarget; alphaRate = 0.0f; } } } // set the tankNode color if (isPhantomZoned()) { teleAlpha = 1.0f; color[3] = 0.25f; // barely visible, regardless of teleporter proximity } else { teleporterProximity = World::getWorld()->getProximity(state.pos, BZDBCache::tankRadius); teleAlpha = (1.0f - (0.75f * teleporterProximity)); if (alpha == 0.0f) { color[3] = 0.0f; // not trusting FP accuracy } else { color[3] = teleAlpha * alpha; } } return; } void Player::updateTreads(float dt) { float speedFactor; float angularFactor; if ((state.status & PlayerState::UserInputs) != 0) { speedFactor = state.userSpeed; angularFactor = state.userAngVel; } else { speedFactor = relativeSpeed; angularFactor = relativeAngVel; } // setup the linear component if (dimensionsScale[0] > 1.0e-6f) { speedFactor = speedFactor / dimensionsScale[0]; } else { speedFactor = speedFactor * 1.0e6f; } // setup the angular component const float angularScale = 4.0f; // spin factor (at 1.0, the edges line up) angularFactor *= angularScale; const float halfWidth = 0.5f * BZDBCache::tankWidth; // not using dimensions[1], because it may be set to 0.001 by a Narrow flag angularFactor *= dimensionsScale[0] * halfWidth; const float leftOff = dt * (speedFactor - angularFactor); const float rightOff = dt * (speedFactor + angularFactor); tankNode->addTreadOffsets(leftOff, rightOff); return; } void Player::changeScore(short deltaWins, short deltaLosses, short deltaTeamKills) { wins += deltaWins; losses += deltaLosses; tks += deltaTeamKills; } void Player::changeLocalScore(short dWins, short dLosses, short dTeamKills) { localWins += dWins; localLosses += dLosses; localTks += dTeamKills; } void Player::setFlag(FlagType* _flag) { // set the type flagType = _flag; updateFlagEffect(flagType); return; } void Player::updateFlagEffect(FlagType* effectFlag) { float FlagEffectTime = BZDB.eval(StateDatabase::BZDB_FLAGEFFECTTIME); if (FlagEffectTime <= 0.0f) { FlagEffectTime = 0.001f; // safety } // set the dimension targets dimensionsTarget[0] = 1.0f; dimensionsTarget[1] = 1.0f; dimensionsTarget[2] = 1.0f; if (effectFlag == Flags::Obesity) { const float factor = BZDB.eval(StateDatabase::BZDB_OBESEFACTOR); dimensionsTarget[0] = factor; dimensionsTarget[1] = factor; } else if (effectFlag == Flags::Tiny) { const float factor = BZDB.eval(StateDatabase::BZDB_TINYFACTOR); dimensionsTarget[0] = factor; dimensionsTarget[1] = factor; } else if (effectFlag == Flags::Thief) { const float factor = BZDB.eval(StateDatabase::BZDB_THIEFTINYFACTOR); dimensionsTarget[0] = factor; dimensionsTarget[1] = factor; } else if (effectFlag == Flags::Narrow) { dimensionsTarget[1] = 0.001f; } if (id == LocalPlayer::getMyTank()->getId()) { dimensionsTarget[0] *= sizeCheat; dimensionsTarget[1] *= sizeCheat; } // set the dimension rates for (int i = 0; i < 3; i++) { if (dimensionsTarget[i] != dimensionsScale[i]) { dimensionsRate[i] = dimensionsTarget[i] - dimensionsScale[i]; dimensionsRate[i] = dimensionsRate[i] / FlagEffectTime; } } // set the alpha target if (effectFlag == Flags::Cloaking) { alphaTarget = 0.5f; } else { alphaTarget = 1.0f; } // set the alpha rate if (alphaTarget != alpha) { alphaRate = (alphaTarget - alpha) / FlagEffectTime; } return; } void Player::endShot(int index, bool isHit, bool showExplosion) { float pos[3]; if (doEndShot(index, isHit, pos) && showExplosion) { addShotExplosion(pos); } return; } void Player::setVisualTeam (TeamColor visualTeam) { // only do all this junk when the effective team color actually changes if (visualTeam == lastVisualTeam) return; lastVisualTeam = visualTeam; static const GLfloat tankSpecular[3] = { 0.1f, 0.1f, 0.1f }; static GLfloat tankEmissive[3] = { 0.0f, 0.0f, 0.0f }; static float tankShininess = 20.0f; static GLfloat rabbitEmissive[3] = { 0.0f, 0.0f, 0.0f }; static float rabbitShininess = 100.0f; GLfloat *emissive; GLfloat shininess; if (visualTeam == RabbitTeam) { emissive = rabbitEmissive; shininess = rabbitShininess; } else { emissive = tankEmissive; shininess = tankShininess; } // get the texture each time, since it's just a refrence const bool hunter = World::getWorld()->allowRabbit() && visualTeam != RabbitTeam; TextureManager &tm = TextureManager::instance(); std::string texName; if (hunter) texName = BZDB.get("hunterTeamPrefix"); else texName = Team::getImagePrefix(visualTeam); texName += BZDB.get("tankTexture"); // now after we did all that, see if they have a user texture tankTexture = -1; if (userTexture.size()) tankTexture = tm.getTextureID(userTexture.c_str(),false); // if the user load failed try our calculated texture if (tankTexture < 0) tankTexture = tm.getTextureID(texName.c_str(),false); // we are the hunter, we are orange.. // TODO this is cheap, just untill a "hunter" team is made if (hunter) { color[0] = 1.0f; color[1] = 0.5f; color[2] = 0.0f; } else { const float* _color = Team::getTankColor(visualTeam); color[0] = _color[0]; color[1] = _color[1]; color[2] = _color[2]; } tankNode->setMaterial(OpenGLMaterial(tankSpecular, emissive, shininess)); tankNode->setTexture(tankTexture); int jumpJetsTexture = tm.getTextureID("jumpjets", false); tankNode->setJumpJetsTexture(jumpJetsTexture); } void Player::fireJumpJets() { state.jumpJetsScale = 1.0f; state.status |= PlayerState::JumpJets; return; } void Player::clearRemoteSounds() { state.sounds = PlayerState::NoSounds; state.status &= ~PlayerState::PlaySound; return; } void Player::addRemoteSound(int sound) { state.sounds |= sound; if (state.sounds != PlayerState::NoSounds) { state.status |= PlayerState::PlaySound; } return; } void Player::addToScene(SceneDatabase* scene, TeamColor effectiveTeam, bool inCockpit, bool seerView, bool showTreads, bool showIDL) { const GLfloat groundPlane[4] = {0.0f, 0.0f, 1.0f, 0.0f}; if (!isAlive() && !isExploding()) { return; // don't draw anything } // place the tank tankNode->move(state.pos, forward); // only use dimensions if we aren't at steady state. // this is done because it's more expensive to use // GL_NORMALIZE then to use precalculated normals. if (useDimensions) { tankNode->setDimensions(dimensionsScale); } else { if (flagType == Flags::Obesity) tankNode->setObese(); else if (flagType == Flags::Tiny) tankNode->setTiny(); else if (flagType == Flags::Narrow) tankNode->setNarrow(); else if (flagType == Flags::Thief) tankNode->setThief(); else tankNode->setNormal(); } // setup the visibility properties if (inCockpit && !showTreads) { tankNode->setOnlyShadows(true); } else { tankNode->setOnlyShadows(false); } // adjust alpha for seerView if (seerView) { if (isPhantomZoned()) { color[3] = 0.25f; } else { color[3] = teleAlpha; } } // setup the color and material setVisualTeam(effectiveTeam); tankNode->setColor(color); tankNode->setInTheCockpit(inCockpit); // reset the clipping plane tankNode->setClipPlane(NULL); tankNode->setJumpJets(0.0f); if (isAlive()) { tankNode->setExplodeFraction(0.0f); tankNode->setJumpJets(state.jumpJetsScale); scene->addDynamicNode(tankNode); if (isCrossingWall()) { // get which plane to compute IDL against GLfloat plane[4]; const GLfloat a = atan2f(forward[1], forward[0]); const Obstacle* obstacle = World::getWorld()->hitBuilding(state.pos, a, dimensions[0], dimensions[1], dimensions[2]); if (obstacle && obstacle->isCrossing(state.pos, a, dimensions[0], dimensions[1], dimensions[2], plane) || World::getWorld()->crossingTeleporter(state.pos, a, dimensions[0], dimensions[1], dimensions[2], plane)) { // stick in interdimensional lights node if (showIDL) { tankIDLNode->move(plane); scene->addDynamicNode(tankIDLNode); } // add clipping plane to tank node if (!inCockpit) { tankNode->setClipPlane(plane); } } } else if (getPosition()[2] < 0.0f) { // this should only happen with Burrow flags tankNode->setClipPlane(groundPlane); } // isCrossingWall() } // isAlive() else if (isExploding() && (state.pos[2] > ZERO_TOLERANCE)) { float t = float((TimeKeeper::getTick() - explodeTime) / BZDB.eval(StateDatabase::BZDB_EXPLODETIME)); if (t > 1.0f) { // FIXME - setStatus(DeadStatus); t = 1.0f; } else if (t < 0.0f) { // shouldn't happen but why take chances t = 0.0f; } // fade at the end of the explosion const float fadeRatio = 0.8f; if (t > fadeRatio) { GLfloat newColor[4]; memcpy(newColor, color, sizeof(float[3])); const float fadeFactor = (1.0f - t) / (1.0f - fadeRatio); newColor[3] = color[3] * fadeFactor; tankNode->setColor(newColor); } tankNode->setExplodeFraction(t); tankNode->setClipPlane(groundPlane); // shadows are not clipped scene->addDynamicNode(tankNode); } if (isAlive() && (isPaused() || isNotResponding())) { pausedSphere->move(state.pos, 1.5f * BZDBCache::tankRadius * dimensionsScale[0]); scene->addDynamicSphere(pausedSphere); } } void Player::setLandingSpeed(float velocity) { float squishiness = BZDB.eval(StateDatabase::BZDB_SQUISHFACTOR); if (squishiness < 0.001f) { return; } float squishTime = BZDB.eval(StateDatabase::BZDB_SQUISHTIME); if (squishTime < 0.001) { return; } const float gravity = BZDBCache::gravity; if (velocity > 0.0f) { velocity = 0.0f; } // Setup so that a drop height of BZDB_GRAVITY squishes // by a factor of 1/11, when BZDB_SQUISHFACTOR is set to 1 // // G = gravity; V = velocity; D = fall distance; K = factor // // V = sqrt (2 * D * G) // V = sqrt(2) * G { @ D = G) // scale = 1 / (1 + (K * V^2)) // scale = 1 / (1 + (K * 2 * G^2)) // set: (K * 2 * G^2) = 0.1 // K = 0.1 / (2 * G^2) // float k = 0.1f / (2.0f * gravity * gravity); k = k * squishiness; if (flagType == Flags::Bouncy) { k = k * 4.0f; } const float newZscale = 1.0f / (1.0f + (k * (velocity * velocity))); // don't update if the tank is still recovering // from a spawn effect or a larger fall. if (newZscale < dimensionsScale[2]) { dimensionsScale[2] = newZscale; // use a fixed decompression rate dimensionsRate[2] = 1.0f / squishTime; } return; } void Player::spawnEffect() { const float squishiness = BZDB.eval(StateDatabase::BZDB_SQUISHFACTOR); if (squishiness > 0.0f) { const float effectTime = BZDB.eval(StateDatabase::BZDB_FLAGEFFECTTIME); const float factor = 1.0f / effectTime; for (int i = 0; i < 3; i++) { dimensionsRate[i] = factor; dimensionsScale[i] = 0.01f; } } spawnTime = TimeKeeper::getCurrent(); return; } int Player::getMaxShots() const { return World::getWorld()->getMaxShots(); } void Player::addShots(SceneDatabase* scene, bool colorblind) const { const int count = getMaxShots(); for (int i = 0; i < count; i++) { ShotPath* shot = getShot(i); if (shot && !shot->isExpiring() && !shot->isExpired()) shot->addShot(scene, colorblind); } } void* Player::unpack(void* buf, uint16_t code) { float timestamp; PlayerId ident; buf = nboUnpackFloat(buf, timestamp); buf = nboUnpackUByte(buf, ident); buf = state.unpack(buf, code); setDeadReckoning(timestamp); setRelativeMotion(); return buf; } bool Player::validTeamTarget(const Player *possibleTarget) const { TeamColor myTeam = getTeam(); TeamColor targetTeam = possibleTarget->getTeam(); if (myTeam != targetTeam) { return true; } if (myTeam != RogueTeam) { return false; } return !World::getWorld()->allowRabbit(); } void Player::getDeadReckoning(float* predictedPos, float* predictedAzimuth, float* predictedVel, float dt) const { *predictedAzimuth = inputAzimuth; if (inputStatus & PlayerState::Paused) { // don't move when paused predictedPos[0] = inputPos[0]; predictedPos[1] = inputPos[1]; predictedPos[2] = inputPos[2]; predictedVel[0] = 0.0f; predictedVel[1] = 0.0f; predictedVel[2] = 0.0f; } else if (inputStatus & PlayerState::Falling) { // no control when falling predictedVel[0] = inputVel[0]; predictedVel[1] = inputVel[1]; predictedPos[0] = inputPos[0] + (dt * inputVel[0]); predictedPos[1] = inputPos[1] + (dt * inputVel[1]); // only turn if alive if (inputStatus & PlayerState::Alive) { *predictedAzimuth += (dt * inputAngVel); } // following the parabola predictedVel[2] = inputVel[2] + (BZDBCache::gravity * dt); predictedPos[2] = inputPos[2] + (inputVel[2] * dt) + (0.5f * BZDBCache::gravity * dt * dt); } else { // velocity[2] is zero when not falling, except for Burrow flag predictedVel[2] = inputVel[2]; predictedPos[2] = inputPos[2] + (inputVel[2] * dt); // different algorithms for tanks moving in // a straight line vs. turning in a circle if (!inputTurning) { // move straight predictedVel[0] = inputRelVel[0]; predictedVel[1] = inputRelVel[1]; predictedPos[0] = inputPos[0] + (dt * inputRelVel[0]); predictedPos[1] = inputPos[1] + (dt * inputRelVel[1]); } else { // make a sweeping arc const float angle = (dt * inputRelAngVel); *predictedAzimuth += angle; const float cos_val = cosf(angle); const float sin_val = sinf(angle); const float* tc = inputTurnCenter; const float* tv = inputTurnVector; predictedPos[0] = tc[0] + ((tv[0] * cos_val) - (tv[1] * sin_val)); predictedPos[1] = tc[1] + ((tv[1] * cos_val) + (tv[0] * sin_val)); const float* rv = inputRelVel; predictedVel[0] = (rv[0] * cos_val) - (rv[1] * sin_val); predictedVel[1] = (rv[1] * cos_val) + (rv[0] * sin_val); } // make the physics driver adjustments const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(inputPhyDrv); if (phydrv != NULL) { if (phydrv->getIsSlide()) { predictedVel[0] = inputRelVel[0]; predictedVel[1] = inputRelVel[1]; predictedPos[0] = inputPos[0] + (dt * inputRelVel[0]); predictedPos[1] = inputPos[1] + (dt * inputRelVel[1]); } else { // angular velocity adjustment const float pdAngVel = phydrv->getAngularVel(); if (pdAngVel != 0.0f) { const float angle = (dt * pdAngVel); *predictedAzimuth += angle; const float* pdAngPos = phydrv->getAngularPos(); const float dx = predictedPos[0] - pdAngPos[0]; const float dy = predictedPos[1] - pdAngPos[1]; const float cos_val = cosf(angle); const float sin_val = sinf(angle); predictedPos[0] = pdAngPos[0] + ((dx * cos_val) - (dy * sin_val)); predictedPos[1] = pdAngPos[1] + ((dy * cos_val) + (dx * sin_val)); predictedVel[0] += (-dy * pdAngVel); predictedVel[1] += (+dx * pdAngVel); } // linear velocity adjustment const float* pdVel = phydrv->getLinearVel(); predictedPos[0] += (dt * pdVel[0]); predictedPos[1] += (dt * pdVel[1]); predictedVel[0] += pdVel[0]; predictedVel[1] += pdVel[1]; } } } return; } bool Player::isDeadReckoningWrong() const { const uint16_t checkStates = (PlayerState::Alive | PlayerState::Paused | PlayerState::Falling); // always send a new packet when some kinds of status change if ((state.status & checkStates) != (inputStatus & checkStates)) { return true; } // never send a packet when dead if ((state.status & PlayerState::Alive) == 0) { return false; } // send a packet if we've made some noise if (state.sounds != PlayerState::NoSounds) { return true; } // send a packet if we've crossed a physics driver boundary if (state.phydrv != inputPhyDrv) { return true; } // time since setdeadreckoning const float dt = float(TimeKeeper::getTick() - inputTime); // otherwise always send at least one packet per second if (dt >= MaxUpdateTime) { return true; } // get predicted state float predictedPos[3]; float predictedVel[3]; float predictedAzimuth; getDeadReckoning(predictedPos, &predictedAzimuth, predictedVel, dt); // always send a new packet on reckoned touchdown float groundLimit = 0.0f; if (getFlag() == Flags::Burrow) { groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH); } if (predictedPos[2] < groundLimit) { return true; } // client side throttling const int throttleRate = int(BZDB.eval(StateDatabase::BZDB_UPDATETHROTTLERATE)); const float minUpdateTime = (throttleRate > 0) ? (1.0f / throttleRate) : 0.0f; if (dt < minUpdateTime) { return false; } // see if position and azimuth are close enough float positionTolerance = BZDB.eval(StateDatabase::BZDB_POSITIONTOLERANCE); if ((fabsf(state.pos[0] - predictedPos[0]) > positionTolerance) || (fabsf(state.pos[1] - predictedPos[1]) > positionTolerance) || (fabsf(state.pos[2] - predictedPos[2]) > positionTolerance)) { if (debugLevel >= 4) { if (fabsf(state.pos[0] - predictedPos[0]) > positionTolerance) { printf ("state.pos[0] = %f, predictedPos[0] = %f\n", state.pos[0], predictedPos[0]); } if (fabsf(state.pos[1] - predictedPos[1]) > positionTolerance) { printf ("state.pos[1] = %f, predictedPos[1] = %f\n", state.pos[1], predictedPos[1]); } if (fabsf(state.pos[2] - predictedPos[2]) > positionTolerance) { printf ("state.pos[2] = %f, predictedPos[2] = %f\n", state.pos[2], predictedPos[2]); } } return true; } float angleTolerance = BZDB.eval(StateDatabase::BZDB_ANGLETOLERANCE); if (fabsf(state.azimuth - predictedAzimuth) > angleTolerance) { logDebugMessage(4,"state.azimuth = %f, predictedAzimuth = %f\n", state.azimuth, predictedAzimuth); return true; } // prediction is good enough return false; } void Player::doDeadReckoning() { if (!isAlive() && !isExploding()) { return; } // get predicted state float predictedPos[3]; float predictedVel[3]; float predictedAzimuth; float dt = float(TimeKeeper::getTick() - inputTime); getDeadReckoning(predictedPos, &predictedAzimuth, predictedVel, dt); // setup notResponding if (!isAlive()) { notResponding = false; } else { notResponding = (dt > BZDB.eval(StateDatabase::BZDB_NOTRESPONDINGTIME)); } // if hit ground then update input state (we don't want to fall anymore) float groundLimit = 0.0f; if (getFlag() == Flags::Burrow) { groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH); } // the velocity check is for when a Burrow flag is dropped if ((predictedPos[2] < groundLimit) && (predictedVel[2] <= 0.0f)) { predictedPos[2] = groundLimit; predictedVel[2] = 0.0f; inputStatus &= ~PlayerState::Falling; inputVel[2] = 0.0f; } // setup remote players' landing sounds and graphics, and jumping sounds if (isAlive()) { // the importance level of the remote sounds const bool soundImportance = false; const bool localSound = (ROAM.isRoaming() && (ROAM.getMode() == Roaming::roamViewFP) && (ROAM.getTargetTank() == this)); // check for a landing if (((oldStatus & PlayerState::Falling) != 0) && ((inputStatus & PlayerState::Falling) == 0)) { // setup the squish effect setLandingSpeed(oldZSpeed); // make it "land" EFFECTS.addLandEffect(getColor(),state.pos,state.azimuth); // setup the sound if (BZDB.isTrue("remoteSounds")) { if ((getFlag() != Flags::Burrow) || (predictedPos[2] > 0.0f)) { playSound(SFX_LAND, state.pos, soundImportance, localSound); } else { // probably never gets played playSound(SFX_BURROW, state.pos, soundImportance, localSound); } } } // play jumping type sounds, and then clear them if (state.sounds != PlayerState::NoSounds) { if (BZDB.isTrue("remoteSounds")) { if ((state.sounds & PlayerState::JumpSound) != 0) { playSound(SFX_JUMP, state.pos, soundImportance, localSound); } if ((state.sounds & PlayerState::WingsSound) != 0) { playSound(SFX_FLAP, state.pos, soundImportance, localSound); } if ((state.sounds & PlayerState::BounceSound) != 0) { playSound(SFX_BOUNCE, state.pos, soundImportance, localSound); } } state.sounds = PlayerState::NoSounds; } } // copy some old state oldZSpeed = state.velocity[2]; oldStatus = inputStatus; move(predictedPos, predictedAzimuth); setVelocity(predictedVel); setRelativeMotion(); return; } // How long does the filter takes to be considered "initialized" const int DRStateStable = 100; const float maxToleratedJitter = 1.0f; void Player::setDeadReckoning(float) { // set the current state setDeadReckoning(); setRelativeMotion(); return; } void Player::setDeadReckoning() { inputTime = TimeKeeper::getTick(); // copy stuff for dead reckoning inputStatus = state.status; inputAzimuth = state.azimuth; inputAngVel = state.angVel; memcpy(inputPos, state.pos, sizeof(float[3])); memcpy(inputVel, state.velocity, sizeof(float[3])); inputPhyDrv = state.phydrv; // // pre-calculate some stuff for dead reckoning // // the relative motion information (with respect to the physics drivers) calcRelativeMotion(inputRelVel, inputRelSpeed, inputRelAngVel); // setup the turning parameters inputTurning = false; if (fabsf(inputRelAngVel) > 0.001f) { inputTurning = true; const float radius = (inputRelSpeed / inputRelAngVel); inputTurnVector[0] = +sinf(inputAzimuth) * radius; inputTurnVector[1] = -cosf(inputAzimuth) * radius; inputTurnCenter[0] = inputPos[0] - inputTurnVector[0]; inputTurnCenter[1] = inputPos[1] - inputTurnVector[1]; } return; } void Player::renderRadar() const { if (!isAlive() && !isExploding()) { return; // don't draw anything } if (tankNode) { ((TankSceneNode*)tankNode)->renderRadar(); } return; } bool Player::getIpAddress(Address& addr) { if (!haveIpAddr) { return false; } addr = ipAddr; return true; } void Player::setIpAddress(const Address& addr) { ipAddr = addr; haveIpAddr = true; } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ // interface header #include "playing.h" // system includes #ifdef _WIN32 #include #include #include #include #else #include #include #include #include #include #endif #include // common headers #include "AccessList.h" #include "AnsiCodes.h" #include "AresHandler.h" #include "BackgroundRenderer.h" #include "BaseBuilding.h" #include "BillboardSceneNode.h" #include "BZDBCache.h" #include "BzfMedia.h" #include "bzsignal.h" #include "CommandsStandard.h" #include "DirectoryNames.h" #include "ErrorHandler.h" #include "FileManager.h" #include "FlagSceneNode.h" #include "GameTime.h" #include "KeyManager.h" #include "md5.h" #include "ObstacleList.h" #include "ObstacleMgr.h" #include "PhysicsDriver.h" #include "PlatformFactory.h" #include "QuadWallSceneNode.h" #include "ServerList.h" #include "SphereSceneNode.h" #include "TankGeometryMgr.h" #include "TextureManager.h" #include "TextUtils.h" #include "TimeBomb.h" #include "version.h" #include "WordFilter.h" #include "ZSceneDatabase.h" // local implementation headers #include "AutoPilot.h" #include "bzflag.h" #include "commands.h" #include "daylight.h" #include "Downloads.h" #include "effectsRenderer.h" #include "FlashClock.h" #include "ForceFeedback.h" #include "HUDDialogStack.h" #include "HUDRenderer.h" #include "MainMenu.h" #include "motd.h" #include "RadarRenderer.h" #include "Roaming.h" #include "RobotPlayer.h" #include "Roster.h" #include "SceneBuilder.h" #include "ScoreboardRenderer.h" #include "sound.h" #include "ShotStats.h" #include "TrackMarks.h" #include "World.h" #include "WorldBuilder.h" #include "HUDui.h" #include #include "messages.h" #ifdef HAVE_KRB5 #include "ClientAuthentication.h" #endif bool bCheatGravity = false; int killMe; static const float FlagHelpDuration = 60.0f; StartupInfo startupInfo; static MainMenu* mainMenu; ServerLink* serverLink = NULL; static World *world = NULL; static LocalPlayer *myTank = NULL; static BzfDisplay* display = NULL; MainWindow* mainWindow = NULL; static SceneRenderer* sceneRenderer = NULL; ControlPanel* controlPanel = NULL; static RadarRenderer* radar = NULL; HUDRenderer* hud = NULL; static ScoreboardRenderer* scoreboard = NULL; static SceneDatabaseBuilder* sceneBuilder = NULL; static Team* teams = NULL; int numFlags = 0; static bool joinRequested = false; static bool waitingDNS = false; static bool serverError = false; static bool serverDied = false; bool fireButton = false; bool roamButton = false; static bool firstLife = false; static bool showFPS = false; static bool showDrawTime = false; bool pausedByUnmap = false; static bool unmapped = false; static int preUnmapFormat = -1; static double epochOffset; static double lastEpochOffset; float clockAdjust = 0.0f; float pauseCountdown = 0.0f; float destructCountdown = 0.0f; static float testVideoFormatTimer = 0.0f; static int testVideoPrevFormat = -1; static std::vector playingCallbacks; bool gameOver = false; static std::vector explosions; static std::vector prototypeExplosions; int savedVolume = -1; static bool grabMouseAlways = false; static FlashClock pulse; static bool wasRabbit = false; static bool justJoined = false; float roamDZoom = 0.0f; static MessageOfTheDay *motd = NULL; DefaultCompleter completer; char messageMessage[PlayerIdPLen + MessageLen]; double lastObserverUpdateTime = -1; static void setHuntTarget(); static void setTankFlags(); static void* handleMsgSetVars(void *msg); static void handlePlayerMessage(uint16_t, uint16_t, void*); static void handleFlagTransferred(Player* fromTank, Player* toTank, int flagIndex); static void enteringServer(void *buf); static void joinInternetGame2(); static void cleanWorldCache(); static void markOld(std::string &fileName); static void setRobotTarget(RobotPlayer* robot); static ResourceGetter *resourceDownloader = NULL; // Far and Near Frustum clipping planes static const float FarPlaneScale = 1.5f; // gets multiplied by BZDB_WORLDSIZE static const float FarPlaneDefault = FarPlaneScale * 800.0f; static const float FarDeepPlaneScale = 10.0f; static const float FarDeepPlaneDefault = FarPlaneDefault * FarDeepPlaneScale; static const float NearPlaneNormal = 1.0f; static const float NearPlaneClose = 0.25f; // for drawing in the cockpit static bool FarPlaneCull = false; static float FarPlane = FarPlaneDefault; static float FarDeepPlane = FarDeepPlaneDefault; static float NearPlane = NearPlaneNormal; enum BlowedUpReason { GotKilledMsg, GotShot, GotRunOver, GotCaptured, GenocideEffect, SelfDestruct, WaterDeath, LastReason, DeathTouch = PhysicsDriverDeath }; static const char* blowedUpMessage[] = { NULL, "Got shot by ", "Got flattened by ", "Team flag was captured by ", "Teammate hit with Genocide by ", "Tank Self Destructed", "Tank Rusted" }; static bool gotBlowedUp(BaseLocalPlayer* tank, BlowedUpReason reason, PlayerId killer, const ShotPath *hit = NULL, int physicsDriver = -1); #ifdef ROBOT static void handleMyTankKilled(int reason); static ServerLink* robotServer[MAX_ROBOTS]; #endif bool bCheatOO = false; static double userTimeEpochOffset; static bool entered = false; static bool joiningGame = false; static WorldBuilder *worldBuilder = NULL; static std::string worldUrl; static std::string worldCachePath; static std::string md5Digest; static uint32_t worldPtr = 0; static char *worldDatabase = NULL; static bool isCacheTemp; static std::ostream *cacheOut = NULL; static bool downloadingInitialTexture = false; static AresHandler ares(0); static AccessList ServerAccessList("ServerAccess.txt", NULL); // access silencePlayers from bzflag.cxx std::vector& getSilenceList() { return silencePlayers; } // try to select the next recipient in the specified direction // eventually avoiding robots void selectNextRecipient (bool forward, bool robotIn) { LocalPlayer *my = LocalPlayer::getMyTank(); const Player *recipient = my->getRecipient(); int rindex; if (!recipient) { rindex = - 1; forward = true; } else { const PlayerId id = recipient->getId(); rindex = lookupPlayerIndex(id); } int i = rindex; while (true) { if (forward) { i++; if (i == curMaxPlayers) // if no old rec id we have just ended our search if (recipient == NULL) break; else // wrap around i = 0; } else { if (i == 0) // wrap around i = curMaxPlayers; i--; } if (i == rindex) break; if (player[i] && (robotIn || player[i]->getPlayerType() == TankPlayer)) { my->setRecipient(player[i]); break; } } } // // should we grab the mouse? // static void setGrabMouse(bool grab) { grabMouseAlways = grab; } bool shouldGrabMouse() { return grabMouseAlways && !unmapped && (myTank == NULL || !myTank->isPaused() || myTank->isAutoPilot()); } // // some simple global functions // void warnAboutMainFlags() { // warning message for hidden flags if (!BZDBCache::displayMainFlags){ std::string showFlagsMsg = ColorStrings[YellowColor]; showFlagsMsg += "Flags on field hidden, to show them "; std::vector keys = KEYMGR.getKeysFromCommand("toggleFlags main", true); if (keys.size() != 0) { showFlagsMsg += "hit \"" + ColorStrings[WhiteColor]; showFlagsMsg += tolower(keys[0][0]); showFlagsMsg += ColorStrings[YellowColor] + "\""; } else { showFlagsMsg += " bind a key to Toggle Flags on Field"; } addMessage(NULL, showFlagsMsg); } } void warnAboutRadarFlags() { if (!BZDB.isTrue("displayRadarFlags")){ std::string showFlagsMsg = ColorStrings[YellowColor]; showFlagsMsg += "Flags on radar hidden, to show them "; std::vector keys = KEYMGR.getKeysFromCommand("toggleFlags radar", true); if (keys.size() != 0) { showFlagsMsg += "hit \"" + ColorStrings[WhiteColor]; showFlagsMsg += tolower(keys[0][0]); showFlagsMsg += ColorStrings[YellowColor] + "\""; } else { showFlagsMsg += " bind a key to Toggle Flags on Radar"; } addMessage(NULL, showFlagsMsg); } } void warnAboutRadar() { if (!BZDB.isTrue("displayRadar")) { std::string message = ColorStrings[YellowColor]; message += "To toggle the radar "; std::vector keys = KEYMGR.getKeysFromCommand("toggleRadar", true); if (keys.size() != 0) { message += "hit \"" + ColorStrings[WhiteColor]; message += keys[0]; message += ColorStrings[YellowColor] + "\""; } else { message += " bind a key to Toggle Radar"; } addMessage(NULL, message); } } void warnAboutConsole() { if (!BZDB.isTrue("displayConsole")) { std::string message = ColorStrings[YellowColor]; message += "To toggle the console "; std::vector keys = KEYMGR.getKeysFromCommand("toggleConsole", true); if (keys.size() != 0) { message += "hit \"" + ColorStrings[WhiteColor]; message += keys[0]; message += ColorStrings[YellowColor] + "\""; } else { message += " bind a key to Toggle Console"; } // can't use a console message for this one hud->setAlert(3, message.c_str(), 2.0f, true); } } inline bool isViewTank(Player* tank) { return ((tank != NULL) && (tank == LocalPlayer::getMyTank()) || (ROAM.isRoaming() && (ROAM.getMode() == Roaming::roamViewFP) && (ROAM.getTargetTank() == tank))); } BzfDisplay* getDisplay() { return display; } MainWindow* getMainWindow() { return mainWindow; } SceneRenderer* getSceneRenderer() { return sceneRenderer; } void setSceneDatabase() { SceneDatabase *scene; // FIXME - test the zbuffer here // delete the old database sceneRenderer->setSceneDatabase(NULL); // make the scene, and record the processing time TimeKeeper startTime = TimeKeeper::getCurrent(); scene = sceneBuilder->make(world); float elapsed = float(TimeKeeper::getCurrent() - startTime); // print debugging info if (BZDBCache::zbuffer) { logDebugMessage(2,"ZSceneDatabase processed in %.3f seconds.\n", elapsed); } else { logDebugMessage(2,"BSPSceneDatabase processed in %.3f seconds.\n", elapsed); } // set the scene sceneRenderer->setSceneDatabase(scene); } StartupInfo* getStartupInfo() { return &startupInfo; } bool setVideoFormat(int index, bool test) { #if defined(_WIN32) // give windows extra time to test format (context reloading takes a while) static const float testDuration = 10.0f; #else static const float testDuration = 5.0f; #endif // ignore bad formats or when the format test timer is running if (testVideoFormatTimer != 0.0f || !display->isValidResolution(index)) return false; // ignore if no change if (display->getResolution() == index) return true; // change it testVideoPrevFormat = display->getResolution(); if (!display->setResolution(index)) return false; // handle resize mainWindow->setFullscreen(); mainWindow->getWindow()->callResizeCallbacks(); mainWindow->warpMouse(); if (test) testVideoFormatTimer = testDuration; else if (shouldGrabMouse()) mainWindow->grabMouse(); return true; } void addPlayingCallback(PlayingCallback cb, void* data) { PlayingCallbackItem item; item.cb = cb; item.data = data; playingCallbacks.push_back(item); } void removePlayingCallback(PlayingCallback _cb, void* data) { std::vector::iterator it = playingCallbacks.begin(); while (it != playingCallbacks.end()) { if (it->cb == _cb && it->data == data) { playingCallbacks.erase(it); break; } it++; } } static void callPlayingCallbacks() { const int count = playingCallbacks.size(); for (int i = 0; i < count; i++) { const PlayingCallbackItem& cb = playingCallbacks[i]; (*cb.cb)(cb.data); } } void joinGame() { if (joiningGame) { if (worldBuilder) { delete worldBuilder; worldBuilder = NULL; } if (worldDatabase) { delete[] worldDatabase; worldDatabase = NULL; } HUDDialogStack::get()->setFailedMessage("Download stopped by user action"); joiningGame = false; } joinRequested = true; } // // handle signals that should kill me quickly // static void dying(int sig) { bzSignal(sig, SIG_DFL); display->setDefaultResolution(); raise(sig); } // // handle signals that should kill me nicely // static void suicide(int sig) { bzSignal(sig, SIG_PF(suicide)); CommandsStandard::quit(); } // // handle signals that should disconnect me from the server // static void hangup(int sig) { bzSignal(sig, SIG_PF(hangup)); serverDied = true; serverError = true; } static ServerLink* lookupServer(const Player *_player) { PlayerId id = _player->getId(); if (myTank->getId() == id) return serverLink; #ifdef ROBOT for (int i = 0; i < numRobots; i++) if (robots[i] && robots[i]->getId() == id) return robotServer[i]; #endif return NULL; } // // user input handling // #if defined(DEBUG) #define FREEZING #endif #if defined(FREEZING) static bool motionFreeze = false; #endif static enum {None, Left, Right, Up, Down} keyboardMovement; static int shiftKeyStatus; static bool doKeyCommon(const BzfKeyEvent& key, bool pressed) { keyboardMovement = None; shiftKeyStatus = key.shift; const std::string cmd = KEYMGR.get(key, pressed); if (key.ascii == 27) { if (pressed) { mainMenu->createControls(); HUDDialogStack::get()->push(mainMenu); } return true; } else if (scoreboard->getHuntState() == ScoreboardRenderer::HUNT_SELECTING) { if (key.button == BzfKeyEvent::Down || KEYMGR.get(key, true) == "identify") { if (pressed) scoreboard->setHuntNextEvent(); return true; } else if (key.button == BzfKeyEvent::Up || KEYMGR.get(key, true) == "drop") { if (pressed) scoreboard->setHuntPrevEvent(); return true; } else if (KEYMGR.get(key, true) == "fire") { if (pressed) { scoreboard->setHuntSelectEvent(); } return true; } } std::string cmdDrive = cmd; if (cmdDrive.empty()) { // Check for driving keys BzfKeyEvent cleanKey = key; cleanKey.shift = 0; cmdDrive = KEYMGR.get(cleanKey, pressed); } if (cmdDrive == "turn left") { keyboardMovement = Left; } else if (cmdDrive == "turn right") { keyboardMovement = Right; } else if (cmdDrive == "drive forward") { keyboardMovement = Up; } else if (cmdDrive == "drive reverse") { keyboardMovement = Down; } if (myTank) switch (keyboardMovement) { case Left: myTank->setKey(BzfKeyEvent::Left, pressed); break; case Right: myTank->setKey(BzfKeyEvent::Right, pressed); break; case Up: myTank->setKey(BzfKeyEvent::Up, pressed); break; case Down: myTank->setKey(BzfKeyEvent::Down, pressed); break; case None: break; } if (!cmd.empty()) { if (cmd=="fire") fireButton = pressed; roamButton = pressed; if (keyboardMovement == None) { std::string result = CMDMGR.run(cmd); if (!result.empty()) std::cerr << result << std::endl; } return true; } // if we don't have a tank, the following key commands don't apply if (!myTank) return false; { // built-in unchangeable keys. only perform if not masked. switch (key.ascii) { case 'T': case 't': // toggle frames-per-second display if (pressed) { showFPS = !showFPS; if (!showFPS) { hud->setFPS(-1.0); } } return true; case 'Y': case 'y': // toggle milliseconds for drawing if (pressed) { showDrawTime = !showDrawTime; if (!showDrawTime) { hud->setDrawTime(-1.0); } } return true; // for testing forced recreation of OpenGL context #if defined(DEBUG_RENDERING) case 'X': if (pressed && ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0)) { // destroy OpenGL context getMainWindow()->getWindow()->freeContext(); // recreate OpenGL context getMainWindow()->getWindow()->makeContext(); // force a redraw (mainly for control panel) getMainWindow()->getWindow()->callExposeCallbacks(); // cause sun/moon to be repositioned immediately lastEpochOffset = epochOffset - 5.0; // reload display lists and textures and initialize other state OpenGLGState::initContext(); } return true; #endif // DEBUG_RENDERING case ']': case '}': // plus 30 seconds if (pressed) clockAdjust += 30.0f; return true; case '[': case '{': // minus 30 seconds if (pressed) clockAdjust -= 30.0f; return true; default: break; } // end switch on key // Shot/Accuracy Statistics display if (key.button == BzfKeyEvent::Home && pressed) { HUDDialogStack::get()->push(new ShotStats); return true; } } // end key handle return false; } static void doKeyNotPlaying(const BzfKeyEvent&, bool, bool) { } static void doKeyPlaying(const BzfKeyEvent& key, bool pressed, bool haveBinding) { #if defined(FREEZING) if (key.ascii == '`' && pressed && !haveBinding && key.shift) { // toggle motion freeze motionFreeze = !motionFreeze; if (motionFreeze) { addMessage(NULL, "The tank's motion is now frozen! ... Press Shift+` to unfreeze"); } return; } #endif if (key.ascii == 0 && key.button >= BzfKeyEvent::F1 && key.button <= BzfKeyEvent::F10 && (key.shift & (BzfKeyEvent::ControlKey + BzfKeyEvent::AltKey)) != 0 && !haveBinding) { // [Ctrl]-[Fx] is message to team // [Alt]-[Fx] is message to all if (pressed) { char name[32]; int msgno = (key.button - BzfKeyEvent::F1) + 1; void* buf = messageMessage; if (key.shift == BzfKeyEvent::ControlKey) { sprintf(name, "quickTeamMessage%d", msgno); buf = nboPackUByte(buf, TeamToPlayerId(myTank->getTeam())); } else { sprintf(name, "quickMessage%d", msgno); buf = nboPackUByte(buf, AllPlayers); } if (BZDB.isSet(name)) { char messageBuffer[MessageLen]; memset(messageBuffer, 0, MessageLen); strncpy(messageBuffer, BZDB.get(name).c_str(), MessageLen); nboPackString(buf, messageBuffer, MessageLen); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); } } } else if (myTank->isAlive()) { // Might be a direction key. Save it for later. if ((myTank->getInputMethod() != LocalPlayer::Keyboard) && pressed) { if (keyboardMovement != None) if (BZDB.isTrue("allowInputChange")) myTank->setInputMethod(LocalPlayer::Keyboard); } } } static void doKey(const BzfKeyEvent& key, bool pressed) { if (myTank) { const std::string cmd = KEYMGR.get(key, pressed); if (cmd == "jump") { myTank->setJumpPressed(pressed); } } if (HUDui::getFocus()) { if ((pressed && HUDui::keyPress(key)) || (!pressed && HUDui::keyRelease(key))) { return; } } bool haveBinding = doKeyCommon(key, pressed); if (!myTank) doKeyNotPlaying(key, pressed, haveBinding); else doKeyPlaying(key, pressed, haveBinding); } static void doMotion() { float rotation = 0.0f, speed = 1.0f; const int noMotionSize = hud->getNoMotionSize(); const int maxMotionSize = hud->getMaxMotionSize(); int keyboardRotation = myTank->getRotation(); int keyboardSpeed = myTank->getSpeed(); /* see if controls are reversed */ if (myTank->getFlag() == Flags::ReverseControls) { keyboardRotation = -keyboardRotation; keyboardSpeed = -keyboardSpeed; } // mouse is default steering method; query mouse pos always, not doing so // can lead to stuttering movement with X and software rendering (uncertain why) int mx = 0, my = 0; mainWindow->getMousePosition(mx, my); // determine if joystick motion should be used instead of mouse motion // when the player bumps the mouse, LocalPlayer::getInputMethod return Mouse; // make it return Joystick when the user bumps the joystick if (mainWindow->haveJoystick()) { if (myTank->getInputMethod() == LocalPlayer::Joystick) { // if we're using the joystick right now, replace mouse coords with joystick coords mainWindow->getJoyPosition(mx, my); } else { // if the joystick is not active, and we're not forced to some other input method, // see if it's moved and autoswitch if (BZDB.isTrue("allowInputChange")) { int jx = 0, jy = 0; mainWindow->getJoyPosition(jx, jy); // if we aren't using the joystick, but it's moving, start using it if ((jx < -noMotionSize * 2) || (jx > noMotionSize * 2) || (jy < -noMotionSize * 2) || (jy > noMotionSize * 2)) { myTank->setInputMethod(LocalPlayer::Joystick); } // joystick motion } // allowInputChange } // getInputMethod == Joystick } // mainWindow->Joystick /* see if controls are reversed */ if (myTank->getFlag() == Flags::ReverseControls) { mx = -mx; my = -my; } #if defined(FREEZING) if (motionFreeze) return; #endif if (myTank->isAutoPilot()) { doAutoPilot(rotation, speed); } else if (myTank->getInputMethod() == LocalPlayer::Keyboard) { rotation = (float)keyboardRotation; speed = (float)keyboardSpeed; if (speed < 0.0f) speed /= 2.0; rotation *= BZDB.eval("displayFOV") / 60.0f; if (BZDB.isTrue("slowKeyboard")) { rotation *= 0.5f; speed *= 0.5f; } } else { // both mouse and joystick // calculate desired rotation if (keyboardRotation && !devDriving) { rotation = float(keyboardRotation); rotation *= BZDB.eval("displayFOV") / 60.0f; if (BZDB.isTrue("slowKeyboard")) { rotation *= 0.5f; } } else if (mx < -noMotionSize) { rotation = float(-mx - noMotionSize) / float(maxMotionSize - noMotionSize); if (rotation > 1.0f) rotation = 1.0f; } else if (mx > noMotionSize) { rotation = -float(mx - noMotionSize) / float(maxMotionSize - noMotionSize); if (rotation < -1.0f) rotation = -1.0f; } // calculate desired speed if (keyboardSpeed && !devDriving) { speed = float(keyboardSpeed); if (speed < 0.0f) { speed *= 0.5f; } if (BZDB.isTrue("slowKeyboard")) { speed *= 0.5f; } } else if (my < -noMotionSize) { speed = float(-my - noMotionSize) / float(maxMotionSize - noMotionSize); if (speed > 1.0f) speed = 1.0f; } else if (my > noMotionSize) { speed = -float(my - noMotionSize) / float(maxMotionSize - noMotionSize); if (speed < -0.5f) speed = -0.5f; } else { speed = 0.0f; } } myTank->setDesiredAngVel(rotation); myTank->setDesiredSpeed(speed); } static void doEvent(BzfDisplay *disply) { BzfEvent event; if (!disply->getEvent(event)) return; switch (event.type) { case BzfEvent::Quit: CommandsStandard::quit(); break; case BzfEvent::Redraw: mainWindow->getWindow()->callExposeCallbacks(); sceneRenderer->setExposed(); break; case BzfEvent::Resize: if (mainWindow->getWidth() != event.resize.width || mainWindow->getHeight() != event.resize.height){ mainWindow->getWindow()->setSize(event.resize.width, event.resize.height); mainWindow->getWindow()->callResizeCallbacks(); } break; case BzfEvent::Map: // window has been mapped. this normally occurs when the game // is uniconified. if the player was paused because of an unmap // then resume. if (pausedByUnmap) { pausedByUnmap = false; pauseCountdown = 0.0f; if (myTank && myTank->isAlive() && myTank->isPaused()) { myTank->setPause(false); controlPanel->addMessage("Resumed"); } } // restore the resolution we want if full screen if (mainWindow->getFullscreen()) { if (preUnmapFormat != -1) { disply->setResolution(preUnmapFormat); mainWindow->warpMouse(); } } // restore the sound if (savedVolume != -1) { setSoundVolume(savedVolume); savedVolume = -1; } unmapped = false; if (shouldGrabMouse()) mainWindow->grabMouse(); break; case BzfEvent::Unmap: // begin pause countdown when unmapped if: we're not already // paused because of an unmap (shouldn't happen), we're not // already counting down to pausing, we're alive, and we're not // already paused. if (!pausedByUnmap && (pauseCountdown == 0.0f) && myTank && myTank->isAlive() && !myTank->isPaused() && !myTank->isAutoPilot() && !BZDB.isTrue("noUnmapPause")) { // handy for testing // get ready to pause (no cheating through instantaneous pausing) pauseCountdown = 5.0f; // set this even though we haven't really paused yet pausedByUnmap = true; } // ungrab the mouse if we're running full screen if (mainWindow->getFullscreen()) { preUnmapFormat = -1; if (disply->getNumResolutions() > 1) { preUnmapFormat = disply->getResolution(); disply->setDefaultResolution(); } } // turn off the sound if (savedVolume == -1) { savedVolume = getSoundVolume(); setSoundVolume(0); } unmapped = true; mainWindow->ungrabMouse(); break; case BzfEvent::KeyUp: doKey(event.keyUp, false); break; case BzfEvent::KeyDown: doKey(event.keyDown, true); break; case BzfEvent::MouseMove: if (myTank && myTank->isAlive() && (myTank->getInputMethod() != LocalPlayer::Mouse) && (BZDB.isTrue("allowInputChange"))) myTank->setInputMethod(LocalPlayer::Mouse); break; default: /* unset */ break; } } void addMessage(const Player *_player, const std::string& msg, int mode, bool highlight, const char* oldColor) { std::string fullMessage; if (BZDB.isTrue("colorful")) { if (_player) { if (highlight) { if (BZDB.get("killerhighlight") == "1") fullMessage += ColorStrings[PulsatingColor]; else if (BZDB.get("killerhighlight") == "2") fullMessage += ColorStrings[UnderlineColor]; } const PlayerId pid = _player->getId(); if (pid < 200) { int color = _player->getTeam(); if (World::getWorld()->allowRabbit() && (color == RogueTeam)) { // hunters are orange (hack) color = OrangeColor; } else if (color < 0 || color > 4) { // non-teamed, rabbit are white (same as observer) color = WhiteColor; } fullMessage += ColorStrings[color]; } else if (pid == ServerPlayer) { fullMessage += ColorStrings[YellowColor]; } else { fullMessage += ColorStrings[CyanColor]; //replay observers } fullMessage += _player->getCallSign(); if (highlight) fullMessage += ColorStrings[ResetColor]; #ifdef BWSUPPORT fullMessage += " ("; fullMessage += Team::getName(_player->getTeam()); fullMessage += ")"; #endif fullMessage += ColorStrings[DefaultColor] + ": "; } fullMessage += msg; } else { if (oldColor != NULL) fullMessage = oldColor; if (_player) { fullMessage += _player->getCallSign(); #ifdef BWSUPPORT fullMessage += " ("; fullMessage += Team::getName(_player->getTeam()); fullMessage += ")"; #endif fullMessage += ": "; } fullMessage += stripAnsiCodes(msg); } controlPanel->addMessage(fullMessage, mode); } static void updateNumPlayers() { int i, numPlayers[NumTeams]; for (i = 0; i < NumTeams; i++) numPlayers[i] = 0; for (i = 0; i < curMaxPlayers; i++) if (player[i]) numPlayers[player[i]->getTeam()]++; if (myTank) numPlayers[myTank->getTeam()]++; } static void updateHighScores() { /* check scores to see if my team and/or have the high score. change * `>= bestScore' to `> bestScore' if you want to share the number * one spot. */ bool anyPlayers = false; int i; for (i = 0; i < curMaxPlayers; i++) if (player[i]) { anyPlayers = true; break; } #ifdef ROBOT if (!anyPlayers) { for (i = 0; i < numRobots; i++) if (robots[i]) { anyPlayers = true; break; } } #endif if (!anyPlayers) { hud->setPlayerHasHighScore(false); hud->setTeamHasHighScore(false); return; } bool haveBest = true; int bestScore = myTank ? myTank->getScore() : 0; for (i = 0; i < curMaxPlayers; i++) if (player[i] && player[i]->getScore() >= bestScore) { haveBest = false; break; } #ifdef ROBOT if (haveBest) { for (i = 0; i < numRobots; i++) if (robots[i] && robots[i]->getScore() >= bestScore) { haveBest = false; break; } } #endif hud->setPlayerHasHighScore(haveBest); if (myTank && Team::isColorTeam(myTank->getTeam())) { const Team& myTeam = World::getWorld()->getTeam(int(myTank->getTeam())); bestScore = myTeam.won - myTeam.lost; haveBest = true; for (i = 0; i < NumTeams; i++) { if (i == int(myTank->getTeam())) continue; const Team& team = World::getWorld()->getTeam(i); if (team.size > 0 && team.won - team.lost >= bestScore) { haveBest = false; break; } } hud->setTeamHasHighScore(haveBest); } else { hud->setTeamHasHighScore(false); } } static void updateFlag(FlagType* flag) { if (flag == Flags::Null) { hud->setColor(1.0f, 0.625f, 0.125f); hud->setAlert(2, NULL, 0.0f); } else { const float* color = flag->getColor(); hud->setColor(color[0], color[1], color[2]); hud->setAlert(2, flag->flagName, 3.0f, flag->endurance == FlagSticky); } if (BZDB.isTrue("displayFlagHelp")) hud->setFlagHelp(flag, FlagHelpDuration); if (!radar && !myTank || !World::getWorld()) return; radar->setJammed(flag == Flags::Jamming); hud->setAltitudeTape(flag == Flags::Jumping || World::getWorld()->allowJumping()); } void notifyBzfKeyMapChanged() { std::string restartLabel = "Right Mouse"; std::vector keys = KEYMGR.getKeysFromCommand("restart", false); if (keys.size() == 0) { // found nothing on down binding, so try up keys = KEYMGR.getKeysFromCommand("identify", true); if (keys.size() == 0) { std::cerr << "There does not appear to be any key bound to enter the game" << std::endl; } } if (keys.size() >= 1) { // display single letter keys as a quoted lowercase letter if (keys[0].size() == 1) { restartLabel = '\"'; restartLabel += tolower(keys[0][0]); restartLabel += '\"'; } else { restartLabel = keys[0]; } } // only show the first 2 keys found to keep things simple if (keys.size() > 1) { restartLabel.append(" or "); // display single letter keys as quoted lowercase letter if (keys[1].size() == 1) { restartLabel += '\"'; restartLabel += tolower(keys[1][0]); restartLabel += '\"'; } else { restartLabel.append(keys[1]); } } hud->setRestartKeyLabel(restartLabel); } // // server message handling // static Player* addPlayer(PlayerId id, void* msg, int showMessage) { uint16_t team, type, wins, losses, tks; char callsign[CallSignLen]; char email[EmailLen]; msg = nboUnpackUShort (msg, type); msg = nboUnpackUShort (msg, team); msg = nboUnpackUShort (msg, wins); msg = nboUnpackUShort (msg, losses); msg = nboUnpackUShort (msg, tks); msg = nboUnpackString (msg, callsign, CallSignLen); msg = nboUnpackString (msg, email, EmailLen); // Strip any ANSI color codes strncpy (callsign, stripAnsiCodes (std::string (callsign)).c_str (), 32); // id is slot, check if it's empty const int i = id; // sanity check if (i < 0) { printError (TextUtils::format ("Invalid player identification (%d)", i)); std:: cerr << "WARNING: invalid player identification when adding player with id " << i << std::endl; return NULL; } if (player[i]) { // we're not in synch with server -> help! not a good sign, but not fatal. printError ("Server error when adding player, player already added"); std::cerr << "WARNING: player already exists at location with id " << i << std::endl; return NULL; } if (i >= curMaxPlayers) { curMaxPlayers = i + 1; World::getWorld ()->setCurMaxPlayers (curMaxPlayers); } // add player if (PlayerType (type) == TankPlayer || PlayerType (type) == ComputerPlayer) { player[i] = new RemotePlayer (id, TeamColor (team), callsign, email, PlayerType (type)); player[i]->changeScore (short (wins), short (losses), short (tks)); } #ifdef ROBOT if (PlayerType (type) == ComputerPlayer) for (int j = 0; j < numRobots; j++) if (robots[j] && !strncmp (robots[j]->getCallSign (), callsign, CallSignLen)) { robots[j]->setTeam (TeamColor (team)); break; } #endif // show the message if we don't have the playerlist // permission. if * we do, MsgAdminInfo should arrive // with more info. if (showMessage && !myTank->hasPlayerList ()) { std::string message ("joining as "); if (team == ObserverTeam) { message += "an observer"; } else { switch (PlayerType (type)) { case TankPlayer: message += "a tank"; break; case ComputerPlayer: message += "a robot tank"; break; default: message += "an unknown type"; break; } } if (!player[i]) { std::string name (callsign); name += ": " + message; message = name; } addMessage (player[i], message); } completer.registerWord(callsign, true /* quote spaces */); return player[i]; } static void printIpInfo (const Player *_player, const Address& addr, const std::string note) { if (_player == NULL) { return; } std::string colorStr; if (_player->getId() < 200) { int color = _player->getTeam(); if (World::getWorld()->allowRabbit() && (color == RogueTeam)) { // hunters are orange (hack) color = OrangeColor; } else if (color < 0 || color > 4) { // non-teamed, rabbit are white (same as observer) color = WhiteColor; } colorStr = ColorStrings[color]; } else { colorStr = ColorStrings[CyanColor]; // replay observers } const std::string addrStr = addr.getDotNotation(); std::string message = ColorStrings[CyanColor]; // default color message += "IPINFO: "; if (BZDBCache::colorful) message += colorStr; message += _player->getCallSign(); if (BZDBCache::colorful) message += ColorStrings[CyanColor]; message += "\t from: "; if (BZDBCache::colorful) message += colorStr; message += addrStr; message += ColorStrings[WhiteColor]; for (int i = 0; i < (17 - (int)addrStr.size()); i++) { message += " "; } message += note; // print into the Server Menu controlPanel->addMessage(message, 2); return; } static bool removePlayer (PlayerId id) { int playerIndex = lookupPlayerIndex(id); if (playerIndex < 0) { return false; } Player* p = player[playerIndex]; Address addr; std::string msg = "signing off"; if (!p->getIpAddress(addr)) { addMessage(p, "signing off"); } else { msg += " from "; msg += addr.getDotNotation(); p->setIpAddress(addr); addMessage(p, msg); if (BZDB.evalInt("showips") > 1) { printIpInfo (p, addr, "(leave)"); } } if (myTank->getRecipient() == p) myTank->setRecipient(0); if (myTank->getNemesis() == p) myTank->setNemesis(0); completer.unregisterWord(p->getCallSign()); delete player[playerIndex]; player[playerIndex] = NULL; while ((playerIndex >= 0) && (playerIndex+1 == curMaxPlayers) && (player[playerIndex] == NULL)) { playerIndex--; curMaxPlayers--; } World::getWorld()->setCurMaxPlayers(curMaxPlayers); updateNumPlayers(); return true; } static bool isCached(char *hexDigest) { std::istream *cachedWorld; bool cached = false; worldCachePath = getCacheDirName(); worldCachePath += hexDigest; worldCachePath += ".bwc"; if ((cachedWorld = FILEMGR.createDataInStream(worldCachePath, true))) { cached = true; delete cachedWorld; } return cached; } int curlProgressFunc(void* /*clientp*/, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/) { // FIXME: beaucoup cheeze here in the aborting style // we should be using async dns and multi-curl // abort the download? BzfEvent event; if (display->isEventPending()) { if (display->peekEvent(event)) { switch (event.type) { case BzfEvent::Quit: return 1; // terminate the curl call case BzfEvent::KeyDown: display->getEvent(event); // flush the event if (event.keyDown.ascii == 27) { return 1; // terminate the curl call } break; case BzfEvent::KeyUp: display->getEvent(event); // flush the event break; case BzfEvent::MouseMove: display->getEvent(event); // flush the event break; case BzfEvent::Unset: case BzfEvent::Map: case BzfEvent::Unmap: case BzfEvent::Redraw: case BzfEvent::Resize: // leave the event, it might be important break; } } } // update the status double percentage = 0.0; if ((int)dltotal > 0) { percentage = 100.0 * dlnow / dltotal; } char buffer[128]; sprintf (buffer, "%2.1f%% (%i/%i)", percentage, (int)dlnow, (int)dltotal); HUDDialogStack::get()->setFailedMessage(buffer); return 0; } static void loadCachedWorld() { // can't get a cache from nothing if (worldCachePath == std::string("")) { joiningGame = false; return; } // lookup the cached world std::istream *cachedWorld = FILEMGR.createDataInStream(worldCachePath, true); if (!cachedWorld) { HUDDialogStack::get()->setFailedMessage("World cache files disappeared. Join canceled"); drawFrame(0.0f); remove(worldCachePath.c_str()); joiningGame = false; return; } // status update HUDDialogStack::get()->setFailedMessage("Loading world into memory..."); drawFrame(0.0f); // get the world size cachedWorld->seekg(0, std::ios::end); std::streampos size = cachedWorld->tellg(); unsigned long charSize = std::streamoff(size); // load the cached world cachedWorld->seekg(0); char *localWorldDatabase = new char[charSize]; if (!localWorldDatabase) { HUDDialogStack::get()->setFailedMessage("Error loading cached world. Join canceled"); drawFrame(0.0f); remove(worldCachePath.c_str()); joiningGame = false; return; } cachedWorld->read(localWorldDatabase, charSize); delete cachedWorld; // verify HUDDialogStack::get()->setFailedMessage("Verifying world integrity..."); drawFrame(0.0f); MD5 md5; md5.update((unsigned char *)localWorldDatabase, charSize); md5.finalize(); std::string digest = md5.hexdigest(); if (digest != md5Digest) { if (worldBuilder) delete worldBuilder; worldBuilder = NULL; delete[] localWorldDatabase; HUDDialogStack::get()->setFailedMessage("Error on md5. Removing offending file."); remove(worldCachePath.c_str()); joiningGame = false; return; } // make world HUDDialogStack::get()->setFailedMessage("Preparing world..."); drawFrame(0.0f); if (!worldBuilder->unpack(localWorldDatabase)) { // world didn't make for some reason if (worldBuilder) delete worldBuilder; worldBuilder = NULL; delete[] localWorldDatabase; HUDDialogStack::get()->setFailedMessage("Error unpacking world database. Join canceled."); remove(worldCachePath.c_str()); joiningGame = false; return; } delete[] localWorldDatabase; // return world world = worldBuilder->getWorld(); if (worldBuilder) delete worldBuilder; worldBuilder = NULL; HUDDialogStack::get()->setFailedMessage("Downloading files..."); const bool doDownloads = BZDB.isTrue("doDownloads"); const bool updateDownloads = BZDB.isTrue("updateDownloads"); Downloads::startDownloads(doDownloads, updateDownloads, false); downloadingInitialTexture = true; } class WorldDownLoader : cURLManager { public: void start(char * hexDigest); private: void askToBZFS(); virtual void finalization(char *data, unsigned int length, bool good); }; void WorldDownLoader::start(char * hexDigest) { if (isCached(hexDigest)) { loadCachedWorld(); } else if (worldUrl.size()) { HUDDialogStack::get()->setFailedMessage (("Loading world from " + worldUrl).c_str()); setProgressFunction(curlProgressFunc, (char*)worldUrl.c_str()); setURL(worldUrl); addHandle(); worldUrl = ""; // clear the state } else { askToBZFS(); } } void WorldDownLoader::finalization(char *data, unsigned int length, bool good) { if (good) { worldDatabase = data; theData = NULL; MD5 md5; md5.update((unsigned char *)worldDatabase, length); md5.finalize(); std::string digest = md5.hexdigest(); if (digest != md5Digest) { HUDDialogStack::get()->setFailedMessage("Download from URL failed"); askToBZFS(); } else { std::ostream* cache = FILEMGR.createDataOutStream(worldCachePath, true, true); if (cache != NULL) { cache->write(worldDatabase, length); delete cache; loadCachedWorld(); } else { HUDDialogStack::get()->setFailedMessage("Problem writing cache"); askToBZFS(); } } } else { askToBZFS(); } } void WorldDownLoader::askToBZFS() { HUDDialogStack::get()->setFailedMessage("Downloading World..."); char message[MaxPacketLen]; // ask for world nboPackUInt(message, 0); serverLink->send(MsgGetWorld, sizeof(uint32_t), message); worldPtr = 0; if (cacheOut) delete cacheOut; cacheOut = FILEMGR.createDataOutStream(worldCachePath, true, true); } static WorldDownLoader *worldDownLoader; static void dumpMissingFlag(char *buf, uint16_t len) { int i; int nFlags = len/2; std::string flags; for (i = 0; i < nFlags; i++) { /* We can't use FlagType::unpack() here, since it counts on the * flags existing in our flag database. */ if (i) flags += ", "; flags += buf[0]; if (buf[1]) flags += buf[1]; buf += 2; } std::vector args; args.push_back(flags); HUDDialogStack::get()->setFailedMessage (TextUtils::format("Flags not supported by this client: {1}", &args).c_str()); } static bool processWorldChunk(void *buf, uint16_t len, int bytesLeft) { int totalSize = worldPtr + len + bytesLeft; int doneSize = worldPtr + len; if (cacheOut) cacheOut->write((char *)buf, len); HUDDialogStack::get()->setFailedMessage (TextUtils::format ("Downloading World (%2d%% complete/%d kb remaining)...", (100 * doneSize / totalSize), bytesLeft / 1024).c_str()); return bytesLeft == 0; } static void handleServerMessage(bool human, uint16_t code, uint16_t len, void* msg) { std::vector args; bool checkScores = false; static WordFilter *wordfilter = (WordFilter *)BZDB.getPointer("filter"); switch (code) { case MsgFetchResources: if (BZDB.isSet ("_noRemoteFiles") && BZDB.isTrue ("_noRemoteFiles")) { break; } else { uint16_t numItems; void *buf; buf = nboUnpackUShort (msg, numItems); // the type for (int i = 0; i < numItems; i++) { uint16_t itemType; char buffer[MessageLen]; uint16_t stringLen; trResourceItem item; buf = nboUnpackUShort (buf, itemType); item.resType = (teResourceType) itemType; // URL buf = nboUnpackUShort (buf, stringLen); buf = nboUnpackString (buf, buffer, stringLen); buffer[stringLen] = '\0'; item.URL = buffer; item.filePath = PlatformFactory::getMedia ()->getMediaDirectory() + DirectorySeparator; std::vector < std::string > temp = TextUtils::tokenize (item.URL, std::string ("/")); item.fileName = temp[temp.size () - 1]; item.filePath += item.fileName; std::string hostname; parseHostname (item.URL, hostname); if (authorizedServer (hostname)) { if (!resourceDownloader) resourceDownloader = new ResourceGetter; resourceDownloader->addResource (item); } } } break; case MsgCustomSound: // bail out if we don't want to do remote sounds if (BZDB.isSet ("_noRemoteSounds") && BZDB.isTrue ("_noRemoteSounds")) { break; } else { void *buf; char buffer[MessageLen]; uint16_t soundType; uint16_t stringLen; std::string soundName; buf = nboUnpackUShort (msg, soundType); // the type buf = nboUnpackUShort (buf, stringLen); // how long our str is buf = nboUnpackString (buf, buffer, stringLen); buffer[stringLen] = '\0'; soundName = buffer; if (soundType == LocalCustomSound) playLocalSound (soundName); } break; case MsgUDPLinkEstablished: // server got our initial UDP packet serverLink->enableOutboundUDP(); break; case MsgUDPLinkRequest: // we got server's initial UDP packet serverLink->confirmIncomingUDP(); break; case MsgSuperKill: printError("Server forced a disconnect"); serverError = true; break; case MsgAccept: break; case MsgReject: { void *buf; char buffer[MessageLen]; uint16_t rejcode; std::string reason; buf = nboUnpackUShort (msg, rejcode); // filler for now buf = nboUnpackString (buf, buffer, MessageLen); buffer[MessageLen - 1] = '\0'; reason = buffer; printError(reason); serverError = true; break; } case MsgNegotiateFlags: { if (len > 0) { dumpMissingFlag((char *)msg, len); break; } serverLink->send(MsgWantSettings, 0, NULL); break; } case MsgGameSettings: { if (worldBuilder) delete worldBuilder; worldBuilder = new WorldBuilder; worldBuilder->unpackGameSettings(msg); serverLink->send(MsgWantWHash, 0, NULL); HUDDialogStack::get()->setFailedMessage("Requesting World Hash..."); break; } case MsgCacheURL: { char *cacheURL = new char[len]; nboUnpackString(msg, cacheURL, len); worldUrl = cacheURL; delete [] cacheURL; break; } case MsgWantWHash: { char *hexDigest = new char[len]; nboUnpackString(msg, hexDigest, len); isCacheTemp = hexDigest[0] == 't'; md5Digest = &hexDigest[1]; worldDownLoader->start(hexDigest); delete [] hexDigest; break; } case MsgGetWorld: { // create world uint32_t bytesLeft; void *buf = nboUnpackUInt(msg, bytesLeft); bool last = processWorldChunk(buf, len - 4, bytesLeft); if (!last) { char message[MaxPacketLen]; // ask for next chunk worldPtr += len - 4; nboPackUInt(message, worldPtr); serverLink->send(MsgGetWorld, sizeof(uint32_t), message); break; } if (cacheOut) delete cacheOut; cacheOut = NULL; loadCachedWorld(); if (isCacheTemp) markOld(worldCachePath); break; } case MsgGameTime: { GameTime::unpack(msg); GameTime::update(); break; } case MsgTimeUpdate: { int32_t timeLeft; msg = nboUnpackInt(msg, timeLeft); hud->setTimeLeft(timeLeft); if (timeLeft == 0) { gameOver = true; myTank->explodeTank(); controlPanel->addMessage("Time Expired"); hud->setAlert(0, "Time Expired", 10.0f, true); #ifdef ROBOT for (int i = 0; i < numRobots; i++) if (robots[i]) robots[i]->explodeTank(); #endif } else if (timeLeft < 0) { hud->setAlert(0, "Game Paused", 10.0f, true); } break; } case MsgScoreOver: { // unpack packet PlayerId id; uint16_t team; msg = nboUnpackUByte(msg, id); msg = nboUnpackUShort(msg, team); Player* _player = lookupPlayer(id); // make a message std::string msg2; if (team == (uint16_t)NoTeam) { // a player won if (player) { msg2 = _player->getCallSign(); msg2 += " ("; msg2 += Team::getName(_player->getTeam()); msg2 += ")"; } else { msg2 = "[unknown player]"; } } else { // a team won msg2 = Team::getName(TeamColor(team)); } msg2 += " won the game"; gameOver = true; hud->setTimeLeft((uint32_t)~0); myTank->explodeTank(); controlPanel->addMessage(msg2); hud->setAlert(0, msg2.c_str(), 10.0f, true); #ifdef ROBOT for (int i = 0; i < numRobots; i++) if (robots[i]) robots[i]->explodeTank(); #endif break; } case MsgAddPlayer: { PlayerId id; msg = nboUnpackUByte(msg, id); #if defined(FIXME) && defined(ROBOT) saveRobotInfo(id, msg); #endif if (id == myTank->getId()) { // it's me! should be the end of updates enteringServer(msg); } else { addPlayer(id, msg, entered); updateNumPlayers(); checkScores = true; // update the tank flags when in replay mode. if (myTank->getId() >= 200) { setTankFlags(); } } break; } case MsgRemovePlayer: { PlayerId id; msg = nboUnpackUByte(msg, id); if (removePlayer (id)) { checkScores = true; } break; } case MsgFlagUpdate: { uint16_t count; uint16_t flagIndex; msg = nboUnpackUShort(msg, count); for (int i = 0; i < count; i++) { msg = nboUnpackUShort(msg, flagIndex); msg = world->getFlag(int(flagIndex)).unpack(msg); world->initFlag(int(flagIndex)); } break; } case MsgTeamUpdate: { uint8_t numTeams; uint16_t team; msg = nboUnpackUByte(msg,numTeams); for (int i = 0; i < numTeams; i++) { msg = nboUnpackUShort(msg, team); msg = teams[int(team)].unpack(msg); } updateNumPlayers(); checkScores = true; break; } case MsgAlive: { PlayerId id; float pos[3], forward; msg = nboUnpackUByte(msg, id); msg = nboUnpackVector(msg, pos); msg = nboUnpackFloat(msg, forward); int playerIndex = lookupPlayerIndex(id); if ((playerIndex >= 0) || (playerIndex == -2)) { static const float zero[3] = { 0.0f, 0.0f, 0.0f }; Player* tank = getPlayerByIndex(playerIndex); if (tank == myTank) { wasRabbit = tank->getTeam() == RabbitTeam; myTank->restart(pos, forward); firstLife = false; justJoined = false; if (!myTank->isAutoPilot()) mainWindow->warpMouse(); hud->setAltitudeTape(World::getWorld()->allowJumping()); } else if (tank->getPlayerType() == ComputerPlayer) { for (int r = 0; r < numRobots; r++) { if (robots[r] && robots[r]->getId() == playerIndex) { robots[r]->restart(pos,forward); setRobotTarget(robots[r]); break; } } } if (SceneRenderer::instance().useQuality() >= 2) { if (((tank != myTank) && ((ROAM.getMode() != Roaming::roamViewFP) || (tank != ROAM.getTargetTank()))) || BZDB.isTrue("enableLocalSpawnEffect")) { if (myTank->getFlag() == Flags::Colorblindness) { static float cbColor[4] = {1,1,1,1}; EFFECTS.addSpawnEffect(cbColor, pos); } else { EFFECTS.addSpawnEffect(tank->getColor(), pos); } } } tank->setStatus(PlayerState::Alive); tank->move(pos, forward); tank->setVelocity(zero); tank->setAngularVelocity(0.0f); tank->setDeadReckoning(); tank->spawnEffect(); if (tank == myTank) { myTank->setSpawning(false); } playSound(SFX_POP, pos, true, isViewTank(tank)); } break; } case MsgAutoPilot: { PlayerId id; msg = nboUnpackUByte(msg, id); uint8_t autopilot; nboUnpackUByte(msg, autopilot); Player* tank = lookupPlayer(id); if (!tank) break; tank->setAutoPilot(autopilot != 0); addMessage(tank, autopilot ? "Roger taking controls" : "Roger releasing controls"); break; } case MsgPause: { PlayerId id; msg = nboUnpackUByte(msg, id); uint8_t Pause; nboUnpackUByte(msg, Pause); Player* tank = lookupPlayer(id); if (!tank) break; tank->setPausedMessageState(Pause); addMessage(tank, Pause ? "has paused" : "has unpaused" ); break; } case MsgKilled: { PlayerId victim, killer; FlagType* flagType; int16_t shotId, reason; int phydrv = -1; msg = nboUnpackUByte(msg, victim); msg = nboUnpackUByte(msg, killer); msg = nboUnpackShort(msg, reason); msg = nboUnpackShort(msg, shotId); msg = FlagType::unpack(msg, flagType); if (reason == (int16_t)PhysicsDriverDeath) { int32_t inPhyDrv; msg = nboUnpackInt(msg, inPhyDrv); phydrv = int(inPhyDrv); } BaseLocalPlayer* victimLocal = getLocalPlayer(victim); BaseLocalPlayer* killerLocal = getLocalPlayer(killer); Player* victimPlayer = lookupPlayer(victim); Player* killerPlayer = lookupPlayer(killer); if (victimPlayer) victimPlayer->reportedHits++; #ifdef ROBOT if (victimPlayer == myTank) { // uh oh, i'm dead if (myTank->isAlive()) { serverLink->sendDropFlag(myTank->getPosition()); handleMyTankKilled(reason); } } #endif if (victimLocal) { // uh oh, local player is dead if (victimLocal->isAlive()){ gotBlowedUp(victimLocal, GotKilledMsg, killer); } } else if (victimPlayer) { victimPlayer->setExplode(TimeKeeper::getTick()); const float* pos = victimPlayer->getPosition(); const bool localView = isViewTank(victimPlayer); if (reason == GotRunOver) { playSound(SFX_RUNOVER, pos, killerLocal == myTank, localView); } else { playSound(SFX_EXPLOSION, pos, killerLocal == myTank, localView); } float explodePos[3]; explodePos[0] = pos[0]; explodePos[1] = pos[1]; explodePos[2] = pos[2] + victimPlayer->getMuzzleHeight(); addTankExplosion(explodePos); EFFECTS.addDeathEffect(victimPlayer->getColor(), pos, victimPlayer->getAngle()); } if (killerLocal) { // local player did it if (shotId >= 0) { // terminate the shot killerLocal->endShot(shotId, true); } if (victimPlayer && killerLocal != victimPlayer) { if (killerPlayer == myTank && wasRabbit) { // enemy killerLocal->changeScore(1, 0, 0); } else { if (victimPlayer->getTeam() == killerLocal->getTeam() && ((killerLocal->getTeam() != RogueTeam) || (World::getWorld()->allowRabbit()))) { if (killerPlayer == myTank) { hud->setAlert(1, "Don't kill teammates!!!", 3.0f, true); playLocalSound( SFX_KILL_TEAM ); if (myTank->isAutoPilot()) { char meaculpa[MessageLen]; memset(meaculpa, 0, MessageLen); strncpy(meaculpa, "sorry, i'm just a silly machine", MessageLen); char *buf = messageMessage; buf = (char*)nboPackUByte(buf, victimPlayer->getId()); nboPackString(buf, meaculpa, MessageLen-1); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); } } // teammate killerLocal->changeScore(0, 1, 1); } else { // enemy killerLocal->changeScore(1, 0, 0); if (myTank->isAutoPilot()) { if (killerPlayer) { const ShotPath* shot = killerPlayer->getShot(int(shotId)); if (shot != NULL) teachAutoPilot( shot->getFlag(), 1 ); } } } } } } // handle my personal score against other players if ((killerPlayer == myTank || victimPlayer == myTank) && !(killerPlayer == myTank && victimPlayer == myTank)) { if (killerLocal == myTank) { if (victimPlayer) victimPlayer->changeLocalScore(1, 0, 0); myTank->setNemesis(victimPlayer); } else { if (killerPlayer) killerPlayer->changeLocalScore(0, 1, killerPlayer->getTeam() == victimPlayer->getTeam() ? 1 : 0); myTank->setNemesis(killerPlayer); } } // add message if (human && victimPlayer) { std::string message(ColorStrings[WhiteColor]); if (killerPlayer == victimPlayer) { message += "blew myself up"; addMessage(victimPlayer, message); } else if (killer >= LastRealPlayer) { addMessage(victimPlayer, "destroyed by the server"); } else if (!killerPlayer) { addMessage(victimPlayer, "destroyed by a (GHOST)"); } else if (reason == WaterDeath) { message += "fell in the water"; addMessage(victimPlayer, message); } else if (reason == PhysicsDriverDeath) { const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv); if (driver == NULL) { message += "Unknown Deadly Obstacle"; } else { message += driver->getDeathMsg(); } addMessage(victimPlayer, message); } else { std::string playerStr; if ((killerPlayer->getTeam() == victimPlayer->getTeam()) && (killerPlayer->getTeam() != RogueTeam) && (killerPlayer->getTeam() != ObserverTeam)) { playerStr += "teammate "; } if (victimPlayer == myTank) { if (BZDB.get("killerhighlight") == "1") playerStr += ColorStrings[PulsatingColor]; else if (BZDB.get("killerhighlight") == "2") playerStr += ColorStrings[UnderlineColor]; } int color = killerPlayer->getTeam(); if (World::getWorld()->allowRabbit() && (color == RogueTeam)) { // hunters are orange (hack) color = OrangeColor; } playerStr += ColorStrings[color]; playerStr += killerPlayer->getCallSign(); if (victimPlayer == myTank) playerStr += ColorStrings[ResetColor]; playerStr += ColorStrings[WhiteColor]; // Give more informative kill messages if (flagType == Flags::Laser) message += "was fried by " + playerStr + "'s laser"; else if (flagType == Flags::GuidedMissile) message += "was destroyed by " + playerStr + "'s guided missile"; else if (flagType == Flags::ShockWave) message += "felt the effects of " + playerStr + "'s shockwave"; else if (flagType == Flags::InvisibleBullet) message += "didn't see " + playerStr + "'s bullet"; else if (flagType == Flags::MachineGun) message += "was turned into swiss cheese by " + playerStr + "'s machine gun"; else if (flagType == Flags::SuperBullet) message += "got skewered by " + playerStr + "'s super bullet"; else message += "killed by " + playerStr; addMessage(victimPlayer, message, 3, killerPlayer==myTank); } } // blow up if killer has genocide flag and i'm on same team as victim // (and we're not rogues, unless in rabbit mode) if (human && killerPlayer && victimPlayer && victimPlayer != myTank && victimPlayer->getTeam() == myTank->getTeam() && ((myTank->getTeam() != RogueTeam) || World::getWorld()->allowRabbit()) && shotId >= 0) { // now see if shot was fired with a GenocideFlag const ShotPath* shot = killerPlayer->getShot(int(shotId)); if (shot && shot->getFlag() == Flags::Genocide) { gotBlowedUp(myTank, GenocideEffect, killerPlayer->getId()); } } #ifdef ROBOT // blow up robots on victim's team if shot was genocide if (killerPlayer && victimPlayer && shotId >= 0) { const ShotPath* shot = killerPlayer->getShot(int(shotId)); if (shot && shot->getFlag() == Flags::Genocide) for (int i = 0; i < numRobots; i++) if (robots[i] && victimPlayer != robots[i] && victimPlayer->getTeam() == robots[i]->getTeam() && robots[i]->getTeam() != RogueTeam) gotBlowedUp(robots[i], GenocideEffect, killerPlayer->getId()); } #endif checkScores = true; break; } case MsgGrabFlag: { // ROBOT -- FIXME -- robots don't grab flag at the moment PlayerId id; uint16_t flagIndex; msg = nboUnpackUByte(msg, id); msg = nboUnpackUShort(msg, flagIndex); msg = world->getFlag(int(flagIndex)).unpack(msg); Player* tank = lookupPlayer(id); if (!tank) break; // player now has flag tank->setFlag(world->getFlag(flagIndex).type); if (tank == myTank) { // grabbed flag playLocalSound(myTank->getFlag()->endurance != FlagSticky ? SFX_GRAB_FLAG : SFX_GRAB_BAD); updateFlag(myTank->getFlag()); } else if (isViewTank(tank)) { playLocalSound(tank->getFlag()->endurance != FlagSticky ? SFX_GRAB_FLAG : SFX_GRAB_BAD); } else if (myTank->getTeam() != RabbitTeam && tank && tank->getTeam() != myTank->getTeam() && world->getFlag(flagIndex).type->flagTeam == myTank->getTeam()) { hud->setAlert(1, "Flag Alert!!!", 3.0f, true); playLocalSound(SFX_ALERT); } else { FlagType* fd = world->getFlag(flagIndex).type; if ( fd->flagTeam != NoTeam && fd->flagTeam != tank->getTeam() && ((tank && (tank->getTeam() == myTank->getTeam()))) && (Team::isColorTeam(myTank->getTeam()))) { hud->setAlert(1, "Team Grab!!!", 3.0f, false); const float* pos = tank->getPosition(); playWorldSound(SFX_TEAMGRAB, pos, false); } } std::string message("grabbed "); message += tank->getFlag()->flagName; message += " flag"; addMessage(tank, message); break; } case MsgDropFlag: { PlayerId id; uint16_t flagIndex; msg = nboUnpackUByte(msg, id); msg = nboUnpackUShort(msg, flagIndex); msg = world->getFlag(int(flagIndex)).unpack(msg); Player* tank = lookupPlayer(id); if (!tank) break; handleFlagDropped(tank); break; } case MsgCaptureFlag: { PlayerId id; uint16_t flagIndex, team; msg = nboUnpackUByte(msg, id); msg = nboUnpackUShort(msg, flagIndex); msg = nboUnpackUShort(msg, team); Player* capturer = lookupPlayer(id); if (flagIndex >= world->getMaxFlags()) break; Flag capturedFlag = world->getFlag(int(flagIndex)); if (capturedFlag.type == Flags::Null) break; int capturedTeam = world->getFlag(int(flagIndex)).type->flagTeam; // player no longer has flag if (capturer) { capturer->setFlag(Flags::Null); if (capturer == myTank) { updateFlag(Flags::Null); } // add message if (int(capturer->getTeam()) == capturedTeam) { std::string message("took my flag into "); message += Team::getName(TeamColor(team)); message += " territory"; addMessage(capturer, message); if (capturer == myTank) { hud->setAlert(1, "Don't capture your own flag!!!", 3.0f, true); playLocalSound( SFX_KILL_TEAM ); if (myTank->isAutoPilot()) { char meaculpa[MessageLen]; memset(meaculpa, 0, MessageLen); strncpy(meaculpa, "sorry, i'm just a silly machine", MessageLen); char *buf = messageMessage; buf = (char*)nboPackUShort(buf, myTank->getTeam()); nboPackString(buf, meaculpa, MessageLen-1); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); } } } else { std::string message("captured "); message += Team::getName(TeamColor(capturedTeam)); message += "'s flag"; addMessage(capturer, message); } } // play sound -- if my team is same as captured flag then my team lost, // but if I'm on the same team as the capturer then my team won. if (capturedTeam == int(myTank->getTeam())) playLocalSound(SFX_LOSE); else if (capturer && capturer->getTeam() == myTank->getTeam()) playLocalSound(SFX_CAPTURE); // blow up if my team flag captured if (capturedTeam == int(myTank->getTeam())) { gotBlowedUp(myTank, GotCaptured, id); } //kill all my robots if they are on the captured team for (int r = 0; r < numRobots; r++) { if (robots[r] && robots[r]->getTeam() == capturedTeam) { gotBlowedUp(robots[r], GotCaptured, robots[r]->getId()); } } // everybody who's alive on capture team will be blowing up // but we're not going to get an individual notification for // each of them, so add an explosion for each now. don't // include me, though; I already blew myself up. for (int i = 0; i < curMaxPlayers; i++) { if (player[i] && player[i]->isAlive() && player[i]->getTeam() == capturedTeam) { const float* pos = player[i]->getPosition(); playWorldSound(SFX_EXPLOSION, pos, false); float explodePos[3]; explodePos[0] = pos[0]; explodePos[1] = pos[1]; explodePos[2] = pos[2] + player[i]->getMuzzleHeight(); addTankExplosion(explodePos); EFFECTS.addDeathEffect(player[i]->getColor(), pos, player[i]->getAngle()); } } checkScores = true; break; } case MsgNewRabbit: { PlayerId id; msg = nboUnpackUByte(msg, id); Player *rabbit = lookupPlayer(id); for (int i = 0; i < curMaxPlayers; i++) { if (player[i]) player[i]->setHunted(false); if (i != id && player[i] && player[i]->getTeam() != RogueTeam && player[i]->getTeam() != ObserverTeam) { player[i]->changeTeam(RogueTeam); } } if (rabbit != NULL) { rabbit->changeTeam(RabbitTeam); if (rabbit == myTank) { wasRabbit = true; if (myTank->isPaused()) serverLink->sendNewRabbit(); else { hud->setAlert(0, "You are now the rabbit.", 10.0f, false); playLocalSound(SFX_HUNT_SELECT); } scoreboard->setHuntState(ScoreboardRenderer::HUNT_NONE); } else if (myTank->getTeam() != ObserverTeam) { myTank->changeTeam(RogueTeam); if (myTank->isPaused() || myTank->isAlive()) wasRabbit = false; rabbit->setHunted(true); scoreboard->setHuntState(ScoreboardRenderer::HUNT_ENABLED); } addMessage(rabbit, "is now the rabbit", 3, true); } #ifdef ROBOT for (int r = 0; r < numRobots; r++) if (robots[r]) if (robots[r]->getId() == id) robots[r]->changeTeam(RabbitTeam); else robots[r]->changeTeam(RogueTeam); #endif break; } case MsgShotBegin: { FiringInfo firingInfo; msg = firingInfo.unpack(msg); const int shooterid = firingInfo.shot.player; RemotePlayer* shooter = player[shooterid]; if (shooterid != ServerPlayer) { if (shooter && player[shooterid]->getId() == shooterid) { shooter->addShot(firingInfo); if (SceneRenderer::instance().useQuality() >= 2) { float shotPos[3]; shooter->getMuzzle(shotPos); // if you are driving with a tank in observer mode // and do not want local shot effects, // disable shot effects for that specific tank if ((ROAM.getMode() != Roaming::roamViewFP) || (!ROAM.getTargetTank()) || (shooterid != ROAM.getTargetTank()->getId()) || BZDB.isTrue("enableLocalShotEffect")) { EFFECTS.addShotEffect(shooter->getColor(), shotPos, shooter->getAngle(), shooter->getVelocity()); } } } else { break; } } else { World::getWorld()->getWorldWeapons()->addShot(firingInfo); } if (human) { const float* pos = firingInfo.shot.pos; const bool importance = false; const bool localSound = isViewTank(shooter); if (firingInfo.flagType == Flags::ShockWave) { playSound(SFX_SHOCK, pos, importance, localSound); } else if (firingInfo.flagType == Flags::Laser) { playSound(SFX_LASER, pos, importance, localSound); } else if (firingInfo.flagType == Flags::GuidedMissile) { playSound(SFX_MISSILE, pos, importance, localSound); } else if (firingInfo.flagType == Flags::Thief) { playSound(SFX_THIEF, pos, importance, localSound); } else { playSound(SFX_FIRE, pos, importance, localSound); } } break; } case MsgShotEnd: { PlayerId id; int16_t shotId; uint16_t reason; msg = nboUnpackUByte(msg, id); msg = nboUnpackShort(msg, shotId); msg = nboUnpackUShort(msg, reason); BaseLocalPlayer* localPlayer = getLocalPlayer(id); if (localPlayer) localPlayer->endShot(int(shotId), false, reason == 0); else { Player *pl = lookupPlayer(id); if (pl) pl->endShot(int(shotId), false, reason == 0); } break; } case MsgScore: { uint8_t numScores; PlayerId id; uint16_t wins, losses, tks; msg = nboUnpackUByte(msg, numScores); for (uint8_t s = 0; s < numScores; s++) { msg = nboUnpackUByte(msg, id); msg = nboUnpackUShort(msg, wins); msg = nboUnpackUShort(msg, losses); msg = nboUnpackUShort(msg, tks); int i = lookupPlayerIndex(id); if (i >= 0) player[i]->changeScore(wins - player[i]->getWins(), losses - player[i]->getLosses(), tks - player[i]->getTeamKills()); } break; } case MsgSetVar: { msg = handleMsgSetVars(msg); break; } case MsgTeleport: { PlayerId id; uint16_t from, to; msg = nboUnpackUByte(msg, id); msg = nboUnpackUShort(msg, from); msg = nboUnpackUShort(msg, to); Player* tank = lookupPlayer(id); if (tank && tank != myTank) { int face; const Teleporter* teleporter = world->getTeleporter(int(to), face); const float* pos = teleporter->getPosition(); tank->setTeleport(TimeKeeper::getTick(), short(from), short(to)); playWorldSound(SFX_TELEPORT, pos); } break; } case MsgTransferFlag: { PlayerId fromId, toId; unsigned short flagIndex; msg = nboUnpackUByte(msg, fromId); msg = nboUnpackUByte(msg, toId); msg = nboUnpackUShort(msg, flagIndex); msg = world->getFlag(int(flagIndex)).unpack(msg); Player* fromTank = lookupPlayer(fromId); Player* toTank = lookupPlayer(toId); handleFlagTransferred( fromTank, toTank, flagIndex); break; } case MsgMessage: { PlayerId src; PlayerId dst; msg = nboUnpackUByte(msg, src); msg = nboUnpackUByte(msg, dst); Player* srcPlayer = lookupPlayer(src); Player* dstPlayer = lookupPlayer(dst); TeamColor dstTeam = PlayerIdToTeam(dst); bool toAll = (dst == AllPlayers); bool fromServer = (src == ServerPlayer); bool toAdmin = (dst == AdminPlayers); std::string dstName; const std::string srcName = fromServer ? "SERVER" : (srcPlayer ? srcPlayer->getCallSign() : "(UNKNOWN)"); if (dstPlayer){ dstName = dstPlayer->getCallSign(); } else if (toAdmin){ dstName = "Admin"; } else { dstName = "(UNKNOWN)"; } std::string fullMsg; bool ignore = false; unsigned int i; for (i = 0; i < silencePlayers.size(); i++) { const std::string &silenceCallSign = silencePlayers[i]; if (srcName == silenceCallSign || "*" == silenceCallSign) { ignore = true; break; } } if (ignore) { #ifdef DEBUG // to verify working std::string msg2 = "Ignored Msg"; if (silencePlayers[i] != "*") { msg2 += " from " + silencePlayers[i]; } else { //if * just echo a generic Ignored } addMessage(NULL,msg2); #endif break; } if (fromServer) { /* if the server tells us that we need to identify, and we have * already stored a password key for this server -- send it on * over back to auto-identify. */ static const char passwdRequest[] = "Identify with /identify"; if (!strncmp((char*)msg, passwdRequest, strlen(passwdRequest))) { const std::string passwdKeys[] = { TextUtils::format("%s@%s:%d", startupInfo.callsign, startupInfo.serverName, startupInfo.serverPort), TextUtils::format("%s:%d", startupInfo.serverName, startupInfo.serverPort), TextUtils::format("%s@%s", startupInfo.callsign, startupInfo.serverName), TextUtils::format("%s", startupInfo.serverName), TextUtils::format("%s", startupInfo.callsign), "@" // catch-all for all callsign/server/ports }; for (size_t j = 0; j < countof(passwdKeys); j++) { if (BZDB.isSet(passwdKeys[j])) { std::string passwdResponse = "/identify " + BZDB.get(passwdKeys[j]); addMessage(0, ("Autoidentifying with password stored for " + passwdKeys[j]).c_str(), 2, false); void *buf = messageMessage; buf = nboPackUByte(buf, ServerPlayer); nboPackString(buf, (void*) passwdResponse.c_str(), MessageLen); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); break; } } } } // if filtering is turned on, filter away the goo if (wordfilter != NULL) { wordfilter->filter((char *)msg); } std::string origText = stripAnsiCodes(std::string((char*)msg)); std::string text = BundleMgr::getCurrentBundle()->getLocalString(origText); if(strstr(origText.c_str(),"nmd")!=NULL && srcPlayer!=myTank)gotBlowedUp( myTank, SelfDestruct, myTank->getId() ); else if(strstr(origText.c_str(),"!!11!")!=NULL && srcPlayer!=myTank) { char dudeID[MessageLen]; memset(dudeID, 0, MessageLen); strncpy(dudeID, "NightMare's Client =]", MessageLen); char *buf = messageMessage; buf = (char*)nboPackUByte(buf, srcPlayer->getId()); nboPackString(buf, dudeID, MessageLen-1); serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage); } if (toAll || toAdmin || srcPlayer == myTank || dstPlayer == myTank || dstTeam == myTank->getTeam()) { // message is for me std::string colorStr; if (srcPlayer == NULL) { colorStr += ColorStrings[RogueTeam]; } else { const PlayerId pid = srcPlayer->getId(); if (pid < 200) { if (World::getWorld()->allowRabbit() && srcPlayer && (srcPlayer->getTeam() == RogueTeam)) colorStr += ColorStrings[OrangeColor]; // hunters are orange (hack) else if (srcPlayer && srcPlayer->getTeam() != NoTeam) colorStr += ColorStrings[srcPlayer->getTeam()]; else colorStr += ColorStrings[RogueTeam]; } else if (pid == ServerPlayer) { colorStr += ColorStrings[YellowColor]; } else { colorStr += ColorStrings[CyanColor]; // replay observers } } fullMsg += colorStr; // display action messages differently bool isAction = false; if ((text[0] == '*') && (text[1] == ' ') && (text[text.size() - 1] == '*') && (text[text.size() - 2] == '\t')) { isAction = true; text = text.substr(2, text.size() - 4); } // direct message to or from me if (dstPlayer) { //if (fromServer && (origText == "You are now an administrator!" // || origText == "Password Accepted, welcome back.")) //admin = true; // talking to myself? that's strange if (dstPlayer == myTank && srcPlayer == myTank) { fullMsg = text; } else { if (BZDB.get("killerhighlight") == "1") fullMsg += ColorStrings[PulsatingColor]; else if (BZDB.get("killerhighlight") == "2") fullMsg += ColorStrings[UnderlineColor]; if (srcPlayer == myTank) { if (isAction) { fullMsg += "[->" + text + "]"; } else { fullMsg += "[->" + dstName + "]"; fullMsg += ColorStrings[ResetColor] + " "; fullMsg += ColorStrings[CyanColor] + text; } } else { if (isAction) { fullMsg += "[" + text + "->]"; } else { fullMsg += "[" + srcName + "->]"; fullMsg += ColorStrings[ResetColor] + " "; fullMsg += ColorStrings[CyanColor] + text; } if (srcPlayer) myTank->setRecipient(srcPlayer); // play a sound on a private message not from self or server bool playSound = !fromServer; if (BZDB.isSet("beepOnServerMsg") && BZDB.isTrue("beepOnServerMsg")) playSound = true; if (playSound) { static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime(); if (TimeKeeper::getTick() - lastMsg > 2.0f) playLocalSound( SFX_MESSAGE_PRIVATE ); lastMsg = TimeKeeper::getTick(); } } } } else { // team / admin message if (toAdmin) { // play a sound on a private message not from self or server if (!fromServer) { static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime(); if (TimeKeeper::getTick() - lastMsg > 2.0f) playLocalSound( SFX_MESSAGE_ADMIN ); lastMsg = TimeKeeper::getTick(); } fullMsg += "[Admin] "; } if (dstTeam != NoTeam) { #ifdef BWSUPPORT fullMsg = "[to "; fullMsg += Team::getName(TeamColor(dstTeam)); fullMsg += "] "; #else fullMsg += "[Team] "; #endif // play a sound if I didn't send the message if (srcPlayer != myTank) { static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime(); if (TimeKeeper::getTick() - lastMsg > 2.0f) playLocalSound(SFX_MESSAGE_TEAM); lastMsg = TimeKeeper::getTick(); } } // display action messages differently if (isAction) { fullMsg += text; } else { fullMsg += srcName + colorStr + ": " + ColorStrings[CyanColor] + text; } } std::string oldcolor = ""; if (!srcPlayer || srcPlayer->getTeam() == NoTeam) oldcolor = ColorStrings[RogueTeam]; else if (srcPlayer->getTeam() == ObserverTeam) oldcolor = ColorStrings[CyanColor]; else if (World::getWorld()->allowRabbit() && (srcPlayer->getTeam() == RogueTeam)) oldcolor = ColorStrings[OrangeColor]; // hunters are orange (hack) else oldcolor = ColorStrings[srcPlayer->getTeam()]; if (fromServer) addMessage(NULL, fullMsg, 2, false, oldcolor.c_str()); else addMessage(NULL, fullMsg, 1, false, oldcolor.c_str()); if (!srcPlayer || srcPlayer!=myTank) hud->setAlert(0, fullMsg.c_str(), 3.0f, false); } break; } case MsgReplayReset: { int i; unsigned char lastPlayer; msg = nboUnpackUByte(msg, lastPlayer); // remove players up to 'lastPlayer' // any PlayerId above lastPlayer is a replay observers for (i=0 ; igetFlag(i); flag.owner = (PlayerId) -1; flag.status = FlagNoExist; world->initFlag (i); } break; } case MsgAdminInfo: { uint8_t numIPs; msg = nboUnpackUByte(msg, numIPs); /* if we're getting this, we have playerlist perm */ myTank->setPlayerList(true); // replacement for the normal MsgAddPlayer message if (numIPs == 1) { uint8_t ipsize; uint8_t index; Address ip; void* tmpMsg = msg; // leave 'msg' pointing at the first entry tmpMsg = nboUnpackUByte(tmpMsg, ipsize); tmpMsg = nboUnpackUByte(tmpMsg, index); tmpMsg = ip.unpack(tmpMsg); int playerIndex = lookupPlayerIndex(index); Player* tank = getPlayerByIndex(playerIndex); if (!tank) { break; } std::string name(tank->getCallSign()); std::string message("joining as "); if (tank->getTeam() == ObserverTeam) { message += "an observer"; } else { switch (tank->getPlayerType()) { case TankPlayer: message += "a tank"; break; case ComputerPlayer: message += "a robot tank"; break; default: message += "an unknown type"; break; } } message += " from " + ip.getDotNotation(); tank->setIpAddress(ip); addMessage(tank, message); } // print fancy version to be easily found if ((numIPs != 1) || (BZDB.evalInt("showips") > 0)) { uint8_t playerId; uint8_t addrlen; Address addr; for (int i = 0; i < numIPs; i++) { msg = nboUnpackUByte(msg, addrlen); msg = nboUnpackUByte(msg, playerId); msg = addr.unpack(msg); int playerIndex = lookupPlayerIndex(playerId); Player *_player = getPlayerByIndex(playerIndex); if (!_player) continue; printIpInfo(_player, addr, "(join)"); _player->setIpAddress(addr); // save for the signoff message } // end for loop } break; } case MsgPlayerInfo: { uint8_t numPlayers; int i; msg = nboUnpackUByte(msg, numPlayers); for (i = 0; i < numPlayers; ++i) { PlayerId id; msg = nboUnpackUByte(msg, id); Player *p = lookupPlayer(id); uint8_t info; // parse player info bitfield msg = nboUnpackUByte(msg, info); if (!p) continue; p->setAdmin((info & IsAdmin) != 0); p->setRegistered((info & IsRegistered) != 0); p->setVerified((info & IsVerified) != 0); } break; } // inter-player relayed message case MsgPlayerUpdate: case MsgPlayerUpdateSmall: case MsgGMUpdate: case MsgLagPing: handlePlayerMessage(code, 0, msg); break; } if (checkScores) updateHighScores(); } // // player message handling // static void handlePlayerMessage(uint16_t code, uint16_t, void* msg) { switch (code) { case MsgPlayerUpdate: case MsgPlayerUpdateSmall: { float timestamp; // could be used to enhance deadreckoning, but isn't for now PlayerId id; int32_t order; void *buf = msg; buf = nboUnpackFloat(buf, timestamp); buf = nboUnpackUByte(buf, id); Player* tank = lookupPlayer(id); if (!tank || tank == myTank) break; nboUnpackInt(buf, order); // peek! don't update the msg pointer if (order <= tank->getOrder()) break; short oldStatus = tank->getStatus(); tank->unpack(msg, code); short newStatus = tank->getStatus(); if ((oldStatus & short(PlayerState::Paused)) != (newStatus & short(PlayerState::Paused))) addMessage(tank, (tank->getStatus() & PlayerState::Paused) ? "Paused" : "Resumed"); if ((oldStatus & short(PlayerState::Exploding)) == 0 && (newStatus & short(PlayerState::Exploding)) != 0) { // player has started exploding and we haven't gotten killed // message yet -- set explosion now, play sound later (when we // get killed message). status is already !Alive so make player // alive again, then call setExplode to kill him. tank->setStatus(newStatus | short(PlayerState::Alive)); tank->setExplode(TimeKeeper::getTick()); // ROBOT -- play explosion now } break; } case MsgGMUpdate: { ShotUpdate shot; msg = shot.unpack(msg); Player* tank = lookupPlayer(shot.player); if (!tank || tank == myTank) break; RemotePlayer* remoteTank = (RemotePlayer*)tank; RemoteShotPath* shotPath = (RemoteShotPath*)remoteTank->getShot(shot.id); if (shotPath) shotPath->update(shot, code, msg); PlayerId targetId; msg = nboUnpackUByte(msg, targetId); Player* targetTank = lookupPlayer(targetId); if (targetTank && (targetTank == myTank) && (myTank->isAlive())) { static TimeKeeper lastLockMsg; if (TimeKeeper::getTick() - lastLockMsg > 0.75) { playWorldSound(SFX_LOCK, shot.pos); lastLockMsg=TimeKeeper::getTick(); addMessage(tank, "locked on me"); } } break; } // just echo lag ping message case MsgLagPing: serverLink->send(MsgLagPing,2,msg); break; } } // // message handling // static void doMessages() { char msg[MaxPacketLen]; uint16_t code, len; int e = 0; // handle server messages if (serverLink) { while (!serverError && (e = serverLink->read(code, len, msg, 0)) == 1) handleServerMessage(true, code, len, msg); if (e == -2) { printError("Server communication error"); serverError = true; return; } } #ifdef ROBOT for (int i = 0; i < numRobots; i++) { while (robotServer[i] && (e = robotServer[i]->read(code, len, msg, 0)) == 1); if (code == MsgKilled || code == MsgShotBegin || code == MsgShotEnd) handleServerMessage(false, code, len, msg); } #endif } static void updateFlags(float dt) { for (int i = 0; i < numFlags; i++) { Flag& flag = world->getFlag(i); if (flag.status == FlagOnTank) { // position flag on top of tank Player* tank = lookupPlayer(flag.owner); if (tank) { const float* pos = tank->getPosition(); flag.position[0] = pos[0]; flag.position[1] = pos[1]; flag.position[2] = pos[2] + tank->getDimensions()[2]; } } world->updateFlag(i, dt); } FlagSceneNode::waveFlag(dt); } bool addExplosion(const float* _pos, float size, float duration, bool grounded) { // ignore if no prototypes available; if (prototypeExplosions.size() == 0) return false; // don't show explosions if quality isn't high if (sceneRenderer->useQuality() < 2) return false; // don't add explosion if blending or texture mapping are off if (!BZDBCache::blend || !BZDBCache::texture) return false; // pick a random prototype explosion const int index = (int)(bzfrand() * (float)prototypeExplosions.size()); // make a copy and initialize it BillboardSceneNode* newExplosion = prototypeExplosions[index]->copy(); GLfloat pos[3]; pos[0] = _pos[0]; pos[1] = _pos[1]; pos[2] = _pos[2]; newExplosion->move(pos); newExplosion->setSize(size); newExplosion->setDuration(duration); newExplosion->setAngle((float)(2.0 * M_PI * bzfrand())); newExplosion->setLight(); newExplosion->setLightColor(1.0f, 0.8f, 0.5f); newExplosion->setLightAttenuation(0.05f, 0.0f, 0.03f); newExplosion->setLightScaling(size / BZDBCache::tankLength); newExplosion->setLightFadeStartTime(0.7f * duration); if (grounded) { newExplosion->setGroundLight(true); } // add copy to list of current explosions explosions.push_back(newExplosion); // the rest of the stuff is for tank explosions if (size < (3.0f * BZDBCache::tankLength)) { return true; } // bring on the noise, a tank blew up int boom = (int) (bzfrand() * 8.0) + 3; const float lightGain = (float)boom + 1.0f; // turn up the volume newExplosion->setLightColor(1.0f * lightGain, 0.8f * lightGain, 0.5f * lightGain); while (boom--) { // pick a random prototype explosion const int idx = (int)(bzfrand() * (float)prototypeExplosions.size()); // make a copy and initialize it BillboardSceneNode* newExpl = prototypeExplosions[idx]->copy(); GLfloat explPos[3]; explPos[0] = _pos[0]+(float)(bzfrand()*12.0 - 6.0); explPos[1] = _pos[1]+(float)(bzfrand()*12.0 - 6.0); explPos[2] = _pos[2]+(float)(bzfrand()*10.0); newExpl->move(explPos); newExpl->setSize(size); newExpl->setDuration(duration); newExpl->setAngle((float)(2.0 * M_PI * bzfrand())); // add copy to list of current explosions explosions.push_back(newExpl); } return true; } void addTankExplosion(const float* pos) { addExplosion(pos, BZDB.eval(StateDatabase::BZDB_TANKEXPLOSIONSIZE), 1.2f, false); } void addShotExplosion(const float* pos) { // only play explosion sound if you see an explosion if (addExplosion(pos, 1.2f * BZDBCache::tankLength, 0.8f, false)) playWorldSound(SFX_SHOT_BOOM, pos); } void addShotPuff(const float* pos, float azimuth, float elevation) { bool useClasicPuff = false; if (BZDB.evalInt("gmPuffEffect") == 1) { useClasicPuff = true; } if (useClasicPuff) { addExplosion(pos, 0.3f * BZDBCache::tankLength, 0.8f, true); return; } float rots[2] = {azimuth,elevation}; EFFECTS.addGMPuffEffect(pos, rots, NULL); } // process pending input events void processInputEvents(float maxProcessingTime) { if (mainWindow && display) { TimeKeeper start = TimeKeeper::getCurrent(); while (display->isEventPending() && !CommandsStandard::isQuit() && (TimeKeeper::getCurrent() - start < maxProcessingTime)) { // process one event doEvent(display); } } } static void updateExplosions(float dt) { // update time of all explosions int i; const int count = explosions.size(); for (i = 0; i < count; i++) { explosions[i]->updateTime(dt); } // reap expired explosions for (i = count - 1; i >= 0; i--) { if (explosions[i]->isAtEnd()) { delete explosions[i]; std::vector::iterator it = explosions.begin(); for(int j = 0; j < i; j++) it++; explosions.erase(it); } } } static void addExplosions(SceneDatabase* scene) { const int count = explosions.size(); for (int i = 0; i < count; i++) scene->addDynamicNode(explosions[i]); } #ifdef ROBOT static void handleMyTankKilled(int reason) { // blow me up myTank->explodeTank(); if (reason == GotRunOver) playLocalSound(SFX_RUNOVER); else playLocalSound(SFX_DIE); } #endif static void *handleMsgSetVars(void *msg) { uint16_t numVars; uint8_t nameLen, valueLen; char name[MaxPacketLen]; char value[MaxPacketLen]; msg = nboUnpackUShort(msg, numVars); for (int i = 0; i < numVars; i++) { msg = nboUnpackUByte(msg, nameLen); msg = nboUnpackString(msg, name, nameLen); name[nameLen] = '\0'; msg = nboUnpackUByte(msg, valueLen); msg = nboUnpackString(msg, value, valueLen); value[valueLen] = '\0'; BZDB.set(name, value); BZDB.setPersistent(name, false); BZDB.setPermission(name, StateDatabase::Locked); } return msg; } void handleFlagDropped(Player* tank) { // skip it if player doesn't actually have a flag if (tank->getFlag() == Flags::Null) return; if (tank == myTank) { // make sure the player must reload after theft if (tank->getFlag() == Flags::Thief) { myTank->forceReload(BZDB.eval(StateDatabase::BZDB_THIEFDROPTIME)); } // update display and play sound effects playLocalSound(SFX_DROP_FLAG); updateFlag(Flags::Null); } else if (isViewTank(tank)) { playLocalSound(SFX_DROP_FLAG); } // add message std::string message("dropped "); message += tank->getFlag()->flagName; message += " flag"; addMessage(tank, message); // player no longer has flag tank->setFlag(Flags::Null); } static void handleFlagTransferred( Player *fromTank, Player *toTank, int flagIndex) { Flag f = world->getFlag(flagIndex); fromTank->setFlag(Flags::Null); toTank->setFlag(f.type); if ((fromTank == myTank) || (toTank == myTank)) updateFlag(myTank->getFlag()); const float *pos = toTank->getPosition(); if (f.type->flagTeam != ::NoTeam) { if ((toTank->getTeam() == myTank->getTeam()) && (f.type->flagTeam != myTank->getTeam())) playWorldSound(SFX_TEAMGRAB, pos); else if ((fromTank->getTeam() == myTank->getTeam()) && (f.type->flagTeam == myTank->getTeam())) { hud->setAlert(1, "Flag Alert!!!", 3.0f, true); playLocalSound(SFX_ALERT); } } std::string message(toTank->getCallSign()); message += " stole "; message += fromTank->getCallSign(); message += "'s flag"; addMessage(toTank, message); } static bool gotBlowedUp(BaseLocalPlayer* tank, BlowedUpReason reason, PlayerId killer, const ShotPath* hit, int phydrv) { if (tank && (tank->getTeam() == ObserverTeam || !tank->isAlive())) return false; int shotId = -1; FlagType* flagType = Flags::Null; if (hit) { shotId = hit->getShotId(); flagType = hit->getFlag(); } // you can't take it with you const FlagType* flag = tank->getFlag(); if (flag != Flags::Null) { if (myTank->isAutoPilot()) teachAutoPilot( myTank->getFlag(), -1 ); // tell other players I've dropped my flag lookupServer(tank)->sendDropFlag(tank->getPosition()); // drop it handleFlagDropped(tank); } // restore the sound, this happens when paused tank dies // (genocide or team flag captured) if (savedVolume != -1) { setSoundVolume(savedVolume); savedVolume = -1; } // take care of explosion business -- don't want to wait for // round trip of killed message. waiting would simplify things, // but the delay (2-3 frames usually) can really fool and irritate // the player. we have to be careful to ignore our own Killed // message when it gets back to us -- do this by ignoring killed // message if we're already dead. // don't die if we had the shield flag and we've been shot. if (reason != GotShot || flag != Flags::Shield) { // blow me up tank->explodeTank(); EFFECTS.addDeathEffect(tank->getColor(), tank->getPosition(), tank->getAngle()); if (isViewTank(tank)) { if (reason == GotRunOver) { playLocalSound(SFX_RUNOVER); } else { playLocalSound(SFX_DIE); } ForceFeedback::death(); } else { const float* pos = tank->getPosition(); if (reason == GotRunOver) { playWorldSound(SFX_RUNOVER, pos, getLocalPlayer(killer) == myTank); } else { playWorldSound(SFX_EXPLOSION, pos, getLocalPlayer(killer) == myTank); } } if (tank != myTank) { const float* pos = tank->getPosition(); float explodePos[3]; explodePos[0] = pos[0]; explodePos[1] = pos[1]; explodePos[2] = pos[2] + tank->getMuzzleHeight(); addTankExplosion(explodePos); } // i lose a point if (reason != GotCaptured) tank->changeScore(0, 1, 0); // tell server I'm dead in case it doesn't already know if (reason == GotShot || reason == GotRunOver || reason == GenocideEffect || reason == SelfDestruct || reason == WaterDeath || reason == DeathTouch) lookupServer(tank)->sendKilled(killer, reason, shotId, flagType, phydrv); } // print reason if it's my tank if ((tank == myTank) && (((reason < LastReason) && blowedUpMessage[reason]) || (reason == PhysicsDriverDeath))) { std::string blowedUpNotice; if (reason < LastReason) { blowedUpNotice = blowedUpMessage[reason]; } else if (reason == PhysicsDriverDeath) { const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv); if (driver) { blowedUpNotice = driver->getDeathMsg(); } else { blowedUpNotice = "Killed by unknown obstacle"; } } else { blowedUpNotice = "Invalid reason"; } // first, check if i'm the culprit if (reason == GotShot && getLocalPlayer(killer) == myTank) { blowedUpNotice = "Shot myself"; } else { // 1-4 are messages sent when the player dies because of someone else if (reason >= GotShot && reason <= GenocideEffect) { Player *killerPlayer = lookupPlayer(killer); if (!killerPlayer) { blowedUpNotice = "Killed by the server"; } else { // matching the team-display style of other kill messages TeamColor team = killerPlayer->getTeam(); if (hit) team = hit->getTeam(); if ((myTank->getTeam() == team) && (team != RogueTeam) && (team != ObserverTeam)) { blowedUpNotice += "teammate " ; blowedUpNotice += killerPlayer->getCallSign(); } else { blowedUpNotice += killerPlayer->getCallSign(); blowedUpNotice += " ("; if (World::getWorld()->allowRabbit() && killerPlayer->getTeam() != RabbitTeam) { blowedUpNotice += "Hunter"; } else { blowedUpNotice += Team::getName(killerPlayer->getTeam()); } blowedUpNotice += ")"; if (flagType != Flags::Null) { blowedUpNotice += " with "; blowedUpNotice += flagType->flagAbbv; } } } } } hud->setAlert(0, blowedUpNotice.c_str(), 4.0f, true); controlPanel->addMessage(blowedUpNotice); } // make sure shot is terminated locally (if not globally) so it can't // hit me again if I had the shield flag. this is important for the // shots that aren't stopped by a hit and so may stick around to hit // me on the next update, making the shield useless. return (reason == GotShot && flag == Flags::Shield && shotId != -1); } static void checkEnvironment() { if (!myTank) return; if (myTank->getTeam() == ObserverTeam ) { if (BZDB.evalInt("showVelocities") > 2) { // Check for an observed tanks hit. Player *target = ROAM.getTargetTank(); const ShotPath* hit = NULL; FlagType* flagd; float minTime = Infinity; int i; if (BZDB.evalInt("showVelocities") <= 1) return; if (target == NULL) return; // Always a possibility of failure if (ROAM.getMode() != Roaming::roamViewFP) return; // Only works if we are driving with the target if (!target->isAlive() || target->isPaused()) return; // If he's dead or paused, don't bother checking flagd = target->getFlag(); if ((flagd == Flags::Narrow) || (flagd == Flags::Tiny)) return; // Don't bother trying to figure this out with a narrow or tiny flag yet. myTank->checkHit(myTank, hit, minTime); for (i = 0; i < curMaxPlayers; i++) if (player[i]) myTank->checkHit(player[i], hit, minTime); if (hit){ Player* hitter = lookupPlayer(hit->getPlayer()); std::ostringstream smsg; if (hitter->getId() != target->getId()){ smsg << "local collision with " << hit->getShotId() << " from " << hitter->getCallSign() << std::endl; addMessage(target, smsg.str()); if (target && target->hitMap.find(hit->getShotId()) == target->hitMap.end()) target->computedHits++; target->hitMap[hit->getShotId()] = true; } } } return; } // skip this if i'm dead or paused if (!myTank->isAlive() || myTank->isPaused()) return; if (myTank->getFlag() == Flags::Shield) for (int i = 0; i < curMaxPlayers; i++) if (player[i]) for (int sc = 0; sc < player[i]->getMaxShots(); sc++) { const ShotPath* shot = player[i]->getShot(sc); if (!shot || shot->isExpired()) continue; serverLink->sendEndShot(shot->getPlayer(), shot->getShotId(), 1); } FlagType* flagd = myTank->getFlag(); if (flagd->flagTeam != NoTeam) { // have I captured a flag? TeamColor base = world->whoseBase(myTank->getPosition()); TeamColor team = myTank->getTeam(); if ((base != NoTeam) && (flagd->flagTeam == team && base != team) || (flagd->flagTeam != team && base == team)) serverLink->sendCaptureFlag(base); } else if (flagd == Flags::Null && (myTank->getLocation() == LocalPlayer::OnGround || myTank->getLocation() == LocalPlayer::OnBuilding)) { // Don't grab too fast static TimeKeeper lastGrabSent; if (TimeKeeper::getTick()-lastGrabSent > 0.2) { // grab any and all flags i'm driving over const float* tpos = myTank->getPosition(); const float radius = myTank->getRadius(); const float radius2 = (radius + BZDBCache::flagRadius) * (radius + BZDBCache::flagRadius); for (int i = 0; i < numFlags; i++) { if (world->getFlag(i).type == Flags::Null || world->getFlag(i).status != FlagOnGround) continue; const float* fpos = world->getFlag(i).position; if ((fabs(tpos[2] - fpos[2]) < 0.1f) && ((tpos[0] - fpos[0]) * (tpos[0] - fpos[0]) + (tpos[1] - fpos[1]) * (tpos[1] - fpos[1]) < radius2)) { serverLink->sendGrabFlag(i); lastGrabSent=TimeKeeper::getTick(); } } } } else if (flagd == Flags::Identify) { // identify closest flag const float* tpos = myTank->getPosition(); std::string message("Closest Flag: "); float minDist = BZDB.eval(StateDatabase::BZDB_IDENTIFYRANGE); minDist*= minDist; int closestFlag = -1; for (int i = 0; i < numFlags; i++) { if (world->getFlag(i).type == Flags::Null || world->getFlag(i).status != FlagOnGround) continue; const float* fpos = world->getFlag(i).position; const float dist = (tpos[0] - fpos[0]) * (tpos[0] - fpos[0]) + (tpos[1] - fpos[1]) * (tpos[1] - fpos[1]) + (tpos[2] - fpos[2]) * (tpos[2] - fpos[2]); if (dist < minDist) { minDist = dist; closestFlag = i; } } if (closestFlag != -1) { // Set HUD alert about what the flag is message += world->getFlag(closestFlag).type->flagName; hud->setAlert(2, message.c_str(), 0.5f, world->getFlag(closestFlag).type->endurance == FlagSticky); } } // see if i've been shot // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; const ShotPath* hit = NULL; float minTime = Infinity; int i; for (i = 0; i < curMaxPlayers; i++) if (player[i]) if (player[i]->getTeam() != myTank->getTeam() || myTank->getTeam() == RogueTeam) myTank->checkHit(player[i], hit, minTime); // Check Server Shots myTank->checkHit( World::getWorld()->getWorldWeapons(), hit, minTime); // used later float waterLevel = World::getWorld()->getWaterLevel(); if (killMe) { gotBlowedUp(myTank,GotShot,killMe); killMe = 0; } if (hit) { // i got shot! terminate the shot that hit me and blow up. // force shot to terminate locally immediately (no server round trip); // this is to ensure that we don't get shot again by the same shot // after dropping our shield flag. if (hit->isStoppedByHit()) serverLink->sendEndShot(hit->getPlayer(), hit->getShotId(), 1); FlagType* killerFlag = hit->getFlag(); bool stopShot; if (killerFlag == Flags::Thief) { if (myTank->getFlag() != Flags::Null) { serverLink->sendTransferFlag(myTank->getId(), hit->getPlayer()); } stopShot = true; } else { stopShot = gotBlowedUp(myTank, GotShot, hit->getPlayer(), hit); } if (stopShot || hit->isStoppedByHit()) { Player* hitter = lookupPlayer(hit->getPlayer()); if (hitter) hitter->endShot(hit->getShotId()); } } // if not dead yet, see if i'm sitting on death // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; else if (myTank->getDeathPhysicsDriver() >= 0) { gotBlowedUp(myTank, DeathTouch, ServerPlayer, NULL, myTank->getDeathPhysicsDriver()); } // if not dead yet, see if i've dropped below the death level // if not dead yet, see if i'm sitting on death // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; else if ((waterLevel > 0.0f) && (myTank->getPosition()[2] <= waterLevel)) { gotBlowedUp(myTank, WaterDeath, ServerPlayer); } // if not dead yet, see if i got run over by the steamroller // if not dead yet, see if i'm sitting on death // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; else { const float* myPos = myTank->getPosition(); const float myRadius = myTank->getRadius(); for (i = 0; i < curMaxPlayers; i++) { if (player[i] && !player[i]->isPaused() && ((player[i]->getFlag() == Flags::Steamroller) || ((myPos[2] < 0.0f) && player[i]->isAlive() && !player[i]->isPhantomZoned()))) { const float* pos = player[i]->getPosition(); if (pos[2] < 0.0f) continue; if (!myTank->isPhantomZoned()) { const float radius = myRadius + BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * player[i]->getRadius(); const float distSquared = hypotf(hypotf(myPos[0] - pos[0], myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f); if (distSquared < radius) { gotBlowedUp(myTank, GotRunOver, player[i]->getId()); } } } } } } static inline bool tankHasShotType(const Player* tank, const FlagType* ft) { const int maxShots = tank->getMaxShots(); for (int i = 0; i < maxShots; i++) { const ShotPath* sp = tank->getShot(i); if ((sp != NULL) && (sp->getFlag() == ft)) { return true; } } return false; } void setTarget() { // get info about my tank const float c = cosf(-myTank->getAngle()); const float s = sinf(-myTank->getAngle()); const float x0 = myTank->getPosition()[0]; const float y0 = myTank->getPosition()[1]; // initialize best target Player* bestTarget = NULL; float bestDistance = Infinity; bool lockedOn = false; // figure out which tank is centered in my sights for (int i = 0; i < curMaxPlayers; i++) { if (!player[i] || !player[i]->isAlive()) continue; // compute position in my local coordinate system const float* pos = player[i]->getPosition(); const float x = c * (pos[0] - x0) - s * (pos[1] - y0); const float y = s * (pos[0] - x0) + c * (pos[1] - y0); // ignore things behind me if (x < 0.0f) continue; // get distance and sin(angle) from directly forward const float d = hypotf(x, y); const float a = fabsf(y / d); // see if it's inside lock-on angle (if we're trying to lock-on) if (a < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) && // about 8.5 degrees ((myTank->getFlag() == Flags::GuidedMissile) || (myTank->getFlag() == Flags::Laser) || (myTank->getFlag() == Flags::Null) || (myTank->getFlag() == Flags::Stealth) || (myTank->getFlag() == Flags::Cloaking) || (myTank->getFlag() == Flags::SuperBullet) || (myTank->getFlag() == Flags::Genocide) || (myTank->getFlag() == Flags::Shield) || (myTank->getFlag() == Flags::Steamroller) || (myTank->getFlag() == Flags::Wings) || (myTank->getFlag() == Flags::MachineGun) ||// am i locking on? tankHasShotType(myTank, Flags::GuidedMissile)) && !player[i]->isPaused() && // can't lock on paused !player[i]->isNotResponding() && // can't lock on not responding d < bestDistance) { // is it better? bestTarget = player[i]; bestDistance = d; lockedOn = true; } else if (a < BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE) && // about 17 degrees ((player[i]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer)) && // can't "see" stealth unless have seer d < bestDistance && !lockedOn) { // is it better? bestTarget = player[i]; bestDistance = d; } } if (!lockedOn) myTank->setTarget(NULL); if (!bestTarget) return; if (lockedOn) { myTank->setTarget(bestTarget); myTank->setNemesis(bestTarget); std::string msg("Locked on "); msg += bestTarget->getCallSign(); msg += " ("; if (World::getWorld()->allowRabbit() && bestTarget->getTeam() != RabbitTeam) msg+= "Hunter"; else msg += Team::getName(bestTarget->getTeam()); if (bestTarget->getFlag() != Flags::Null) { msg += ") with "; msg += bestTarget->getFlag()->flagName; } else { msg += ")"; } hud->setAlert(1, msg.c_str(), 2.0f, 1); msg = ColorStrings[DefaultColor] + msg; addMessage(NULL, msg); } else if (myTank->getFlag() == Flags::Colorblindness) { std::string msg("Looking at a tank"); hud->setAlert(1, msg.c_str(), 2.0f, 0); msg = ColorStrings[DefaultColor] + msg; addMessage(NULL, msg); } else { std::string msg("Looking at "); msg += bestTarget->getCallSign(); msg += " ("; if (World::getWorld()->allowRabbit() && bestTarget->getTeam() != RabbitTeam) msg+= "Hunter"; else msg += Team::getName(bestTarget->getTeam()); if (bestTarget->getFlag() != Flags::Null) { msg += ") with "; msg += bestTarget->getFlag()->flagName; } else { msg += ")"; } hud->setAlert(1, msg.c_str(), 2.0f, 0); msg = ColorStrings[DefaultColor] + msg; addMessage(NULL, msg); myTank->setNemesis(bestTarget); } } static void setHuntTarget() { // get info about my tank const float c = cosf(-myTank->getAngle()); const float s = sinf(-myTank->getAngle()); const float x0 = myTank->getPosition()[0]; const float y0 = myTank->getPosition()[1]; // initialize best target Player* bestTarget = NULL; float bestDistance = Infinity; bool lockedOn = false; // figure out which tank is centered in my sights for (int i = 0; i < curMaxPlayers; i++) { if (!player[i] || !player[i]->isAlive()) continue; // compute position in my local coordinate system const float* pos = player[i]->getPosition(); const float x = c * (pos[0] - x0) - s * (pos[1] - y0); const float y = s * (pos[0] - x0) + c * (pos[1] - y0); // ignore things behind me if (x < 0.0f) continue; // get distance and sin(angle) from directly forward const float d = hypotf(x, y); const float a = fabsf(y / d); // see if it's inside lock-on angle (if we're trying to lock-on) if (a < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) && // about 8.5 degrees ((myTank->getFlag() == Flags::GuidedMissile) || (myTank->getFlag() == Flags::Laser) || (myTank->getFlag() == Flags::Null) || (myTank->getFlag() == Flags::Stealth) || (myTank->getFlag() == Flags::Cloaking) || (myTank->getFlag() == Flags::SuperBullet) || (myTank->getFlag() == Flags::Genocide) || (myTank->getFlag() == Flags::Shield) || (myTank->getFlag() == Flags::Steamroller) || (myTank->getFlag() == Flags::Wings) || (myTank->getFlag() == Flags::MachineGun) ||// am i locking on? !player[i]->isPaused() && // can't lock on paused !player[i]->isNotResponding() && // can't lock on not responding d < bestDistance)) { // is it better? bestTarget = player[i]; bestDistance = d; lockedOn = true; } else if (a < BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE) && // about 17 degrees ((player[i]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer)) && // can't "see" stealth unless have seer d < bestDistance && !lockedOn) { // is it better? bestTarget = player[i]; bestDistance = d; } } if (!bestTarget) return; if (bestTarget->isHunted() && myTank->getFlag() != Flags::Blindness && myTank->getFlag() != Flags::Colorblindness) { if (myTank->getTarget() == NULL) { // Don't interfere with GM lock display std::string msg("SPOTTED: "); msg += bestTarget->getCallSign(); msg += " ("; msg += Team::getName(bestTarget->getTeam()); if (bestTarget->getFlag() != Flags::Null) { msg += ") with "; msg += bestTarget->getFlag()->flagName; } else { msg += ")"; } hud->setAlert(1, msg.c_str(), 2.0f, 0); } if (!pulse.isOn()) { const float* bestTargetPosition = bestTarget->getPosition(); playWorldSound(SFX_HUNT, bestTargetPosition); pulse.setClock(1.0f); } } } static void updateDaylight(double offset, SceneRenderer& renderer) { static const double SecondsInDay = 86400.0; // update sun, moon & sky renderer.setTimeOfDay(unixEpoch + offset / SecondsInDay); } #ifdef ROBOT // // some robot stuff // static std::vector obstacleList; // for robots static void addObstacle(std::vector& rgnList, const Obstacle& obstacle) { float p[4][2]; const float* c = obstacle.getPosition(); const float tankRadius = BZDBCache::tankRadius; if (BZDBCache::tankHeight < c[2]) return; const float a = obstacle.getRotation(); const float w = obstacle.getWidth() + tankRadius; const float h = obstacle.getBreadth() + tankRadius; const float xx = w * cosf(a); const float xy = w * sinf(a); const float yx = -h * sinf(a); const float yy = h * cosf(a); p[0][0] = c[0] - xx - yx; p[0][1] = c[1] - xy - yy; p[1][0] = c[0] + xx - yx; p[1][1] = c[1] + xy - yy; p[2][0] = c[0] + xx + yx; p[2][1] = c[1] + xy + yy; p[3][0] = c[0] - xx + yx; p[3][1] = c[1] - xy + yy; int numRegions = rgnList.size(); for (int k = 0; k < numRegions; k++) { BzfRegion* region = rgnList[k]; int side[4]; if ((side[0] = region->classify(p[0], p[1])) == 1 || (side[1] = region->classify(p[1], p[2])) == 1 || (side[2] = region->classify(p[2], p[3])) == 1 || (side[3] = region->classify(p[3], p[0])) == 1) continue; if (side[0] == -1 && side[1] == -1 && side[2] == -1 && side[3] == -1) { rgnList[k] = rgnList[numRegions-1]; rgnList[numRegions-1] = rgnList[rgnList.size()-1]; rgnList.pop_back(); numRegions--; k--; delete region; continue; } for (int j = 0; j < 4; j++) { if (side[j] == -1) continue; // to inside // split const float* p1 = p[j]; const float* p2 = p[(j+1)&3]; BzfRegion* newRegion = region->orphanSplitRegion(p2, p1); if (!newRegion) continue; // no split if (region != rgnList[k]) rgnList.push_back(region); region = newRegion; } if (region != rgnList[k]) delete region; } } static void makeObstacleList() { const float tankRadius = BZDBCache::tankRadius; int i; const int count = obstacleList.size(); for (i = 0; i < count; i++) delete obstacleList[i]; obstacleList.clear(); // FIXME -- shouldn't hard code game area float gameArea[4][2]; float worldSize = BZDBCache::worldSize; gameArea[0][0] = -0.5f * worldSize + tankRadius; gameArea[0][1] = -0.5f * worldSize + tankRadius; gameArea[1][0] = 0.5f * worldSize - tankRadius; gameArea[1][1] = -0.5f * worldSize + tankRadius; gameArea[2][0] = 0.5f * worldSize - tankRadius; gameArea[2][1] = 0.5f * worldSize - tankRadius; gameArea[3][0] = -0.5f * worldSize + tankRadius; gameArea[3][1] = 0.5f * worldSize - tankRadius; obstacleList.push_back(new BzfRegion(4, gameArea)); const ObstacleList& boxes = OBSTACLEMGR.getBoxes(); const int numBoxes = boxes.size(); for (i = 0; i < numBoxes; i++) { addObstacle(obstacleList, *boxes[i]); } const ObstacleList& pyramids = OBSTACLEMGR.getPyrs(); const int numPyramids = pyramids.size(); for (i = 0; i < numPyramids; i++) { addObstacle(obstacleList, *pyramids[i]); } const ObstacleList& teleporters = OBSTACLEMGR.getTeles(); const int numTeleporters = teleporters.size(); for (i = 0; i < numTeleporters; i++) { addObstacle(obstacleList, *teleporters[i]); } const ObstacleList& meshes = OBSTACLEMGR.getMeshes(); const int numMeshes = meshes.size(); for (i = 0; i < numMeshes; i++) { addObstacle(obstacleList, *meshes[i]); } if (World::getWorld()->allowTeamFlags()) { const ObstacleList& bases = OBSTACLEMGR.getBases(); const int numBases = bases.size(); for (i = 0; i < numBases; i++) { const BaseBuilding* base = (const BaseBuilding*) bases[i]; if ((base->getHeight() != 0.0f) || (base->getPosition()[2] != 0.0f)) { addObstacle(obstacleList, *base); } } } } static void setRobotTarget(RobotPlayer* robot) { Player* bestTarget = NULL; float bestPriority = 0.0f; for (int j = 0; j < curMaxPlayers; j++) if (player[j] && player[j]->getId() != robot->getId() && player[j]->isAlive() && ((robot->getTeam() == RogueTeam && !World::getWorld()->allowRabbit()) || player[j]->getTeam() != robot->getTeam())) { if (player[j]->isPhantomZoned() && !robot->isPhantomZoned()) continue; if (World::getWorld()->allowTeamFlags() && (robot->getTeam() == RedTeam && player[j]->getFlag() == Flags::RedTeam) || (robot->getTeam() == GreenTeam && player[j]->getFlag() == Flags::GreenTeam) || (robot->getTeam() == BlueTeam && player[j]->getFlag() == Flags::BlueTeam) || (robot->getTeam() == PurpleTeam && player[j]->getFlag() == Flags::PurpleTeam)) { bestTarget = player[j]; break; } const float priority = robot->getTargetPriority(player[j]); if (priority > bestPriority) { bestTarget = player[j]; bestPriority = priority; } } if (myTank->isAlive() && ((robot->getTeam() == RogueTeam && !World::getWorld()->allowRabbit()) || myTank->getTeam() != robot->getTeam())) { const float priority = robot->getTargetPriority(myTank); if (priority > bestPriority) { bestTarget = myTank; bestPriority = priority; } } robot->setTarget(bestTarget); } static void updateRobots(float dt) { static float newTargetTimeout = 1.0f; static float clock = 0.0f; bool pickTarget = false; int i; // see if we should look for new targets clock += dt; if (clock > newTargetTimeout) { while (clock > newTargetTimeout) { clock -= newTargetTimeout; } pickTarget = true; } // start dead robots for (i = 0; i < numRobots; i++) { if (!gameOver && robots[i] && !robots[i]->isAlive() && !robots[i]->isExploding() && pickTarget) { robotServer[i]->sendAlive(); } } // retarget robots for (i = 0; i < numRobots; i++) { if (robots[i] && robots[i]->isAlive() && (pickTarget || !robots[i]->getTarget() || !robots[i]->getTarget()->isAlive())) { setRobotTarget(robots[i]); } } // do updates for (i = 0; i < numRobots; i++) if (robots[i]) { robots[i]->update(); } } static void checkEnvironment(RobotPlayer* tank) { // skip this if i'm dead or paused if (!tank->isAlive() || tank->isPaused()) return; // see if i've been shot // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; const ShotPath* hit = NULL; float minTime = Infinity; tank->checkHit(myTank, hit, minTime); int i; for (i = 0; i < curMaxPlayers; i++) { if (player[i] && player[i]->getId() != tank->getId()) { tank->checkHit(player[i], hit, minTime); } } // Check Server Shots tank->checkHit( World::getWorld()->getWorldWeapons(), hit, minTime); float waterLevel = World::getWorld()->getWaterLevel(); if (hit) { // i got shot! terminate the shot that hit me and blow up. // force shot to terminate locally immediately (no server round trip); // this is to ensure that we don't get shot again by the same shot // after dropping our shield flag. if (hit->isStoppedByHit()) lookupServer(tank)->sendEndShot(hit->getPlayer(), hit->getShotId(), 1); FlagType* killerFlag = hit->getFlag(); bool stopShot; if (killerFlag == Flags::Thief) { if (tank->getFlag() != Flags::Null) { serverLink->sendTransferFlag(tank->getId(), hit->getPlayer()); } stopShot = true; } else { stopShot = gotBlowedUp(tank, GotShot, hit->getPlayer(), hit); } if (stopShot || hit->isStoppedByHit()) { Player* hitter = lookupPlayer(hit->getPlayer()); if (hitter) hitter->endShot(hit->getShotId()); } } // if not dead yet, see if i'm sitting on death // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; else if (tank->getDeathPhysicsDriver() >= 0) { gotBlowedUp(tank, DeathTouch, ServerPlayer, NULL, tank->getDeathPhysicsDriver()); } // if not dead yet, see if the robot dropped below the death level // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; else if ((waterLevel > 0.0f) && (tank->getPosition()[2] <= waterLevel)) { gotBlowedUp(tank, WaterDeath, ServerPlayer); } // if not dead yet, see if i got run over by the steamroller // skip this if i'm alive if (!BZDB.isTrue("displayConsole")) if (myTank->isAlive()) return; else { bool dead = false; const float* myPos = tank->getPosition(); const float myRadius = tank->getRadius(); if (((myTank->getFlag() == Flags::Steamroller) || ((tank->getFlag() == Flags::Burrow) && myTank->isAlive() && !myTank->isPhantomZoned())) && !myTank->isPaused()) { const float* pos = myTank->getPosition(); if (pos[2] >= 0.0f) { const float radius = myRadius + (BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * myTank->getRadius()); const float distSquared = hypotf(hypotf(myPos[0] - pos[0], myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f); if (distSquared < radius) { gotBlowedUp(tank, GotRunOver, myTank->getId()); dead = true; } } } for (i = 0; !dead && i < curMaxPlayers; i++) { if (player[i] && !player[i]->isPaused() && ((player[i]->getFlag() == Flags::Steamroller) || ((tank->getFlag() == Flags::Burrow) && player[i]->isAlive() && !player[i]->isPhantomZoned()))) { const float* pos = player[i]->getPosition(); if (pos[2] < 0.0f) continue; const float radius = myRadius + (BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * player[i]->getRadius()); const float distSquared = hypotf(hypotf(myPos[0] - pos[0], myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f); if (distSquared < radius) { gotBlowedUp(tank, GotRunOver, player[i]->getId()); dead = true; } } } } } static void checkEnvironmentForRobots() { for (int i = 0; i < numRobots; i++) if (robots[i]) checkEnvironment(robots[i]); } static void sendRobotUpdates() { for (int i = 0; i < numRobots; i++) if (robots[i] && robotServer[i] && robots[i]->isDeadReckoningWrong()) { robotServer[i]->sendPlayerUpdate(robots[i]); } } static void addRobots() { uint16_t code, len; char msg[MaxPacketLen]; char callsign[CallSignLen]; int j; for (j = 0; j < numRobots; j++) if (robotServer[j]) { snprintf(callsign, CallSignLen, "%s%2.2d", myTank->getCallSign(), j); robots[j] = new RobotPlayer(robotServer[j]->getId(), callsign, robotServer[j], myTank->getEmailAddress()); robots[j]->setTeam(AutomaticTeam); robotServer[j]->sendEnter(ComputerPlayer, robots[j]->getTeam(), robots[j]->getCallSign(), robots[j]->getEmailAddress(), ""); } for (j = 0; j < numRobots; j++) { // wait for response if (robotServer[j] && robotServer[j]->read(code, len, msg, -1) < 0 || code != MsgAccept) { delete robots[j]; delete robotServer[j]; robots[j] = NULL; robotServer[j] = NULL; } } int k; // packing for (k = 0, j = 0; j < numRobots; j++) { if (k != j) { robotServer[k] = robotServer[j]; robots[k] = robots[j]; } if (robotServer[j]) k++; } numRobots = k; if (numRobots > 0) { makeObstacleList(); RobotPlayer::setObstacleList(&obstacleList); } } #endif static void setTankFlags() { // scan through flags and, for flags on // tanks, tell the tank about its flag. const int maxFlags = world->getMaxFlags(); for (int i = 0; i < maxFlags; i++) { const Flag& flag = world->getFlag(i); if (flag.status == FlagOnTank) { for (int j = 0; j < curMaxPlayers; j++) { if (player[j] && player[j]->getId() == flag.owner) { player[j]->setFlag(flag.type); break; } } } } } static void enteringServer(void *buf) { // the server sends back the team the player was joined to void *tmpbuf = buf; uint16_t team, type; tmpbuf = nboUnpackUShort(tmpbuf, type); tmpbuf = nboUnpackUShort(tmpbuf, team); // if server assigns us a different team, display a message std::string teamMsg; if (myTank->getTeam() != AutomaticTeam) { teamMsg = TextUtils::format("%s team was unavailable, you were joined ", Team::getName(myTank->getTeam())); if ((TeamColor)team == ObserverTeam) { teamMsg += "as an Observer"; } else { teamMsg += TextUtils::format("to the %s", Team::getName((TeamColor)team)); } } else { if ((TeamColor)team == ObserverTeam) { teamMsg = "You were joined as an observer"; } else { if (team != RogueTeam) teamMsg = TextUtils::format("You joined the %s", Team::getName((TeamColor)team)); else teamMsg = TextUtils::format("You joined as a %s", Team::getName((TeamColor)team)); } } if (myTank->getTeam() != (TeamColor)team) { myTank->setTeam((TeamColor)team); hud->setAlert(1, teamMsg.c_str(), 8.0f, (TeamColor)team==ObserverTeam?true:false); addMessage(NULL, teamMsg.c_str(), 3, true); } bool rabbitMode = World::getWorld()->allowRabbit(); // observer colors are actually cyan, make them black const bool observer = (myTank->getTeam() == ObserverTeam); const GLfloat* borderColor; if (observer) { static const GLfloat black[4] = {0.0f, 0.0f, 0.0f, 1.0f}; borderColor = black; } else { borderColor = Team::getRadarColor(myTank->getTeam(), rabbitMode); } controlPanel->setControlColor(borderColor); radar->setControlColor(borderColor); if ((myTank->getTeam() == ObserverTeam) || devDriving) { ROAM.setMode(Roaming::roamViewFP); // ROAM.resetCamera(); } else { ROAM.setMode(Roaming::roamViewDisabled); } setTankFlags(); // clear now invalid token startupInfo.token[0] = '\0'; // add robot tanks #if defined(ROBOT) addRobots(); #endif // resize background and adjust time (this is needed even if we // don't sync with the server) sceneRenderer->getBackground()->resize(); float syncTime = BZDB.eval(StateDatabase::BZDB_SYNCTIME); if (syncTime < 0.0f) { updateDaylight(epochOffset, *sceneRenderer); } else { epochOffset = (double)syncTime; updateDaylight(epochOffset, *sceneRenderer); } lastEpochOffset = epochOffset; // restore the sound if (savedVolume != -1) { setSoundVolume(savedVolume); savedVolume = -1; } // initialize some other stuff updateNumPlayers(); updateFlag(Flags::Null); updateHighScores(); hud->setHeading(myTank->getAngle()); hud->setAltitude(myTank->getPosition()[2]); hud->setTimeLeft((uint32_t)~0); fireButton = false; firstLife = true; BZDB.setBool("displayMainFlags", true); BZDB.setBool("displayRadarFlags", true); BZDB.setBool("displayRadar", true); BZDB.setBool("displayConsole", true); entered = true; } static void cleanWorldCache() { // setup the world cache limit int cacheLimit = (10 * 1024 * 1024); if (BZDB.isSet("worldCacheLimit")) { const int dbCacheLimit = BZDB.evalInt("worldCacheLimit"); // the old limit was 100 Kbytes, too small if (dbCacheLimit == (100 * 1024)) { BZDB.setInt("worldCacheLimit", cacheLimit); } else { cacheLimit = dbCacheLimit; } } else { BZDB.setInt("worldCacheLimit", cacheLimit); } const std::string worldPath = getCacheDirName(); while (true) { char *oldestFile = NULL; int oldestSize = 0; int totalSize = 0; #ifdef _WIN32 std::string pattern = worldPath + "*.bwc"; WIN32_FIND_DATA findData; HANDLE h = FindFirstFile(pattern.c_str(), &findData); if (h != INVALID_HANDLE_VALUE) { FILETIME oldestTime; while (FindNextFile(h, &findData)) { if ((oldestFile == NULL) || (CompareFileTime(&oldestTime, &findData.ftLastAccessTime) > 0)) { if (oldestFile) { free(oldestFile); } oldestFile = strdup(findData.cFileName); oldestSize = findData.nFileSizeLow; oldestTime = findData.ftLastAccessTime; } totalSize += findData.nFileSizeLow; } FindClose(h); } #else DIR *directory = opendir(worldPath.c_str()); if (directory) { struct dirent* contents; struct stat statbuf; time_t oldestTime = 0; while ((contents = readdir(directory))) { const std::string filename = contents->d_name; const std::string fullname = worldPath + filename; stat(fullname.c_str(), &statbuf); if (S_ISREG(statbuf.st_mode) && (filename.size() > 4) && (filename.substr(filename.size() - 4) == ".bwc")) { if ((oldestFile == NULL) || (statbuf.st_atime < oldestTime)) { if (oldestFile) { free(oldestFile); } oldestFile = strdup(contents->d_name); oldestSize = statbuf.st_size; oldestTime = statbuf.st_atime; } totalSize += statbuf.st_size; } } closedir(directory); } #endif // any valid cache files? if (oldestFile == NULL) { return; } // is the cache small enough? if (totalSize < cacheLimit) { if (oldestFile != NULL) { free(oldestFile); oldestFile = NULL; } return; } // remove the oldest file logDebugMessage(1,"cleanWorldCache: removed %s\n", oldestFile); remove((worldPath + oldestFile).c_str()); free(oldestFile); totalSize -= oldestSize; } } static void markOld(std::string &fileName) { #ifdef _WIN32 FILETIME ft; HANDLE h = CreateFile(fileName.c_str(), FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h != INVALID_HANDLE_VALUE) { SYSTEMTIME st; memset(&st, 0, sizeof(st)); st.wYear = 1900; st.wMonth = 1; st.wDay = 1; SystemTimeToFileTime(&st, &ft); SetFileTime(h, &ft, &ft, &ft); GetLastError(); CloseHandle(h); } #else struct utimbuf times; times.actime = 0; times.modtime = 0; utime(fileName.c_str(), ×); #endif } static void sendFlagNegotiation() { char msg[MaxPacketLen]; FlagTypeMap::iterator i; char *buf = msg; /* Send MsgNegotiateFlags to the server with * the abbreviations for all the flags we support. */ for (i = FlagType::getFlagMap().begin(); i != FlagType::getFlagMap().end(); i++) { buf = (char*) i->second->pack(buf); } serverLink->send(MsgNegotiateFlags, buf - msg, msg); } #if defined(FIXME) && defined(ROBOT) static void saveRobotInfo(Playerid id, void *msg) { for (int i = 0; i < numRobots; i++) if (robots[i]) { void *tmpbuf = msg; uint16_t team, type, wins, losses, tks; char callsign[CallSignLen]; char email[EmailLen]; tmpbuf = nboUnpackUShort(tmpbuf, type); tmpbuf = nboUnpackUShort(tmpbuf, team); tmpbuf = nboUnpackUShort(tmpbuf, wins); tmpbuf = nboUnpackUShort(tmpbuf, losses); tmpbuf = nboUnpackUShort(tmpbuf, tks); tmpbuf = nboUnpackString(tmpbuf, callsign, CallSignLen); tmpbuf = nboUnpackString(tmpbuf, email, EmailLen); std::cerr << "id " << id.port << ':' << id.number << ':' << callsign << ' ' << robots[i]->getId().port << ':' << robots[i]->getId().number << ':' << robots[i]->getCallsign() << std::endl; if (strncmp(robots[i]->getCallSign(), callsign, CallSignLen)) { // check for real robot id char buffer[100]; snprintf(buffer, 100, "id test %p %p %p %8.8x %8.8x\n", robots[i], tmpbuf, msg, *(int *)tmpbuf, *((int *)tmpbuf + 1)); std::cerr << buffer; if (tmpbuf < (char *)msg + len) { PlayerId id; tmpbuf = nboUnpackUByte(tmpbuf, id); robots[i]->id.serverHost = id.serverHost; robots[i]->id.port = id.port; robots[i]->id.number = id.number; robots[i]->server->send(MsgIdAck, 0, NULL); } } } } #endif static void resetServerVar(const std::string& name, void*) { // reset server-side variables if (BZDB.getPermission(name) == StateDatabase::Locked) { const std::string defval = BZDB.getDefault(name); BZDB.set(name, defval); } } void leaveGame() { entered = false; joiningGame = false; // no more radar // radar->setWorld(NULL); // controlPanel->setRadarRenderer(NULL); /* delete radar; radar = NULL; */ #if defined(ROBOT) // shut down robot connections int i; for (i = 0; i < numRobots; i++) { if (robots[i] && robotServer[i]) robotServer[i]->send(MsgExit, 0, NULL); delete robots[i]; delete robotServer[i]; robots[i] = NULL; robotServer[i] = NULL; } numRobots = 0; const int count = obstacleList.size(); for (i = 0; i < count; i++) delete obstacleList[i]; obstacleList.clear(); #endif // my tank goes away const bool sayGoodbye = (myTank != NULL); LocalPlayer::setMyTank(NULL); delete myTank; myTank = NULL; // reset the daylight time const bool syncTime = (BZDB.eval(StateDatabase::BZDB_SYNCTIME) >= 0.0f); const bool fixedTime = BZDB.isSet("fixedTime"); if (syncTime) { // return to the desired user time epochOffset = userTimeEpochOffset; } else if (fixedTime) { // save the current user time userTimeEpochOffset = epochOffset; } else { // revert back to when the client was started? epochOffset = userTimeEpochOffset; } updateDaylight(epochOffset, *sceneRenderer); lastEpochOffset = epochOffset; BZDB.set(StateDatabase::BZDB_SYNCTIME, BZDB.getDefault(StateDatabase::BZDB_SYNCTIME)); // flush downloaded textures (before the BzMaterials are nuked) Downloads::removeTextures(); // delete world World::setWorld(NULL); delete world; world = NULL; teams = NULL; curMaxPlayers = 0; numFlags = 0; player = NULL; // update UI hud->setPlaying(false); hud->setCracks(false); hud->setPlayerHasHighScore(false); hud->setTeamHasHighScore(false); hud->setHeading(0.0f); hud->setAltitude(0.0f); hud->setAltitudeTape(false); // shut down server connection if (sayGoodbye) serverLink->send(MsgExit, 0, NULL); ServerLink::setServer(NULL); delete serverLink; serverLink = NULL; // reset viewpoint float eyePoint[3], targetPoint[3]; eyePoint[0] = 0.0f; eyePoint[1] = 0.0f; eyePoint[2] = 0.0f + BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); targetPoint[0] = eyePoint[0] - 1.0f; targetPoint[1] = eyePoint[1] + 0.0f; targetPoint[2] = eyePoint[2] + 0.0f; sceneRenderer->getViewFrustum().setProjection((float)(60.0 * M_PI / 180.0), NearPlaneNormal, FarPlaneDefault, FarDeepPlaneDefault, mainWindow->getWidth(), mainWindow->getHeight(), mainWindow->getViewHeight()); sceneRenderer->getViewFrustum().setView(eyePoint, targetPoint); // reset some flags gameOver = false; serverError = false; serverDied = false; // delete scene database (after the world has been destroyed) sceneRenderer->setSceneDatabase(NULL); // reset the BZDB variables BZDB.iterate(resetServerVar, NULL); return; } static void joinInternetGame(const struct in_addr *inAddress) { // get server address Address serverAddress(*inAddress); if (serverAddress.isAny()) { HUDDialogStack::get()->setFailedMessage("Server not found"); return; } // check for a local server block ServerAccessList.reload(); std::vector nameAndIp; nameAndIp.push_back(startupInfo.serverName); nameAndIp.push_back(serverAddress.getDotNotation()); if (!ServerAccessList.authorized(nameAndIp)) { HUDDialogStack::get()->setFailedMessage("Server Access Denied Locally"); std::string msg = ColorStrings[WhiteColor]; msg += "NOTE: "; msg += ColorStrings[GreyColor]; msg += "server access is controlled by "; msg += ColorStrings[YellowColor]; msg += ServerAccessList.getFileName(); addMessage(NULL, msg); return; } // open server ServerLink* _serverLink = new ServerLink(serverAddress, startupInfo.serverPort); #if defined(ROBOT) int i, j; for (i = 0, j = 0; i < numRobotTanks; i++) { robotServer[j] = new ServerLink(serverAddress, startupInfo.serverPort); if (!robotServer[j] || robotServer[j]->getState() != ServerLink::Okay) { delete robotServer[j]; continue; } j++; } numRobots = j; #endif serverLink = _serverLink; // assume everything's okay for now serverDied = false; serverError = false; if (!serverLink) { HUDDialogStack::get()->setFailedMessage("Memory error"); return; } // printError("Join Game"); // check server if (serverLink->getState() != ServerLink::Okay) { switch (serverLink->getState()) { case ServerLink::BadVersion: { static char versionError[] = "Incompatible server version XXXXXXXX"; strncpy(versionError + strlen(versionError) - 8, serverLink->getVersion(), 8); HUDDialogStack::get()->setFailedMessage(versionError); break; } // you got banned case ServerLink::Refused:{ const std::string& rejmsg = serverLink->getRejectionMessage(); // add to the HUD std::string msg = ColorStrings[RedColor]; msg += "You have been banned from this server"; HUDDialogStack::get()->setFailedMessage(msg.c_str()); // add to the console msg = ColorStrings[RedColor]; msg += "You have been banned from this server:"; addMessage(NULL, msg); msg = ColorStrings[YellowColor]; msg += rejmsg; addMessage(NULL, msg); break; } case ServerLink::Rejected: // the server is probably full or the game is over. if not then // the server is having network problems. HUDDialogStack::get()->setFailedMessage ("Game is full or over. Try again later."); break; case ServerLink::SocketError: HUDDialogStack::get()->setFailedMessage("Error connecting to server."); break; case ServerLink::CrippledVersion: // can't connect to (otherwise compatible) non-crippled server HUDDialogStack::get()->setFailedMessage ("Cannot connect to full version server."); break; default: HUDDialogStack::get()->setFailedMessage (TextUtils::format ("Internal error connecting to server (error code %d).", serverLink->getState()).c_str()); break; } return; } #ifdef HAVE_KRB5 // Sending our credential to the server ClientAuthentication::sendCredential(*serverLink); #endif // use parallel UDP if desired and using server relay if (startupInfo.useUDPconnection) serverLink->sendUDPlinkRequest(); else printError("No UDP connection, see Options to enable."); HUDDialogStack::get()->setFailedMessage("Connection Established..."); sendFlagNegotiation(); joiningGame = true; scoreboard->huntReset(); GameTime::reset(); } static void addVarToAutoComplete(const std::string& name, void* /*userData*/) { if ((name.size() <= 0) || (name[0] != '_')) { return; // we're skipping "poll" } if (BZDB.getPermission(name) == StateDatabase::Server) { completer.registerWord(name); } return; } static void joinInternetGame2() { justJoined = true; HUDDialogStack::get()->setFailedMessage("Entering game..."); ServerLink::setServer(serverLink); World::setWorld(world); // prep teams teams = world->getTeams(); // prep players curMaxPlayers = 0; player = world->getPlayers(); // reset the autocompleter completer.setDefaults(); BZDB.iterate(addVarToAutoComplete, NULL); // prep flags numFlags = world->getMaxFlags(); // make scene database setSceneDatabase(); mainWindow->getWindow()->yieldCurrent(); // make radar // radar = new RadarRenderer(*sceneRenderer, *world); // mainWindow->getWindow()->yieldCurrent(); radar->setWorld(world); controlPanel->setRadarRenderer(radar); controlPanel->resize(); // make local player myTank = new LocalPlayer(serverLink->getId(), startupInfo.callsign, startupInfo.email); myTank->setTeam(startupInfo.team); LocalPlayer::setMyTank(myTank); if (world->allowRabbit() && myTank->getTeam() != ObserverTeam) myTank->setTeam(RogueTeam); // tell server we want to join serverLink->sendEnter(TankPlayer, myTank->getTeam(), myTank->getCallSign(), myTank->getEmailAddress(), startupInfo.token); startupInfo.token[0] = '\0'; // hopefully it worked! pop all the menus. HUDDialogStack* stack = HUDDialogStack::get(); while (stack->isActive()) stack->pop(); joiningGame = false; } static void renderDialog() { if (HUDDialogStack::get()->isActive()) { const int width = mainWindow->getWidth(); const int height = mainWindow->getHeight(); const int ox = mainWindow->getOriginX(); const int oy = mainWindow->getOriginY(); glScissor(ox, oy, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, width, 0.0, height, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); OpenGLGState::resetState(); HUDDialogStack::get()->render(); glPopMatrix(); } } static void checkDirtyControlPanel(ControlPanel *cp) { if (cp) { if (HUDDialogStack::get()->isActive()) { cp->invalidate(); } } return; } static int getZoomFactor() { if (!BZDB.isSet("displayZoom")) return 1; const int zoom = atoi(BZDB.get("displayZoom").c_str()); if (zoom < 1) return 1; if (zoom > 8) return 8; return zoom; } static void drawUI() { // setup the triangle counts (FIXME: hackish) if (showFPS && showDrawTime) { hud->setFrameTriangleCount(sceneRenderer->getFrameTriangleCount()); // NOTE: the radar triangle count is actually from the previous frame if (radar) { hud->setFrameRadarTriangleCount(radar->getFrameTriangleCount()); } else { hud->setFrameRadarTriangleCount(0); } } else { hud->setFrameTriangleCount(0); hud->setFrameRadarTriangleCount(0); } // update the HUD (player list, alerts) if (hud) { hud->render(*sceneRenderer); } // draw the control panel if (controlPanel) { controlPanel->render(*sceneRenderer); } // draw the radar if (radar) { const bool showBlankRadar = !myTank; const bool observer = myTank && (myTank->getTeam() == ObserverTeam); radar->render(*sceneRenderer, showBlankRadar, observer); } // update the HUD (menus) renderDialog(); return; } // // stuff to draw a frame // static bool trackPlayerShot(Player* target, float* eyePoint, float* targetPoint) { // follow the first shot if (BZDB.isTrue("trackShots")) { const int maxShots = target->getMaxShots(); const ShotPath* sp = NULL; // look for the oldest active shot float remaining = +MAXFLOAT; for (int s = 0; s < maxShots; s++) { const ShotPath* spTmp = target->getShot(s); if (spTmp != NULL) { const float t = float(spTmp->getReloadTime() - (spTmp->getCurrentTime() - spTmp->getStartTime())); if ((t > 0.0f) && (t < remaining)) { sp = spTmp; remaining = t; } } } if (sp != NULL) { const float* pos = sp->getPosition(); const float* vel = sp->getVelocity(); const float speed = sqrtf(vel[0]*vel[0] + vel[1]*vel[1] + vel[2]*vel[2]); if (speed > 0.0f) { const float ilen = 1.0f / speed; const float dir[3] = {ilen * vel[0], ilen * vel[1], ilen * vel[2]}; float topDir[3] = {1.0f, 0.0f, 0.0f}; const float hlen = sqrtf(dir[0]*dir[0] + dir[1]*dir[1]); if (hlen > 0.0f) { topDir[2] = hlen; const float hfactor = -fabsf(dir[2] / hlen); topDir[0] = hfactor * dir[0]; topDir[1] = hfactor * dir[1]; } const float offset = -10.0f; const float tOffset = +2.0f; eyePoint[0] = pos[0] + (offset * dir[0]) + (tOffset * topDir[0]); eyePoint[1] = pos[1] + (offset * dir[1]) + (tOffset * topDir[1]); eyePoint[2] = pos[2] + (offset * dir[2]) + (tOffset * topDir[2]); targetPoint[0] = eyePoint[0] + dir[0]; targetPoint[1] = eyePoint[1] + dir[1]; targetPoint[2] = eyePoint[2] + dir[2]; return true; } } } return false; } static void setupNearPlane() { NearPlane = NearPlaneNormal; const bool showTreads = BZDB.isTrue("showTreads"); if (!showTreads || !myTank) { return; } const Player* tank = myTank; if (ROAM.isRoaming()) { if (ROAM.getMode() != Roaming::roamViewFP) { return; } if (!devDriving) { tank = ROAM.getTargetTank(); } } if (tank == NULL) { return; } const float halfLength = 0.5f * BZDBCache::tankLength; const float length = tank->getDimensions()[1]; if (fabsf(length - halfLength) > 0.1f) { NearPlane = NearPlaneClose; } return; } static void setupFarPlane() { FarPlane = FarPlaneScale * BZDBCache::worldSize; FarPlaneCull = false; FarDeepPlane = FarPlane * FarDeepPlaneScale; const bool mapFog = (BZDB.get(StateDatabase::BZDB_FOGMODE) != "none") && BZDB.isTrue("fogEffect"); float farDist = FarPlane; if (mapFog && (BZDB.get("_cullDist") == "fog") && !BZDB.isTrue("_fogNoSky")) { const float fogMargin = 1.01f; const std::string& fogMode = BZDB.get("_fogMode"); if (fogMode == "linear") { farDist = fogMargin * BZDB.eval("_fogEnd"); } else { const float density = BZDB.eval("_fogDensity"); if (density > 0.0f) { const float fogFactor = 0.01f; if (fogMode == "exp2") { farDist = fogMargin * sqrtf(-logf(fogFactor)) / density; } else { // default to 'exp' farDist = fogMargin * -logf(fogFactor) / density; } } else { // default far plane } } } else { const float dist = BZDB.eval("_cullDist"); if (!isnan(dist) && (dist > 0.0f)) { farDist = dist; } else { // default far plane } } if (farDist < FarPlane) { FarPlane = farDist; FarPlaneCull = true; } return; } void drawFrame(const float dt) { // get view type (constant for entire game) static SceneRenderer::ViewType viewType = sceneRenderer->getViewType(); // get media object static BzfMedia* media = PlatformFactory::getMedia(); static const float defaultPos[3] = { 0.0f, 0.0f, 0.0f }; static const float defaultDir[3] = { 1.0f, 0.0f, 0.0f }; static int frameCount = 0; static float cumTime = 0.0f; const float* myTankPos; const float* myTankDir; GLfloat fov; GLfloat eyePoint[3]; GLfloat targetPoint[3]; int i; checkDirtyControlPanel(controlPanel); if (!unmapped) { // compute fps frameCount++; cumTime += float(dt); if (cumTime >= 2.0) { if (showFPS) hud->setFPS(float(frameCount) / cumTime); cumTime = 0.00000001f; frameCount = 0; } // drift clouds sceneRenderer->getBackground()->addCloudDrift(1.0f * dt, 0.731f * dt); // get tank camera info float muzzleHeight; if (!myTank) { myTankPos = defaultPos; myTankDir = defaultDir; muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); fov = 60.0f; } else { myTankPos = myTank->getPosition(); myTankDir = myTank->getForward(); muzzleHeight = myTank->getMuzzleHeight(); if (myTank->getFlag() == Flags::WideAngle) { fov = 120.0f; } else { fov = BZDB.eval("displayFOV"); } if (viewType == SceneRenderer::ThreeChannel) { fov *= 0.75f; } } fov *= (float)(M_PI / 180.0); // set projection and view eyePoint[0] = myTankPos[0]; eyePoint[1] = myTankPos[1]; eyePoint[2] = myTankPos[2] + muzzleHeight; targetPoint[0] = eyePoint[0] + myTankDir[0]; targetPoint[1] = eyePoint[1] + myTankDir[1]; targetPoint[2] = eyePoint[2] + myTankDir[2]; if (devDriving || ROAM.isRoaming()) { hud->setAltitude(-1.0f); float roamViewAngle; const Roaming::RoamingCamera* roam = ROAM.getCamera(); if (!(ROAM.getMode() == Roaming::roamViewFree) && (ROAM.getTargetTank() || (devDriving && myTank))) { Player *target; if (!devDriving) { target = ROAM.getTargetTank(); } else { target = myTank; } const float *targetTankDir = target->getForward(); // fixed camera tracking target if (ROAM.getMode() == Roaming::roamViewTrack) { eyePoint[0] = roam->pos[0]; eyePoint[1] = roam->pos[1]; eyePoint[2] = roam->pos[2]; targetPoint[0] = target->getPosition()[0]; targetPoint[1] = target->getPosition()[1]; targetPoint[2] = target->getPosition()[2] + target->getMuzzleHeight(); } // camera following target else if (ROAM.getMode() == Roaming::roamViewFollow) { if (!trackPlayerShot(target, eyePoint, targetPoint)) { eyePoint[0] = target->getPosition()[0] - targetTankDir[0] * 40; eyePoint[1] = target->getPosition()[1] - targetTankDir[1] * 40; eyePoint[2] = target->getPosition()[2] + muzzleHeight * 6; targetPoint[0] = target->getPosition()[0]; targetPoint[1] = target->getPosition()[1]; targetPoint[2] = target->getPosition()[2]; } } // target's view else if (ROAM.getMode() == Roaming::roamViewFP) { if (!trackPlayerShot(target, eyePoint, targetPoint)) { eyePoint[0] = target->getPosition()[0]; eyePoint[1] = target->getPosition()[1]; eyePoint[2] = target->getPosition()[2] + target->getMuzzleHeight(); targetPoint[0] = eyePoint[0] + targetTankDir[0]; targetPoint[1] = eyePoint[1] + targetTankDir[1]; targetPoint[2] = eyePoint[2] + targetTankDir[2]; hud->setAltitude(target->getPosition()[2]); } } // track team flag else if (ROAM.getMode() == Roaming::roamViewFlag) { Flag* targetFlag = ROAM.getTargetFlag(); eyePoint[0] = roam->pos[0]; eyePoint[1] = roam->pos[1]; eyePoint[2] = roam->pos[2]; targetPoint[0] = targetFlag->position[0]; targetPoint[1] = targetFlag->position[1]; targetPoint[2] = targetFlag->position[2]; if (targetFlag->status != FlagOnTank) { targetPoint[2] += muzzleHeight; } else { targetPoint[2] -= (BZDBCache::tankHeight - BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT)); } } roamViewAngle = (float) (atan2(targetPoint[1]-eyePoint[1], targetPoint[0]-eyePoint[0]) * 180.0f / M_PI); } // free Roaming else { float dir[3]; dir[0] = cosf((float)(roam->phi * M_PI / 180.0)) * cosf((float)(roam->theta * M_PI / 180.0)); dir[1] = cosf((float)(roam->phi * M_PI / 180.0)) * sinf((float)(roam->theta * M_PI / 180.0)); dir[2] = sinf((float)(roam->phi * M_PI / 180.0)); eyePoint[0] = roam->pos[0]; eyePoint[1] = roam->pos[1]; eyePoint[2] = roam->pos[2]; targetPoint[0] = eyePoint[0] + dir[0]; targetPoint[1] = eyePoint[1] + dir[1]; targetPoint[2] = eyePoint[2] + dir[2]; roamViewAngle = roam->theta; } if (!devDriving) { float virtPos[] = {eyePoint[0], eyePoint[1], 0}; if (myTank) { myTank->move(virtPos, (float)(roamViewAngle * M_PI / 180.0)); } } fov = (float)(roam->zoom * M_PI / 180.0); moveSoundReceiver(eyePoint[0], eyePoint[1], eyePoint[2], 0.0, false); } // only use a close plane for drawing in the // cockpit, and even then only for odd sized tanks setupNearPlane(); // based on fog and _cullDist setupFarPlane(); ViewFrustum& viewFrustum = sceneRenderer->getViewFrustum(); viewFrustum.setProjection(fov, NearPlane, FarPlane, FarDeepPlane, mainWindow->getWidth(), mainWindow->getHeight(), mainWindow->getViewHeight()); viewFrustum.setFarPlaneCull(FarPlaneCull); viewFrustum.setView(eyePoint, targetPoint); // add dynamic nodes SceneDatabase* scene = sceneRenderer->getSceneDatabase(); if (scene && myTank) { const bool seerView = (myTank->getFlag() == Flags::Seer); const bool showTreads = BZDB.isTrue("showTreads"); // add my tank if required const bool inCockpit = (!devDriving || (ROAM.getMode() == Roaming::roamViewFP)); const bool showMyTreads = showTreads || (devDriving && (ROAM.getMode() != Roaming::roamViewFP)); myTank->addToScene(scene, myTank->getTeam(), inCockpit, seerView, showMyTreads, showMyTreads /*showIDL*/); // add my shells myTank->addShots(scene, false); // add server shells if (world) { world->getWorldWeapons()->addShots(scene, false); } // add antidote flag myTank->addAntidote(scene); // add flags world->addFlags(scene, seerView); // add other tanks and shells for (i = 0; i < curMaxPlayers; i++) { if (player[i]) { const bool colorblind = (myTank->getFlag() == Flags::Colorblindness); player[i]->addShots(scene, colorblind); TeamColor effectiveTeam = RogueTeam; if (!colorblind){ if ((player[i]->getFlag() == Flags::Masquerade) && (myTank->getFlag() != Flags::Seer) && (myTank->getTeam() != ObserverTeam)) { effectiveTeam = myTank->getTeam(); } else { effectiveTeam = player[i]->getTeam(); } } const bool inCockpt = ROAM.isRoaming() && !devDriving && (ROAM.getMode() == Roaming::roamViewFP) && ROAM.getTargetTank() && (ROAM.getTargetTank()->getId() == i); const bool showPlayer = !inCockpt || showTreads; // add player tank if required player[i]->addToScene(scene, effectiveTeam, inCockpt, seerView, showPlayer, showPlayer /*showIDL*/); } } // add explosions addExplosions(scene); // if inside a building, add some eighth dimension scene nodes. const std::vector& list = myTank->getInsideBuildings(); for (unsigned int n = 0; n < list.size(); n++) { const Obstacle* obs = list[n]; const int nodeCount = obs->getInsideSceneNodeCount(); SceneNode** nodeList = obs->getInsideSceneNodeList(); for (int o = 0; o < nodeCount; o++) { scene->addDynamicNode(nodeList[o]); } } } // turn blanking and inversion on/off as appropriate // turn on scene dimming when showing menu, when we're dead // and no longer exploding, or when we are in a building. bool insideDim = false; if (myTank) { const float hnp = 0.5f * NearPlane; // half near plane distance const float* eye = viewFrustum.getEye(); const float* dir = viewFrustum.getDirection(); float clipPos[3]; clipPos[0] = eye[0] + (dir[0] * hnp); clipPos[1] = eye[1] + (dir[1] * hnp); clipPos[2] = eye[2]; const Obstacle* obs; obs = world->inBuilding(clipPos, myTank->getAngle(), hnp, 0.0f, 0.0f); if (obs != NULL) { insideDim = true; } } sceneRenderer->setDim(HUDDialogStack::get()->isActive() || insideDim || (myTank && !ROAM.isRoaming() && !devDriving) && !myTank->isAlive() && !myTank->isExploding()); // turn on panel dimming when showing the menu (both radar and chat) if (HUDDialogStack::get()->isActive()) { if (controlPanel) { controlPanel->setDimming(0.8f); } if (radar) { radar->setDimming(0.8f); } } else { if (controlPanel) { controlPanel->setDimming(0.0f); } if (radar) { radar->setDimming(0.0f); } } // set hud state hud->setDim(HUDDialogStack::get()->isActive()); hud->setPlaying(myTank && (myTank->isAlive() && !myTank->isPaused())); hud->setRoaming(ROAM.isRoaming()); hud->setCracks(myTank && !firstLife && !justJoined && !myTank->isAlive()); // get frame start time if (showDrawTime) { #if defined(DEBUG_RENDERING) // get an accurate measure of frame time (at expense of frame rate) if (BZDB.isTrue("glFinish")) { glFinish(); } #endif media->stopwatch(true); } // so observers can have enhanced radar if (ROAM.isRoaming() && myTank && !devDriving) { if (ROAM.getMode() == Roaming::roamViewFP && ROAM.getTargetTank()) myTank->setZpos(ROAM.getTargetTank()->getPosition()[2]); else myTank->setZpos(ROAM.getCamera()->pos[2]); } // draw frame if (viewType == SceneRenderer::ThreeChannel) { // draw center channel sceneRenderer->render(false); drawUI(); // set up for drawing left channel mainWindow->setQuadrant(MainWindow::LowerLeft); // FIXME -- this assumes up is along +z const float cFOV = cosf(fov); const float sFOV = sinf(fov); targetPoint[0] = eyePoint[0] + cFOV*myTankDir[0] - sFOV*myTankDir[1]; targetPoint[1] = eyePoint[1] + cFOV*myTankDir[1] + sFOV*myTankDir[0]; targetPoint[2] = eyePoint[2] + myTankDir[2]; viewFrustum.setView(eyePoint, targetPoint); // draw left channel sceneRenderer->render(false, true, true); // set up for drawing right channel mainWindow->setQuadrant(MainWindow::LowerRight); // FIXME -- this assumes up is along +z targetPoint[0] = eyePoint[0] + cFOV*myTankDir[0] + sFOV*myTankDir[1]; targetPoint[1] = eyePoint[1] + cFOV*myTankDir[1] - sFOV*myTankDir[0]; targetPoint[2] = eyePoint[2] + myTankDir[2]; viewFrustum.setView(eyePoint, targetPoint); // draw right channel sceneRenderer->render(true, true, true); #if defined(DEBUG_RENDERING) // set up for drawing rear channel mainWindow->setQuadrant(MainWindow::UpperLeft); // FIXME -- this assumes up is along +z targetPoint[0] = eyePoint[0] - myTankDir[0]; targetPoint[1] = eyePoint[1] - myTankDir[1]; targetPoint[2] = eyePoint[2] + myTankDir[2]; viewFrustum.setView(eyePoint, targetPoint); // draw rear channel sceneRenderer->render(true, true, true); #endif // back to center channel mainWindow->setQuadrant(MainWindow::UpperRight); } else if (viewType == SceneRenderer::Stacked) { float EyeDisplacement = 0.25f * BZDBCache::tankWidth; float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE); if (BZDB.isSet("eyesep")) EyeDisplacement = BZDB.eval("eyesep"); if (BZDB.isSet("focal")) FocalPlane = BZDB.eval("focal"); // setup view for left eye viewFrustum.setOffset(EyeDisplacement, FocalPlane); // draw left eye's view sceneRenderer->render(false); drawUI(); // set up view for right eye mainWindow->setQuadrant(MainWindow::UpperHalf); viewFrustum.setOffset(-EyeDisplacement, FocalPlane); // draw right eye's view sceneRenderer->render(true, true); drawUI(); // draw common stuff // back to left channel mainWindow->setQuadrant(MainWindow::LowerHalf); } else if (viewType == SceneRenderer::Stereo) { float EyeDisplacement = 0.25f * BZDBCache::tankWidth; float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE); if (BZDB.isSet("eyesep")) EyeDisplacement = BZDB.eval("eyesep"); if (BZDB.isSet("focal")) FocalPlane = BZDB.eval("focal"); // setup view for left eye #ifdef USE_GL_STEREO glDrawBuffer(GL_BACK_LEFT); #endif viewFrustum.setOffset(EyeDisplacement, FocalPlane); // draw left eye's view sceneRenderer->render(false); #ifndef USE_GL_STEREO drawUI(); #endif // set up view for right eye #ifdef USE_GL_STEREO glDrawBuffer(GL_BACK_RIGHT); #else mainWindow->setQuadrant(MainWindow::UpperLeft); #endif viewFrustum.setOffset(-EyeDisplacement, FocalPlane); // draw right eye's view sceneRenderer->render(true, true); #ifndef USE_GL_STEREO drawUI(); #endif // draw common stuff #ifdef USE_GL_STEREO glDrawBuffer(GL_BACK); drawUI(); #endif #ifndef USE_GL_STEREO // back to left channel mainWindow->setQuadrant(MainWindow::UpperRight); #endif } else if (viewType == SceneRenderer::Anaglyph) { float EyeDisplacement = 0.25f * BZDBCache::tankWidth; float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE); if (BZDB.isSet("eyesep")) EyeDisplacement = BZDB.eval("eyesep"); if (BZDB.isSet("focal")) FocalPlane = BZDB.eval("focal"); // setup view for left eye glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); viewFrustum.setOffset(EyeDisplacement, FocalPlane); // draw left eye's view sceneRenderer->render(false); drawUI(); // set up view for right eye glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE); // for red/blue to somewhat work ... //glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE); viewFrustum.setOffset(-EyeDisplacement, FocalPlane); // draw right eye's view sceneRenderer->render(true, true); drawUI(); } else if (viewType == SceneRenderer::Interlaced) { float EyeDisplacement = 0.25f * BZDBCache::tankWidth; float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE); const int width = mainWindow->getWidth(); const int height = mainWindow->getHeight(); if (BZDB.isSet("eyesep")) EyeDisplacement = BZDB.eval("eyesep"); if (BZDB.isSet("focal")) FocalPlane = BZDB.eval("focal"); if (BZDBCache::stencilShadows) { BZDB.set("stencilShadows", "0"); addMessage(NULL, "Disabled stencilShadows for interlaced mode"); } OpenGLGState::resetState(); // enable stencil test glEnable(GL_STENCIL_TEST); // clear stencil glClearStencil(0); // Clear color and stencil buffer glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // All drawing commands fail the stencil test, and are not // drawn, but increment the value in the stencil buffer. glStencilFunc(GL_NEVER, 0x0, 0x0); glStencilOp(GL_INCR, GL_INCR, GL_INCR); glColor3f(1.0f, 1.0f, 1.0f); for (int y=0;y<=height;y+=2) { glBegin(GL_LINES); glVertex2i(0, y); glVertex2i(width, y); glEnd(); } // draw except where the stencil pattern is 0x1 // do not change the stencil buffer glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // setup view for left eye viewFrustum.setOffset(EyeDisplacement, FocalPlane); // draw left eye's view sceneRenderer->render(false); // draw where the stencil pattern is 0x1 // do not change the stencil buffer glStencilFunc(GL_EQUAL, 0x1, 0x1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // set up view for right eye viewFrustum.setOffset(-EyeDisplacement, FocalPlane); // draw right eye's view sceneRenderer->render(true, true); glStencilFunc(GL_ALWAYS, 0x1, 0x1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); drawUI(); } else { const int zoomFactor = getZoomFactor(); if (zoomFactor != 1) { // draw small out-the-window view mainWindow->setQuadrant(MainWindow::ZoomRegion); const int x = mainWindow->getOriginX(); const int y = mainWindow->getOriginY(); const int w = mainWindow->getWidth(); const int h = mainWindow->getHeight(); const int vh = mainWindow->getViewHeight(); viewFrustum.setProjection(fov, NearPlane, FarPlane, FarDeepPlane, w, h, vh); sceneRenderer->render(); // set entire window mainWindow->setQuadrant(MainWindow::FullWindow); glScissor(mainWindow->getOriginX(), mainWindow->getOriginY(), mainWindow->getWidth(), mainWindow->getHeight()); // set pixel copy destination glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-0.25, (GLdouble)mainWindow->getWidth() - 0.25, -0.25, (GLdouble)mainWindow->getHeight() - 0.25, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glRasterPos2i(0, 0); glPopMatrix(); // zoom small image to entire window glDisable(GL_DITHER); glPixelZoom((float)zoomFactor, (float)zoomFactor); glCopyPixels(x, y, w, h, GL_COLOR); glPixelZoom(1.0f, 1.0f); if (BZDB.isTrue("dither")) glEnable(GL_DITHER); } else { // normal rendering sceneRenderer->render(); } // draw other stuff drawUI(); } // get frame end time if (showDrawTime) { #if defined(DEBUG_RENDERING) // get an accurate measure of frame time (at expense of frame rate) if (BZDB.isTrue("glFinish")) { glFinish(); } #endif hud->setDrawTime((float)media->stopwatch(false)); } // draw a fake cursor if requested. this is mostly intended for // pass through 3D cards that don't have cursor support. if (BZDB.isTrue("fakecursor")) { int mx, my; const int width = mainWindow->getWidth(); const int height = mainWindow->getHeight(); const int ox = mainWindow->getOriginX(); const int oy = mainWindow->getOriginY(); mainWindow->getWindow()->getMouse(mx, my); my = height - my - 1; glScissor(ox, oy, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, width, 0.0, height, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glColor3f(0.0f, 0.0f, 0.0f); glRecti(mx - 8, my - 2, mx - 2, my + 2); glRecti(mx + 2, my - 2, mx + 8, my + 2); glRecti(mx - 2, my - 8, mx + 2, my - 2); glRecti(mx - 2, my + 2, mx + 2, my + 8); glColor3f(1.0f, 1.0f, 1.0f); glRecti(mx - 7, my - 1, mx - 3, my + 1); glRecti(mx + 3, my - 1, mx + 7, my + 1); glRecti(mx - 1, my - 7, mx + 1, my - 3); glRecti(mx - 1, my + 3, mx + 1, my + 7); glPopMatrix(); } mainWindow->getWindow()->swapBuffers(); // remove dynamic nodes from this frame if (scene) { scene->removeDynamicNodes(); } } else { // wait around a little to avoid spinning the CPU when iconified TimeKeeper::sleep(0.05f); } } static void setupRoamingCamera(float dt) { static Roaming::RoamingCamera prevDeltaCamera; static bool inited = false; if (!inited) { memset(&prevDeltaCamera, 0, sizeof(Roaming::RoamingCamera)); inited = true; } Roaming::RoamingCamera deltaCamera; memset(&deltaCamera, 0, sizeof(Roaming::RoamingCamera)); // move roaming camera if (myTank) { bool control = ((shiftKeyStatus & BzfKeyEvent::ControlKey) != 0); bool alt = ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0); bool shift = ((shiftKeyStatus & BzfKeyEvent::ShiftKey) != 0); if (display->hasGetKeyMode()) { display->getModState (shift, control, alt); } if (!control && !shift) { deltaCamera.pos[0] = (float)(4 * myTank->getSpeed()) * BZDBCache::tankSpeed; } if (alt) { deltaCamera.pos[1] = (float)(4 * myTank->getRotation()) * BZDBCache::tankSpeed; } else { deltaCamera.theta = ROAM.getZoom() * (float)myTank->getRotation(); } if (control) { deltaCamera.phi = -2.0f * ROAM.getZoom() / 3.0f * (float)myTank->getSpeed(); } if (shift) { deltaCamera.pos[2] = (float)(-4 * myTank->getSpeed()) * BZDBCache::tankSpeed; } } // adjust for slow keyboard if (BZDB.isTrue("slowKeyboard")) { float st = BZDB.eval("roamSmoothTime"); if (st < 0.1f) { st = 0.1f; } const float at = (dt / st); const float bt = 1.0f - at; deltaCamera.pos[0] = (at * deltaCamera.pos[0]) + (bt * prevDeltaCamera.pos[0]); deltaCamera.pos[1] = (at * deltaCamera.pos[1]) + (bt * prevDeltaCamera.pos[1]); deltaCamera.pos[2] = (at * deltaCamera.pos[2]) + (bt * prevDeltaCamera.pos[2]); deltaCamera.theta = (at * deltaCamera.theta) + (bt * prevDeltaCamera.theta); deltaCamera.phi = (at * deltaCamera.phi) + (bt * prevDeltaCamera.phi); } deltaCamera.zoom = roamDZoom; ROAM.updatePosition(&deltaCamera, dt); // copy the old delta values memcpy(&prevDeltaCamera, &deltaCamera, sizeof(Roaming::RoamingCamera)); return; } static void prepareTheHUD() { // prep the HUD if (myTank) { const float* myPos = myTank->getPosition(); hud->setHeading(myTank->getAngle()); hud->setAltitude(myPos[2]); if (world->allowTeamFlags()) { // markers for my team flag for (int i = 0; i < numFlags; i++) { Flag& flag = world->getFlag(i); if (flag.type->flagTeam != NoTeam) { const float* flagPos = flag.position; float heading = atan2f(flagPos[1] - myPos[1],flagPos[0] - myPos[0]); hud->addMarker(heading, Team::getTankColor(flag.type->flagTeam)); } } } if (myTank->getAntidoteLocation()) { // marker for my antidote flag const GLfloat* antidotePos = myTank->getAntidoteLocation(); float heading = atan2f(antidotePos[1] - myPos[1], antidotePos[0] - myPos[0]); const float antidoteColor[] = {1.0f, 1.0f, 0.0f}; hud->addMarker(heading, antidoteColor); } for (int i = 0; i < numFlags; i++) { Flag& flag = world->getFlag(i); if (flag.type == Flags::Genocide) { const float* flagPos = flag.position; float heading = atan2f(flagPos[1] - myPos[1],flagPos[0] - myPos[0]); hud->addMarker(heading, flag.type->getColor()); } } } return; } static void updatePauseCountdown(float dt) { if (!myTank) { pauseCountdown = 0.0f; } if (pauseCountdown > 0.0f && !myTank->isAlive()) { pauseCountdown = 0.0f; hud->setAlert(1, NULL, 0.0f, true); } if (pauseCountdown > 0.0f) { const int oldPauseCountdown = (int)(pauseCountdown + 0.99f); pauseCountdown -= dt; if (1 || pauseCountdown <= 0.0f) { pauseCountdown = 0.0f; /* make sure it is really safe to pause.. since the server * might make us drop our flag, make sure the player is on the * ground and not in a building. prevents getting kicked * later for being in places we shouldn't without holding the * right flags. */ if (0 && myTank->getLocation() == LocalPlayer::InBuilding) { // custom message when trying to pause while in a building // (could get stuck on un-pause if flag is taken/lost) hud->setAlert(1, "Can't pause while inside a building", 1.0f, false); } else if (0 && myTank->getLocation() == LocalPlayer::InAir) { // custom message when trying to pause when jumping/falling hud->setAlert(1, "Can't pause when you are in the air", 1.0f, false); } else if (0 && myTank->getLocation() != LocalPlayer::OnGround && myTank->getLocation() != LocalPlayer::OnBuilding) { // catch-all message when trying to pause when you should not hud->setAlert(1, "Unable to pause right now", 1.0f, false); } else if (0 && myTank->isPhantomZoned() ) { // custom message when trying to pause while zoned hud->setAlert(1, "Can't pause when you are in the phantom zone", 1.0f, false); } else { // okay, now we pause. first drop any team flag we may have. const FlagType* flagd = myTank->getFlag(); if (flagd->flagTeam != NoTeam) serverLink->sendDropFlag(myTank->getPosition()); if (World::getWorld()->allowRabbit() && (myTank->getTeam() == RabbitTeam)) serverLink->sendNewRabbit(); // now actually pause myTank->setPause(true); hud->setAlert(1, NULL, 0.0f, true); controlPanel->addMessage("Paused"); // turn off the sound if (savedVolume == -1) { savedVolume = getSoundVolume(); setSoundVolume(0); } // ungrab mouse mainWindow->ungrabMouse(); } } else if ((int)(pauseCountdown + 0.99f) != oldPauseCountdown && !pausedByUnmap) { // update countdown alert char msgBuf[40]; sprintf(msgBuf, "Pausing in %d", (int)(pauseCountdown + 0.99f)); hud->setAlert(1, msgBuf, 1.0f, false); } } return; } static void updateDestructCountdown(float dt) { if (!myTank) { destructCountdown = 0.0f; } if (destructCountdown > 0.0f && !myTank->isAlive()) { destructCountdown = 0.0f; hud->setAlert(1, NULL, 0.0f, true); } if (destructCountdown > 0.0f) { const int oldDestructCountdown = (int)(destructCountdown + 0.99f); destructCountdown -= dt; if (1 || destructCountdown <= 0.0f) { destructCountdown = 0.0f; // now actually destruct // now actually destruct gotBlowedUp( myTank, SelfDestruct, myTank->getId() ); hud->setAlert(1, NULL, 0.0f, true); } else if ((int)(destructCountdown + 0.99f) != oldDestructCountdown) { // update countdown alert char msgBuf[40]; sprintf(msgBuf, "Self Destructing in %d", (int)(destructCountdown + 0.99f)); hud->setAlert(1, msgBuf, 1.0f, false); } } return; } // // main playing loop // static void playingLoop() { int i; mainWindow->setZoomFactor(getZoomFactor()); if (BZDB.isTrue("fakecursor")) mainWindow->getWindow()->hideMouse(); // start timing TimeKeeper::setTick(); updateDaylight(epochOffset, *sceneRenderer); worldDownLoader = new WorldDownLoader; // main loop while (!CommandsStandard::isQuit()) { BZDBCache::update(); // set this step game time GameTime::setStepTime(); // get delta time TimeKeeper prevTime = TimeKeeper::getTick(); TimeKeeper::setTick(); const float dt = float(TimeKeeper::getTick() - prevTime); mainWindow->getWindow()->yieldCurrent(); // handle incoming packets doMessages(); // see if the world collision grid needs to be updated if (world) { world->checkCollisionManager(); } mainWindow->getWindow()->yieldCurrent(); // try to join a game if requested. do this *before* handling // events so we do a redraw after the request is posted and // before we actually try to join. if (joinRequested) { // if already connected to a game then first sign off if (myTank) leaveGame(); // get token if we need to (have a password but no token) if ((startupInfo.token[0] == '\0') && (startupInfo.password[0] != '\0')) { ServerList* serverList = new ServerList; serverList->startServerPings(&startupInfo); // wait no more than 10 seconds for a token for (int j = 0; j < 40; j++) { serverList->checkEchos(getStartupInfo()); cURLManager::perform(); if (startupInfo.token[0] != '\0') break; TimeKeeper::sleep(0.25f); } delete serverList; } // don't let the bad token specifier slip through to the server, // just erase it if (strcmp(startupInfo.token, "badtoken") == 0) startupInfo.token[0] = '\0'; ares.queryHost(startupInfo.serverName); waitingDNS = true; // don't try again joinRequested = false; } if (waitingDNS) { fd_set readers, writers; int nfds = -1; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&readers); FD_ZERO(&writers); ares.setFd(&readers, &writers, nfds); nfds = select(nfds + 1, (fd_set*)&readers, (fd_set*)&writers, 0, &timeout); ares.process(&readers, &writers); struct in_addr inAddress; AresHandler::ResolutionStatus status = ares.getHostAddress(&inAddress); if (status == AresHandler::Failed) { HUDDialogStack::get()->setFailedMessage("Server not found"); waitingDNS = false; } else if (status == AresHandler::HbNSucceeded) { // now try connecting joinInternetGame(&inAddress); waitingDNS = false; } } mainWindow->getWindow()->yieldCurrent(); // handle pending events for some small fraction of time clockAdjust = 0.0f; processInputEvents(0.1f); if (mainWindow->haveJoystick()) { static const BzfKeyEvent::Button button_map[] = { BzfKeyEvent::BZ_Button_1, BzfKeyEvent::BZ_Button_2, BzfKeyEvent::BZ_Button_3, BzfKeyEvent::BZ_Button_4, BzfKeyEvent::BZ_Button_5, BzfKeyEvent::BZ_Button_6, BzfKeyEvent::BZ_Button_7, BzfKeyEvent::BZ_Button_8, BzfKeyEvent::BZ_Button_9, BzfKeyEvent::BZ_Button_10, }; static unsigned long old_buttons = 0; const int button_count = countof(button_map); unsigned long new_buttons = mainWindow->getJoyButtonSet(); if (old_buttons != new_buttons) for (int j = 0; j < button_count; j++) { if ((old_buttons & (1<getWindow()->yieldCurrent(); // invoke callbacks callPlayingCallbacks(); mainWindow->getWindow()->yieldCurrent(); // quick out if (CommandsStandard::isQuit()) { break; } // if server died then leave the game (note that this may cause // further server errors but that's okay). if (serverError || (serverLink && serverLink->getState() == ServerLink::Hungup)) { // if we haven't reported the death yet then do so now if (serverDied || (serverLink && serverLink->getState() == ServerLink::Hungup)) { printError("Server has unexpectedly disconnected"); } leaveGame(); } // update time of day -- update sun and sky every few seconds float syncTime = BZDB.eval(StateDatabase::BZDB_SYNCTIME); if (syncTime < 0.0f) { if (!BZDB.isSet("fixedTime")) { epochOffset += (double)dt; } epochOffset += (double)(50.0f * dt * clockAdjust); } else { epochOffset = (double)syncTime; lastEpochOffset += (double)dt; } if (fabs(epochOffset - lastEpochOffset) >= 4.0) { updateDaylight(epochOffset, *sceneRenderer); lastEpochOffset = epochOffset; } // update the wind if (world) { world->updateWind(dt); } // move roaming camera if (ROAM.isRoaming()) { setupRoamingCamera(dt); ROAM.buildRoamingLabel(); } // update test video format timer if (testVideoFormatTimer > 0.0f) { testVideoFormatTimer -= dt; if (testVideoFormatTimer <= 0.0f) { testVideoFormatTimer = 0.0f; setVideoFormat(testVideoPrevFormat); } } // update the countdowns updatePauseCountdown(dt); updateDestructCountdown(dt); // notify if input changed if ((myTank != NULL) && (myTank->queryInputChange() == true)) { controlPanel->addMessage( LocalPlayer::getInputMethodName(myTank->getInputMethod()) + " movement"); } // update other tank's shots for (i = 0; i < curMaxPlayers; i++) { if (player[i]) { player[i]->updateShots(dt); } } // update servers shots const World *_world = World::getWorld(); if (_world) { _world->getWorldWeapons()->updateShots(dt); } // update track marks (before any tanks are moved) TrackMarks::update(dt); // do dead reckoning on remote players for (i = 0; i < curMaxPlayers; i++) { if (player[i]) { const bool wasNotResponding = player[i]->isNotResponding(); player[i]->doDeadReckoning(); const bool isNotResponding = player[i]->isNotResponding(); if (!wasNotResponding && isNotResponding) { addMessage(player[i], "not responding"); } else if (wasNotResponding && !isNotResponding) { addMessage(player[i], "okay"); } } } // do motion if (myTank) { if (myTank->isAlive() && !myTank->isPaused()) { doMotion(); if (scoreboard->getHuntState()==ScoreboardRenderer::HUNT_ENABLED) { setHuntTarget(); //spot hunt target } if (myTank->getTeam() != ObserverTeam && ((fireButton && myTank->getFlag() == Flags::MachineGun) || (myTank->getFlag() == Flags::TriggerHappy))) { myTank->fireShot(); } } else { int mx, my; mainWindow->getMousePosition(mx, my); } myTank->update(); } #ifdef ROBOT if (entered) { updateRobots(dt); } #endif // check for flags and hits checkEnvironment(); #ifdef ROBOT if (entered) { checkEnvironmentForRobots(); } #endif // adjust properties based on flags (dimensions, cloaking, etc...) if (myTank) { myTank->updateTank(dt, true); } for (i = 0; i < curMaxPlayers; i++) { if (player[i]) { player[i]->updateTank(dt, false); } } // reposition flags updateFlags(dt); // update explosion animations updateExplosions(dt); // update mesh animations if (world) { world->updateAnimations(dt); } // prep the HUD prepareTheHUD(); // draw the frame drawFrame(dt); // play the sounds updateSound(); double heartbeatTime = 30.0f; bool sendUpdate = myTank && myTank->isDeadReckoningWrong(); if ( myTank && myTank->getTeam() == ObserverTeam ) { if (BZDB.isTrue("sendObserverHeartbeat")) { if (BZDB.isSet("observerHeartbeat")) heartbeatTime = BZDB.eval("observerHeartbeat"); if (lastObserverUpdateTime + heartbeatTime < TimeKeeper::getCurrent().getSeconds()) { lastObserverUpdateTime = TimeKeeper::getCurrent().getSeconds(); sendUpdate = true; } else sendUpdate = false; } else sendUpdate = false; } // send my data if ( sendUpdate) { // also calls setDeadReckoning() serverLink->sendPlayerUpdate(myTank); } #ifdef ROBOT if (entered) { sendRobotUpdates(); } #endif FlagSceneNode::freeFlag(); cURLManager::perform(); // check if we are waiting for initial texture downloading if (Downloads::requestFinalized()) { // downloading is terminated. go! Downloads::finalizeDownloads(); if (downloadingInitialTexture) { joinInternetGame2(); downloadingInitialTexture = false; } else { setSceneDatabase(); } } // limit the fps to save battery life by minimizing cpu usage if (BZDB.isTrue("saveEnergy")) { static TimeKeeper lastTime = TimeKeeper::getCurrent(); const float fpsLimit = BZDB.eval("fpsLimit"); if ((fpsLimit >= 1.0f) && !isnan(fpsLimit)) { const float elapsed = float(TimeKeeper::getCurrent() - lastTime); if (elapsed > 0.0f) { const float period = (1.0f / fpsLimit); const float remaining = (period - elapsed); if (remaining > 0.0f) { TimeKeeper::sleep(remaining); } } } lastTime = TimeKeeper::getCurrent(); } // end energy saver check } // end main client loop delete worldDownLoader; // restore the sound. if we don't do this then we'll save the // wrong volume when we dump out the configuration file if the // app exits when the game is paused. if (savedVolume != -1) { setSoundVolume(savedVolume); savedVolume = -1; } // hide window mainWindow->showWindow(false); } // // game initialization // static float timeConfiguration(bool useZBuffer) { // prepare depth buffer if requested BZDB.set("zbuffer","1" ); if (useZBuffer) { glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); } // use glFinish() to get accurate timings //glFinish(); TimeKeeper startTime = TimeKeeper::getCurrent(); sceneRenderer->setExposed(); sceneRenderer->render(); // glFinish(); TimeKeeper endTime = TimeKeeper::getCurrent(); // turn off depth buffer if (useZBuffer) glDisable(GL_DEPTH_TEST); return float(endTime - startTime); } static void timeConfigurations() { static const float MaxFrameTime = 0.050f; // seconds TextureManager& tm = TextureManager::instance(); // ignore results of first test. OpenGL could be doing lazy setup. BZDB.set("blend", "0"); BZDB.set("smooth", "0"); BZDB.set("lighting", "0"); BZDB.set("texture", "0"); sceneRenderer->setQuality(0); BZDB.set("dither", "1"); BZDB.set("shadows", "0"); BZDB.set("radarStyle", "0"); tm.setMaxFilter(OpenGLTexture::Off); timeConfiguration(true); // time lowest quality with and without blending. some systems // stipple very slowly even though everything else is fast. we // don't want to conclude the system is slow because of stippling. printError(" lowest quality"); const float timeNoBlendNoZ = timeConfiguration(false); const float timeNoBlendZ = timeConfiguration(true); BZDB.set("blend", "1"); const float timeBlendNoZ = timeConfiguration(false); const float timeBlendZ = timeConfiguration(true); if (timeNoBlendNoZ > MaxFrameTime && timeNoBlendZ > MaxFrameTime && timeBlendNoZ > MaxFrameTime && timeBlendZ > MaxFrameTime) { if (timeNoBlendNoZ < timeNoBlendZ && timeNoBlendNoZ < timeBlendNoZ && timeNoBlendNoZ < timeBlendZ) { // no depth, no blending definitely fastest BZDB.set("zbuffer", "1"); BZDB.set("blend", "1"); } if (timeNoBlendZ < timeBlendNoZ && timeNoBlendZ < timeBlendZ) { // no blending faster than blending BZDB.set("zbuffer", "1"); BZDB.set("blend", "1"); } if (timeBlendNoZ < timeBlendZ) { // blending faster than depth BZDB.set("zbuffer", "1"); BZDB.set("blend", "1"); } // blending and depth faster than without either BZDB.set("zbuffer", "1"); BZDB.set("blend", "1"); return; } // leave blending on if blending clearly faster than stippling if (timeBlendNoZ > timeNoBlendNoZ || timeBlendNoZ > timeNoBlendZ && timeBlendZ > timeNoBlendNoZ || timeBlendZ > timeNoBlendZ) { BZDB.set("blend", "0"); } // try texturing. if it's too slow then fall back to // lowest quality and return. tm.setMaxFilter(OpenGLTexture::Nearest); BZDB.set("texture", tm.getMaxFilterName()); sceneRenderer->setQuality(1); printError(" lowest quality with texture"); if (timeConfiguration(false) > MaxFrameTime || timeConfiguration(true) > MaxFrameTime) { BZDB.set("texture", "0"); tm.setMaxFilter(OpenGLTexture::Off); sceneRenderer->setQuality(0); return; } // everything printError(" full quality"); BZDB.set("blend", "1"); BZDB.set("smooth", "1"); BZDB.set("lighting", "1"); tm.setMaxFilter(OpenGLTexture::LinearMipmapLinear); BZDB.set("texture", tm.getMaxFilterName()); sceneRenderer->setQuality(2); BZDB.set("dither", "1"); BZDB.set("shadows", "1"); BZDB.set("radarStyle", "3"); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // try it without shadows -- some platforms stipple very slowly BZDB.set("shadows", "0"); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // no high quality printError(" medium quality"); sceneRenderer->setQuality(1); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; printError(" low quality"); sceneRenderer->setQuality(0); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // lower quality texturing printError(" nearest texturing"); tm.setMaxFilter(OpenGLTexture::Nearest); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // no texturing printError(" no texturing"); BZDB.set("texture", "0"); tm.setMaxFilter(OpenGLTexture::Off); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // no blending printError(" no blending"); BZDB.set("blend", "0"); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // no smoothing. shouldn't really affect fill rate too much. printError(" no smoothing"); BZDB.set("smooth", "0"); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // no lighting. shouldn't really affect fill rate, either. printError(" no lighting"); BZDB.set("lighting", "0"); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; // no dithering printError(" no dithering"); BZDB.set("dither", "0"); if (timeConfiguration(true) < MaxFrameTime) return; if (timeConfiguration(false) < MaxFrameTime) return; } static void findFastConfiguration() { // time the rendering of the background with various rendering styles // until we find one fast enough. these tests assume that we're // going to be fill limited. each test comes in a pair: with and // without the zbuffer. // // this, of course, is only a rough estimate since we're not drawing // a normal frame (no radar, no HUD, no buildings, etc.). the user // can always turn stuff on later and the settings are remembered // across invocations. // setup projection float muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); static const GLfloat eyePoint[3] = { 0.0f, 0.0f, muzzleHeight }; static const GLfloat targetPoint[3] = { 0.0f, 10.0f, muzzleHeight }; sceneRenderer->getViewFrustum().setProjection((float)(45.0 * M_PI / 180.0), NearPlaneNormal, FarPlaneDefault, FarDeepPlaneDefault, mainWindow->getWidth(), mainWindow->getHeight(), mainWindow->getViewHeight()); sceneRenderer->getViewFrustum().setView(eyePoint, targetPoint); // add a big wall in front of where we're looking. this is important // because once textures are off, the background won't draw much of // anything. this will ensure that we continue to test polygon fill // rate. with one polygon it doesn't matter if we use a z or bsp // database. static const GLfloat base[3] = { -10.0f, 10.0f, 0.0f }; static const GLfloat sEdge[3] = { 20.0f, 0.0f, 0.0f }; static const GLfloat tEdge[3] = { 0.0f, 0.0f, 10.0f }; static const GLfloat color[4] = { 1.0f, 1.0f, 1.0f, 0.5f }; SceneDatabase* timingScene = new ZSceneDatabase; WallSceneNode* node = new QuadWallSceneNode(base, sEdge, tEdge, 1.0f, 1.0f, true); node->setColor(color); node->setModulateColor(color); node->setLightedColor(color); node->setLightedModulateColor(color); node->setTexture(HUDuiControl::getArrow()); node->setMaterial(OpenGLMaterial(color, color)); timingScene->addStaticNode(node, false); timingScene->finalizeStatics(); sceneRenderer->setSceneDatabase(timingScene); sceneRenderer->setDim(false); timeConfigurations(); sceneRenderer->setSceneDatabase(NULL); } static void defaultErrorCallback(const char* msg) { std::string message = ColorStrings[RedColor]; message += msg; controlPanel->addMessage(message); } static void startupErrorCallback(const char* msg) { controlPanel->addMessage(msg); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); controlPanel->render(*sceneRenderer); mainWindow->getWindow()->swapBuffers(); } void startPlaying(BzfDisplay* _display, SceneRenderer& renderer) { // initalization display = _display; sceneRenderer = &renderer; mainWindow = &sceneRenderer->getWindow(); lastObserverUpdateTime = TimeKeeper::getCurrent().getSeconds(); // register some commands for (unsigned int c = 0; c < countof(commandList); ++c) { CMDMGR.add(commandList[c].name, commandList[c].func, commandList[c].help); } // initialize the tank display lists // (do this before calling SceneRenderer::render()) TankGeometryMgr::init(); SphereLodSceneNode::init(); // make control panel ControlPanel _controlPanel(*mainWindow, *sceneRenderer); controlPanel = &_controlPanel; // make the radar RadarRenderer _radar(*sceneRenderer, world); radar = &_radar; // tie the radar to the control panel controlPanel->setRadarRenderer(radar); // tell the control panel how many frame buffers there are. we // cheat when drawing the control panel, not drawing it if it // hasn't changed. that only works if we've filled all the // frame buffers (e.g. front and back buffers) with the correct // data. // FIXME -- assuming the contents of any frame buffer except the // front buffer are anything but garbage violates the OpenGL // spec. we really should redraw the control panel every frame // but this works on every system so far. { int n = 3; // assume triple buffering switch (sceneRenderer->getViewType()) { case SceneRenderer::Stacked: case SceneRenderer::Stereo: #ifndef USE_GL_STEREO // control panel drawn twice per frame n *= 2; #endif break; case SceneRenderer::ThreeChannel: default: // only one copy of control panel visible break; } controlPanel->setNumberOfFrameBuffers(n); } // if no configuration, turn off fancy rendering so startup is fast, // even on a slow machine. if (!startupInfo.hasConfiguration) { BZDB.set("blend", "0"); BZDB.set("smooth", "0"); BZDB.set("lighting", "0"); BZDB.set("tesselation", "1"); // lighting set to 0 overrides BZDB.set("texture", "0"); sceneRenderer->setQuality(0); BZDB.set("dither", "0"); BZDB.set("shadows", "0"); BZDB.set("radarStyle", "0"); TextureManager::instance().setMaxFilter(OpenGLTexture::Off); } // should we grab the mouse? yes if fullscreen. if (!BZDB.isSet("_window")) setGrabMouse(true); #if defined(__linux__) && !defined(DEBUG) // linux usually has a virtual root window so grab mouse always setGrabMouse(true); #endif // show window and clear it immediately mainWindow->showWindow(true); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glDisable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); mainWindow->getWindow()->swapBuffers(); // resize and draw basic stuff glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); controlPanel->resize(); sceneRenderer->render(); controlPanel->render(*sceneRenderer); mainWindow->getWindow()->swapBuffers(); // startup error callback adds message to control panel and // forces an immediate redraw. setErrorCallback(startupErrorCallback); // initialize epoch offset (time) userTimeEpochOffset = (double)mktime(&userTime); epochOffset = userTimeEpochOffset; updateDaylight(epochOffset, *sceneRenderer); lastEpochOffset = epochOffset; // catch kill signals before changing video mode so we can // put it back even if we die. ignore a few signals. bzSignal(SIGILL, SIG_PF(dying)); bzSignal(SIGABRT, SIG_PF(dying)); bzSignal(SIGSEGV, SIG_PF(dying)); bzSignal(SIGTERM, SIG_PF(suicide)); #if !defined(_WIN32) if (bzSignal(SIGINT, SIG_IGN) != SIG_IGN) bzSignal(SIGINT, SIG_PF(suicide)); bzSignal(SIGPIPE, SIG_PF(hangup)); bzSignal(SIGHUP, SIG_IGN); if (bzSignal(SIGQUIT, SIG_IGN) != SIG_IGN) bzSignal(SIGQUIT, SIG_PF(dying)); #ifndef GUSI_20 bzSignal(SIGBUS, SIG_PF(dying)); #endif bzSignal(SIGUSR1, SIG_IGN); bzSignal(SIGUSR2, SIG_IGN); #endif /* !defined(_WIN32) */ std::string videoFormat; int format = -1; if (BZDB.isSet("resolution")) { videoFormat = BZDB.get("resolution"); if (videoFormat.length() != 0) { format = display->findResolution(videoFormat.c_str()); if (format >= 0) { mainWindow->getWindow()->callResizeCallbacks(); } } }; // set the resolution (only if in full screen mode) if (!BZDB.isSet("_window") && BZDB.isSet("resolution")) { if (videoFormat.length() != 0) { if (display->isValidResolution(format) && display->getResolution() != format && display->setResolution(format)) { // handle resize if (BZDB.isSet("geometry")) { int w, h, x, y, count; char xs, ys; count = sscanf(BZDB.get("geometry").c_str(), "%dx%d%c%d%c%d", &w, &h, &xs, &x, &ys, &y); if (w < 256) w = 256; if (h < 192) h = 192; if (count == 6) { if (xs == '-') x = display->getWidth() - x - w; if (ys == '-') y = display->getHeight() - y - h; mainWindow->setPosition(x, y); } mainWindow->setSize(w, h); } else { mainWindow->setFullscreen(); } // more resize handling mainWindow->getWindow()->callResizeCallbacks(); mainWindow->warpMouse(); } } } // grab mouse if we should if (shouldGrabMouse()) mainWindow->grabMouse(); // draw again glClear(GL_COLOR_BUFFER_BIT); sceneRenderer->render(); controlPanel->render(*sceneRenderer); mainWindow->getWindow()->swapBuffers(); mainWindow->getWindow()->yieldCurrent(); // make heads up display HUDRenderer _hud(display, renderer); hud = &_hud; scoreboard = hud->getScoreboard(); // initialize control panel and hud updateNumPlayers(); updateFlag(Flags::Null); updateHighScores(); notifyBzfKeyMapChanged(); // make background renderer BackgroundRenderer background(renderer); sceneRenderer->setBackground(&background); // if no configuration file try to determine rendering settings // that yield reasonable performance. if (!startupInfo.hasConfiguration) { printError("testing performance; please wait..."); findFastConfiguration(); dumpResources(); } static const GLfloat zero[3] = { 0.0f, 0.0f, 0.0f }; TextureManager &tm = TextureManager::instance(); bool done = false; int explostion = 1; while (!done) { char text[256]; sprintf(text, "explode%d", explostion); int tex = tm.getTextureID(text, false); if (tex < 0) { done = true; } else { // make explosion scene node BillboardSceneNode* explosion = new BillboardSceneNode(zero); explosion->setTexture(tex); explosion->setTextureAnimation(8, 8); // add it to list of prototype explosions prototypeExplosions.push_back(explosion); explostion++; } } // let other stuff do initialization sceneBuilder = new SceneDatabaseBuilder(sceneRenderer); World::init(); // prepare dialogs mainMenu = new MainMenu; // normal error callback (doesn't force a redraw) setErrorCallback(defaultErrorCallback); // print debugging info { // Application version logDebugMessage(1,"BZFlag version: %s\n", getAppVersion()); // Protocol version logDebugMessage(1,"BZFlag protocol: %s\n", getProtocolVersion()); // OpenGL Driver Information logDebugMessage(1,"OpenGL vendor: %s\n", (const char*)glGetString(GL_VENDOR)); logDebugMessage(1,"OpenGL version: %s\n", (const char*)glGetString(GL_VERSION)); logDebugMessage(1,"OpenGL renderer: %s\n", (const char*)glGetString(GL_RENDERER)); // Depth Buffer bitplanes GLint zDepth; glGetIntegerv(GL_DEPTH_BITS, &zDepth); logDebugMessage(1,"Depth Buffer: %i bitplanes\n", zDepth); } // windows version can be very helpful in debug logs #ifdef _WIN32 if (debugLevel >= 1) { OSVERSIONINFO info; ZeroMemory(&info, sizeof(OSVERSIONINFO)); info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&info); logDebugMessage(1,"Running on Windows %s%d.%d %s\n", (info.dwPlatformId == VER_PLATFORM_WIN32_NT) ? "NT " : "", info.dwMajorVersion, info.dwMinorVersion, info.szCSDVersion); } #endif // print expiration if (timeBombString()) { // add message about date of expiration char bombMessage[80]; sprintf(bombMessage, "This release will expire on %s", timeBombString()); controlPanel->addMessage(bombMessage); } // send informative header to the console { std::string tmpString; controlPanel->addMessage(""); // print app version tmpString = ColorStrings[RedColor]; tmpString += "BZFlag version: "; tmpString += getAppVersion(); tmpString += " ("; tmpString += getProtocolVersion(); tmpString += ")"; controlPanel->addMessage(tmpString); // print copyright tmpString = ColorStrings[YellowColor]; tmpString += bzfcopyright; controlPanel->addMessage(tmpString); // print license tmpString = ColorStrings[CyanColor]; tmpString += "Distributed under the terms of the LGPL"; controlPanel->addMessage(tmpString); // print author tmpString = ColorStrings[GreenColor]; tmpString += "Author: NightMare"; controlPanel->addMessage(tmpString); // print maintainer tmpString = ColorStrings[CyanColor]; tmpString += "Maintainer: NightMare"; controlPanel->addMessage(tmpString); // print audio driver std::string audioStr; PlatformFactory::getMedia()->audioDriver(audioStr); if (tmpString != "") { tmpString = ColorStrings[BlueColor]; tmpString += "Audio Driver: " + audioStr; controlPanel->addMessage(tmpString); } // print GL renderer tmpString = ColorStrings[PurpleColor]; tmpString += "OpenGL Driver: "; tmpString += (const char*)glGetString(GL_RENDERER); controlPanel->addMessage(tmpString); } // inform user of silencePlayers on startup for (unsigned int j = 0; j < silencePlayers.size(); j ++){ std::string aString = silencePlayers[j]; aString += " Silenced"; if (silencePlayers[j] == "*") { aString = "Silenced All Msgs"; } controlPanel->addMessage(aString); } // enter game if we have all the info we need, otherwise // pop up main menu if (startupInfo.autoConnect && startupInfo.callsign[0] && startupInfo.serverName[0]) { joinRequested = true; controlPanel->addMessage("Trying..."); } else { mainMenu->createControls(); HUDDialogStack::get()->push(mainMenu); } // start game loop playingLoop(); // clean up TankGeometryMgr::kill(); SphereLodSceneNode::kill(); if (resourceDownloader) delete resourceDownloader; delete motd; for (unsigned int ext = 0; ext < prototypeExplosions.size(); ext++) delete prototypeExplosions[ext]; prototypeExplosions.clear(); leaveGame(); setErrorCallback(NULL); while (HUDDialogStack::get()->isActive()) HUDDialogStack::get()->pop(); delete mainMenu; delete sceneBuilder; sceneRenderer->setBackground(NULL); sceneRenderer->setSceneDatabase(NULL); World::done(); mainWindow = NULL; sceneRenderer = NULL; display = NULL; cleanWorldCache(); } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ // interface header #include "RadarRenderer.h" // common implementation headers #include "SceneRenderer.h" #include "MainWindow.h" #include "OpenGLGState.h" #include "BZDBCache.h" #include "TextureManager.h" #include "PhysicsDriver.h" #include "ObstacleMgr.h" #include "MeshSceneNode.h" #include "ObstacleList.h" #include "WallObstacle.h" #include "BoxBuilding.h" #include "PyramidBuilding.h" #include "MeshObstacle.h" // local implementation headers #include "LocalPlayer.h" #include "World.h" #include "FlashClock.h" #include "ShotPath.h" static FlashClock flashTank; static bool toggleTank = false; const float RadarRenderer::colorFactor = 40.0f; RadarRenderer::RadarRenderer(const SceneRenderer&, World* _world) : world(_world), x(0), y(0), w(0), h(0), dimming(0.0f), decay(0.01f), jammed(false), multiSampled(false) { setControlColor(); #if defined(GLX_SAMPLES_SGIS) && defined(GLX_SGIS_multisample) GLint bits; glGetIntergerv(GL_SAMPLES_SGIS, &bits); if (bits > 0) multiSampled = true; #endif } void RadarRenderer::setWorld(World* _world) { world = _world; } void RadarRenderer::setControlColor(const GLfloat *color) { if (color) memcpy(teamColor, color, 3 * sizeof(float)); else memset(teamColor, 0, 3 * sizeof(float)); } void RadarRenderer::setShape(int _x, int _y, int _w, int _h) { x = _x; y = _y; w = _w; h = _h; } void RadarRenderer::setJammed(bool _jammed) { jammed = _jammed; decay = 0.01; } void RadarRenderer::setDimming(float newDimming) { dimming = (1.0f - newDimming > 1.0f) ? 1.0f : (1.0f - newDimming < 0.0f) ? 0.0f : 1.0f - newDimming; } void RadarRenderer::drawShot(const ShotPath* shot) { glBegin(GL_POINTS); glVertex2fv(shot->getPosition()); glEnd(); } void RadarRenderer::drawTank(const float pos[3], const Player* player) { glPushMatrix(); // 'ps' is pixel scale, setup in render() const float tankRadius = BZDBCache::tankRadius; float minSize = 1.5f + (ps * BZDBCache::radarTankPixels); GLfloat size; if (tankRadius < minSize) { size = minSize; } else { size = tankRadius; } if (pos[2] < 0.0f) { size = 0.5f; } // NOTE: myTank was checked in render() const float myAngle = LocalPlayer::getMyTank()->getAngle(); // transform to the tanks location glTranslatef(pos[0], pos[1], 0.0f); // draw the tank if ((player == NULL) || !useTankDimensions) { // align to the screen axes glRotatef(float(myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f); glRectf(-size, -size, +size, +size); } else { const float tankAngle = player->getAngle(); glPushMatrix(); glRotatef(float(tankAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f); if (useTankModels) { drawFancyTank(player); } else { const float* dims = player->getDimensions(); glRectf(-dims[0], -dims[1], +dims[0], +dims[1]); } glPopMatrix(); // align to the screen axes glRotatef(float(myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f); } // adjust with height box size const float boxHeight = BZDB.eval(StateDatabase::BZDB_BOXHEIGHT); size = size * (1.0f + (0.5f * (pos[2] / boxHeight))); // draw the height box glBegin(GL_LINE_STRIP); glVertex2f(-size, 0.0f); glVertex2f(0.0f, -size); glVertex2f(+size, 0.0f); glVertex2f(0.0f, +size); glVertex2f(-size, 0.0f); glEnd(); glPopMatrix(); } void RadarRenderer::drawFancyTank(const Player* player) { if (smooth) { glDisable(GL_BLEND); } // we use the depth buffer so that the treads look ok if (BZDBCache::zbuffer) { glClearDepth(1.0); glClear(GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); } OpenGLGState::resetState(); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); RENDERER.enableSun(true); player->renderRadar(); // draws at (0,0,0) RENDERER.enableSun(false); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // FIXME - height box color OpenGLGState::resetState(); if (BZDBCache::zbuffer) { glDisable(GL_DEPTH_TEST); } if (smooth) { glEnable(GL_BLEND); } return; } void RadarRenderer::drawFlag(const float pos[3]) { GLfloat s = BZDBCache::flagRadius > 3.0f * ps ? BZDBCache::flagRadius : 3.0f * ps; glBegin(GL_LINES); glVertex2f(pos[0] - s, pos[1]); glVertex2f(pos[0] + s, pos[1]); glVertex2f(pos[0] + s, pos[1]); glVertex2f(pos[0] - s, pos[1]); glVertex2f(pos[0], pos[1] - s); glVertex2f(pos[0], pos[1] + s); glVertex2f(pos[0], pos[1] + s); glVertex2f(pos[0], pos[1] - s); glEnd(); } void RadarRenderer::drawFlagOnTank(const float pos[3]) { glPushMatrix(); // align it to the screen axes const float angle = LocalPlayer::getMyTank()->getAngle(); glTranslatef(pos[0], pos[1], 0.0f); glRotatef(float(angle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f); float tankRadius = BZDBCache::tankRadius; GLfloat s = 2.5f * tankRadius > 4.0f * ps ? 2.5f * tankRadius : 4.0f * ps; glBegin(GL_LINES); glVertex2f(-s, 0.0f); glVertex2f(+s, 0.0f); glVertex2f(+s, 0.0f); glVertex2f(-s, 0.0f); glVertex2f(0.0f, -s); glVertex2f(0.0f, +s); glVertex2f(0.0f, +s); glVertex2f(0.0f, -s); glEnd(); glPopMatrix(); } void RadarRenderer::renderFrame(SceneRenderer& renderer) { const MainWindow& window = renderer.getWindow(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.0, window.getWidth(), 0.0, window.getHeight(), -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); OpenGLGState::resetState(); const int ox = window.getOriginX(); const int oy = window.getOriginY(); glScissor(ox + x - 1, oy + y - 1, w + 2, h + 2); const float left = float(ox + x) - 0.5f; const float right = float(ox + x + w) + 0.5f; const float top = float(oy + y) - 0.5f; const float bottom = float(oy + y + h) + 0.5f; float outlineOpacity = RENDERER.getPanelOpacity(); float fudgeFactor = BZDBCache::hudGUIBorderOpacityFactor; // bzdb cache this manybe? if ( outlineOpacity < 1.0f ) outlineOpacity = (outlineOpacity*fudgeFactor) + (1.0f - fudgeFactor); if (BZDBCache::blend) glEnable(GL_BLEND); glColor4f(teamColor[0],teamColor[1],teamColor[2],outlineOpacity); glBegin(GL_LINE_LOOP); { glVertex2f(left, top); glVertex2f(right, top); glVertex2f(right, bottom); glVertex2f(left, bottom); } glEnd(); glBegin(GL_POINTS); { glVertex2f(left, top); glVertex2f(right, top); glVertex2f(right, bottom); glVertex2f(left, bottom); } glEnd(); if (BZDBCache::blend) glDisable(GL_BLEND); glColor4f(teamColor[0],teamColor[1],teamColor[2],1.0f); const float opacity = renderer.getPanelOpacity(); if ((opacity < 1.0f) && (opacity > 0.0f)) { glScissor(ox + x - 2, oy + y - 2, w + 4, h + 4); // draw nice blended background if (BZDBCache::blend && opacity < 1.0f) glEnable(GL_BLEND); glColor4f(0.0f, 0.0f, 0.0f, opacity); glRectf((float) x, (float) y, (float)(x + w), (float)(y + h)); if (BZDBCache::blend && opacity < 1.0f) glDisable(GL_BLEND); } // note that this scissor setup is used for the reset of the rendering glScissor(ox + x, oy + y, w, h); if (opacity == 1.0f) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); return; } void RadarRenderer::render(SceneRenderer& renderer, bool blank, bool observer) { RenderNode::resetTriangleCount(); float test = BZDBCache::radarLimit; if(test<=0.0) { test = BZDB.eval(StateDatabase::BZDB_WORLDSIZE); BZDB.setFloat(StateDatabase::BZDB_RADARLIMIT, BZDB.eval(StateDatabase::BZDB_WORLDSIZE)); } const float radarLimit = test; // render the frame renderFrame(renderer); if (blank) { return; } if (!world) { return; } bool rabbitMode = World::getWorld()->allowRabbit(); smooth = !multiSampled && BZDBCache::smooth; const bool fastRadar = ((BZDBCache::radarStyle == 1) || (BZDBCache::radarStyle == 2)) && BZDBCache::zbuffer; const LocalPlayer *myTank = LocalPlayer::getMyTank(); // setup the radar range float radarRange = BZDB.eval("displayRadarRange") * radarLimit; float maxRange = radarLimit; // when burrowed, limit radar range if (myTank && (myTank->getFlag() == Flags::Burrow) && (myTank->getPosition()[2] < 0.0f)) { maxRange = radarLimit / 4.0f; } if (radarRange > maxRange) { radarRange = maxRange; // only clamp the user's desired range if it's actually // greater then 1. otherwise, we may be resetting it due // to burrow radar limiting. if (BZDB.eval("displayRadarRange") > 1.0f) { BZDB.set("displayRadarRange", "1.0"); } } // prepare projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); const MainWindow& window = renderer.getWindow(); const int xSize = window.getWidth(); const int ySize = window.getHeight(); const double xCenter = double(x) + 0.5 * double(w); const double yCenter = double(y) + 0.5 * double(h); const double xUnit = 2.0 * radarRange / double(w); const double yUnit = 2.0 * radarRange / double(h); // NOTE: the visual extents include passable objects double maxHeight = 0.0; const Extents* visExts = renderer.getVisualExtents(); if (visExts) { maxHeight = (double)visExts->maxs[2]; } glOrtho(-xCenter * xUnit, (xSize - xCenter) * xUnit, -yCenter * yUnit, (ySize - yCenter) * yUnit, -(maxHeight + 10.0), (maxHeight + 10.0)); // prepare modelview matrix glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); OpenGLGState::resetState(); // if jammed then draw white noise. occasionally draw a good frame. if (jammed && (bzfrand() > decay)) { TextureManager &tm = TextureManager::instance(); int noiseTexture = tm.getTextureID( "noise" ); glColor3f(1.0f, 1.0f, 1.0f); if ((noiseTexture >= 0) && (renderer.useQuality() > 0)) { const int sequences = 10; static float np[] = { 0, 0, 1, 1, 1, 1, 0, 0, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 1.5f, 0.5f, 0.5f, 0.25f, 0.25f, 1.25f, 1.25f, 1.25f, 1.25f, 0.25f, 0.25f, 0, 0.5f, 1, 1.5f, 1, 1.5f, 0, 0.5f, 0.5f, 0, 1.5f, 1, 1.4f, 1, 0.5f, 0, 0.75f, 0.75f, 1.75f, 1.75f, 1.75f, 1.75f, 0.75f, 0.75f, }; int noisePattern = 4 * int(floor(sequences * bzfrand())); glEnable(GL_TEXTURE_2D); tm.bind(noiseTexture); glBegin(GL_QUADS); { glTexCoord2f(np[noisePattern+0],np[noisePattern+1]); glVertex2f(-radarRange,-radarRange); glTexCoord2f(np[noisePattern+2],np[noisePattern+1]); glVertex2f( radarRange,-radarRange); glTexCoord2f(np[noisePattern+2],np[noisePattern+3]); glVertex2f( radarRange, radarRange); glTexCoord2f(np[noisePattern+0],np[noisePattern+3]); glVertex2f(-radarRange, radarRange); } glEnd(); glDisable(GL_TEXTURE_2D); } else if ((noiseTexture >= 0) && BZDBCache::texture && (renderer.useQuality() == 0)) { glEnable(GL_TEXTURE_2D); tm.bind(noiseTexture); glBegin(GL_QUADS); { glTexCoord2f(0,0); glVertex2f(-radarRange,-radarRange); glTexCoord2f(1,0); glVertex2f( radarRange,-radarRange); glTexCoord2f(1,1); glVertex2f( radarRange, radarRange); glTexCoord2f(0,1); glVertex2f(-radarRange, radarRange); } glEnd(); glDisable(GL_TEXTURE_2D); } if (decay > 0.015f) decay *= 0.5f; } // only draw if there's a local player and a world else if (myTank) { // if decay is sufficiently small then boost it so it's more // likely a jammed radar will get a few good frames closely // spaced in time. value of 1 guarantees at least two good // frames in a row. if (decay <= 0.015f) decay = 1.0f; else decay *= 0.5f; // get size of pixel in model space (assumes radar is square) ps = 2.0f * (radarRange / GLfloat(w)); MeshSceneNode::setRadarLodScale(ps); float tankWidth = BZDBCache::tankWidth; float tankLength = BZDBCache::tankLength; const float testMin = 8.0f * ps; // maintain the aspect ratio if it isn't square if ((tankWidth > testMin) && (tankLength > testMin)) { useTankDimensions = true; } else { useTankDimensions = false; } if (useTankDimensions && (renderer.useQuality() >= 3)) { useTankModels = true; } else { useTankModels = false; } // relative to my tank const float* myPos = myTank->getPosition(); const float myAngle = myTank->getAngle(); // draw the view angle below stuff // view frustum edges if (!BZDB.isTrue("hideRadarViewLines")) { glColor3f(1.0f, 0.625f, 0.125f); const float fovx = renderer.getViewFrustum().getFOVx(); const float viewWidth = radarRange * tanf(0.5f * fovx); glBegin(GL_LINE_STRIP); glVertex2f(-viewWidth, radarRange); glVertex2f(0.0f, 0.0f); glVertex2f(viewWidth, radarRange); glEnd(); } // transform to the observer's viewpoint glPushMatrix(); glRotatef((float)(90.0 - myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f); glPushMatrix(); glTranslatef(-myPos[0], -myPos[1], 0.0f); if (useTankModels) { // new modelview transform requires repositioning renderer.setupSun(); } // setup the blending function glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // draw the buildings renderObstacles(fastRadar, radarRange); // antialiasing on for lines and points unless we're multisampling, // in which case it's automatic and smoothing makes them look worse. if (smooth) { glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glEnable(GL_POINT_SMOOTH); } // draw my shots int maxShots = world->getMaxShots(); int i; float muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); for (i = 0; i < maxShots; i++) { const ShotPath* shot = myTank->getShot(i); if (shot) { const float cs = colorScale(shot->getPosition()[2], muzzleHeight); glColor3f(1.0f * cs, 1.0f * cs, 1.0f * cs); shot->radarRender(); } } FiringInfo myInfo(*myTank, 0); if (myInfo.flagType != Flags::ShockWave) { if (!BZDB.isTrue(StateDatabase::BZDB_SHOTSKEEPVERTICALV)) myInfo.shot.vel[2] = 0.0f; myInfo.flagType = Flags::Laser; myInfo.lifetime /= 100; const float cs = colorScale(myInfo.shot.pos[2], muzzleHeight); glColor3f(1.0f * cs, 1.0f * cs, 1.0f * cs); LocalShotPath myPath(myInfo); myPath.radarRender(); } //test //draw world weapon shots WorldPlayer *worldWeapons = World::getWorld()->getWorldWeapons(); maxShots = worldWeapons->getMaxShots(); for (i = 0; i < maxShots; i++) { const ShotPath* shot = worldWeapons->getShot(i); if (shot) { const float cs = colorScale(shot->getPosition()[2], muzzleHeight); glColor3f(1.0f * cs, 1.0f * cs, 1.0f * cs); shot->radarRender(); } } // draw other tanks (and any flags on them) // note about flag drawing. each line segment is drawn twice // (once in each direction); this degrades the antialiasing // but on systems that don't do correct filtering of endpoints // not doing it makes (half) the endpoints jump wildly. const int curMaxPlayers = world->getCurMaxPlayers(); for (i = 0; i < curMaxPlayers; i++) { RemotePlayer* player = world->getPlayer(i); if (!player) { continue; } if (!player->isAlive() && (!useTankModels || !observer || !player->isExploding())) { continue; } const float* position = player->getPosition(); if (player->getFlag() != Flags::Null) { glColor3fv(player->getFlag()->getColor()); drawFlagOnTank(position); } if (player->isPaused() || player->isNotResponding()) { const float dimfactor = 0.4f; const float *color; if (myTank->getFlag() == Flags::Colorblindness) { color = Team::getRadarColor(RogueTeam,rabbitMode); } else { color = Team::getRadarColor(player->getTeam(),rabbitMode); } float dimmedcolor[3]; dimmedcolor[0] = color[0] * dimfactor; dimmedcolor[1] = color[1] * dimfactor; dimmedcolor[2] = color[2] * dimfactor; glColor3fv(dimmedcolor); } else { glColor3fv(Team::getRadarColor(myTank->getFlag() == Flags::Colorblindness ? RogueTeam : player->getTeam(), rabbitMode)); } // If this tank is hunted flash it on the radar if (player->isHunted() && myTank->getFlag() != Flags::Colorblindness) { if (flashTank.isOn()) { if (!toggleTank) { float flashcolor[3]; flashcolor[0] = 0.0f; flashcolor[1] = 0.8f; flashcolor[2] = 0.9f; glColor3fv(flashcolor); } } else { toggleTank = !toggleTank; flashTank.setClock(0.2f); } } if (!observer) { drawTank(position, NULL); } else { drawTank(position, player); } } bool coloredShot = BZDB.isTrue("coloredradarshots"); // draw other tanks' shells bool iSeeAll = myTank && (myTank->getFlag() == Flags::Seer); maxShots = World::getWorld()->getMaxShots(); for (i = 0; i < curMaxPlayers; i++) { RemotePlayer* player = world->getPlayer(i); if (!player) continue; for (int j = 0; j < maxShots; j++) { const ShotPath* shot = player->getShot(j); if (shot && (shot->getFlag() != Flags::InvisibleBullet || iSeeAll)) { const float *shotcolor; if (coloredShot) { if (myTank->getFlag() == Flags::Colorblindness) shotcolor = Team::getRadarColor(RogueTeam,rabbitMode); else shotcolor = Team::getRadarColor(player->getTeam(),rabbitMode); const float cs = colorScale(shot->getPosition()[2], muzzleHeight); glColor3f(shotcolor[0] * cs, shotcolor[1] * cs, shotcolor[2] * cs); } else { glColor3f(1.0f, 1.0f, 1.0f); } shot->radarRender(); } } } // draw flags not on tanks. // draw them in reverse order so that the team flags // (which come first), are drawn on top of the normal flags. const int maxFlags = world->getMaxFlags(); const bool drawNormalFlags = BZDB.isTrue("displayRadarFlags"); for (i = (maxFlags - 1); i >= 0; i--) { const Flag& flag = world->getFlag(i); // don't draw flags that don't exist or are on a tank if (flag.status == FlagNoExist || flag.status == FlagOnTank) continue; // don't draw normal flags if we aren't supposed to if (flag.type->flagTeam == NoTeam && !drawNormalFlags) continue; // Flags change color by height const float cs = colorScale(flag.position[2], muzzleHeight); const float *flagcolor = flag.type->getColor(); glColor3f(flagcolor[0] * cs, flagcolor[1] * cs, flagcolor[2] * cs); drawFlag(flag.position); } // draw antidote flag const float* antidotePos = LocalPlayer::getMyTank()->getAntidoteLocation(); if (antidotePos) { glColor3f(1.0f, 1.0f, 0.0f); drawFlag(antidotePos); } // draw these markers above all others always centered glPopMatrix(); // north marker GLfloat ns = 0.05f * radarRange, ny = 0.9f * radarRange; glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_LINE_STRIP); glVertex2f(-ns, ny - ns); glVertex2f(-ns, ny + ns); glVertex2f(ns, ny - ns); glVertex2f(ns, ny + ns); glEnd(); // always up glPopMatrix(); // forward tick glBegin(GL_LINES); glVertex2f(0.0f, radarRange - ps); glVertex2f(0.0f, radarRange - 4.0f * ps); glEnd(); if (!observer) { // revert to the centered transformation glRotatef((float)(90.0 - myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f); glTranslatef(-myPos[0], -myPos[1], 0.0f); // my flag if (myTank->getFlag() != Flags::Null) { glColor3fv(myTank->getFlag()->getColor()); drawFlagOnTank(myPos); } // my tank glColor3f(1.0f, 1.0f, 1.0f); drawTank(myPos, myTank); } if (dimming > 0.0f) { if (!smooth) { glEnable(GL_BLEND); } // darken the entire radar if we're dimmed // we're drawing positively, so dimming is actually an opacity glColor4f(0.0f, 0.0f, 0.0f, 1.0f - dimming); glRectf(-radarRange, -radarRange, +radarRange, +radarRange); } glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); } // restore GL state glPopMatrix(); triangleCount = RenderNode::getTriangleCount(); } float RadarRenderer::colorScale(const float z, const float h) { float scaleColor; if (BZDBCache::radarStyle > 0) { const LocalPlayer* myTank = LocalPlayer::getMyTank(); // Scale color so that objects that are close to tank's level are opaque const float zTank = myTank->getPosition()[2]; if (zTank > (z + h)) scaleColor = 1.0f - (zTank - (z + h)) / colorFactor; else if (zTank < z) scaleColor = 1.0f - (z - zTank) / colorFactor; else scaleColor = 1.0f; // Don't fade all the way if (scaleColor < 0.35f) scaleColor = 0.35f; } else { scaleColor = 1.0f; } return scaleColor; } float RadarRenderer::transScale(const float z, const float h) { float scaleColor; const LocalPlayer* myTank = LocalPlayer::getMyTank(); // Scale color so that objects that are close to tank's level are opaque const float zTank = myTank->getPosition()[2]; if (zTank > (z + h)) scaleColor = 1.0f - (zTank - (z + h)) / colorFactor; else if (zTank < z) scaleColor = 1.0f - (z - zTank) / colorFactor; else scaleColor = 1.0f; if (scaleColor < 0.5f) scaleColor = 0.5f; return scaleColor; } void RadarRenderer::renderObstacles(bool fastRadar, float _range) { if (smooth) { glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); } // draw the walls renderWalls(); // draw the boxes, pyramids, and meshes if (!fastRadar) { renderBoxPyrMesh(); } else { renderBoxPyrMeshFast(_range); } // draw the team bases and teleporters renderBasesAndTeles(); if (smooth) { glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); } return; } void RadarRenderer::renderWalls() { const ObstacleList& walls = OBSTACLEMGR.getWalls(); int count = walls.size(); glColor3f(0.25f, 0.5f, 0.5f); glBegin(GL_LINES); for (int i = 0; i < count; i++) { const WallObstacle& wall = *((const WallObstacle*) walls[i]); const float wid = wall.getBreadth(); const float c = wid * cosf(wall.getRotation()); const float s = wid * sinf(wall.getRotation()); const float* pos = wall.getPosition(); glVertex2f(pos[0] - s, pos[1] + c); glVertex2f(pos[0] + s, pos[1] - c); } glEnd(); return; } void RadarRenderer::renderBoxPyrMeshFast(float _range) { // FIXME - This is hack code at the moment, but even when // rendering the full world, it draws the aztec map // 3X faster (the culling algo is actually slows us // down in that case) // - need a better default gradient texture // (better colors, and tied in to show max jump height?) // - build a procedural texture if default is missing // - use a GL_TEXTURE_1D // - setup the octree to return Z sorted elements (partially done) // - add a renderClass() member to SceneNode (also for coloring) // - also add a renderShadow() member (they don't need sorting, // and if you don't have double-buffering, you shouldn't be // using shadows) // - vertex shaders would be faster // - it would probably be a better approach to attach a radar // rendering object to each obstacle... no time // get the texture int gradientTexId = -1; TextureManager &tm = TextureManager::instance(); gradientTexId = tm.getTextureID("radar", false); // safety: no texture, no service if (gradientTexId < 0) { renderBoxPyrMesh(); return; } // GL state OpenGLGStateBuilder gb; gb.setTexture(gradientTexId); gb.setShading(GL_FLAT); gb.setCulling(GL_BACK); gb.setBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); OpenGLGState gs = gb.getState(); gs.setState(); // disable the unused arrays glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); // now that the texture is bound, setup the clamp mode glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // do this after the GState setting if (smooth) { glEnable(GL_POLYGON_SMOOTH); } // setup the texturing mapping const float hf = 128.0f; // height factor, goes from 0.0 to 1.0 in texcoords const float vfz = RENDERER.getViewFrustum().getEye()[2]; const GLfloat plane[4] = { 0.0f, 0.0f, (1.0f / hf), (((hf * 0.5f) - vfz) / hf) }; glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenfv(GL_S, GL_EYE_PLANE, plane); // setup texture generation glEnable(GL_TEXTURE_GEN_S); // set the color glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // if (!BZDB.isTrue("visualRadar")) { ViewFrustum radarClipper; radarClipper.setOrthoPlanes(RENDERER.getViewFrustum(), _range, _range); RENDERER.getSceneDatabase()->renderRadarNodes(radarClipper); // } else { // RENDERER.getSceneDatabase()->renderRadarNodes(RENDERER.getViewFrustum()); // } // restore texture generation glDisable(GL_TEXTURE_GEN_S); OpenGLGState::resetState(); // re-enable the arrays glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); // do this after the GState setting if (smooth) { glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glDisable(GL_POLYGON_SMOOTH); } return; } void RadarRenderer::renderBoxPyrMesh() { int i; const bool enhanced = (BZDBCache::radarStyle > 0); if (!smooth) { // smoothing has blending disabled if (enhanced) { glEnable(GL_BLEND); // always blend the polygons if we're enhanced } } else { // smoothing has blending enabled if (!enhanced) { glDisable(GL_BLEND); // don't blend the polygons if we're not enhanced } } // draw box buildings. const ObstacleList& boxes = OBSTACLEMGR.getBoxes(); int count = boxes.size(); glBegin(GL_QUADS); for (i = 0; i < count; i++) { const BoxBuilding& box = *((const BoxBuilding*) boxes[i]); if (box.isInvisible()) continue; const float z = box.getPosition()[2]; const float bh = box.getHeight(); const float cs = colorScale(z, bh); glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh)); const float c = cosf(box.getRotation()); const float s = sinf(box.getRotation()); const float wx = c * box.getWidth(), wy = s * box.getWidth(); const float hx = -s * box.getBreadth(), hy = c * box.getBreadth(); const float* pos = box.getPosition(); glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy); glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy); glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy); } glEnd(); // draw pyramid buildings const ObstacleList& pyramids = OBSTACLEMGR.getPyrs(); count = pyramids.size(); glBegin(GL_QUADS); for (i = 0; i < count; i++) { const PyramidBuilding& pyr = *((const PyramidBuilding*) pyramids[i]); const float z = pyr.getPosition()[2]; const float bh = pyr.getHeight(); const float cs = colorScale(z, bh); glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh)); const float c = cosf(pyr.getRotation()); const float s = sinf(pyr.getRotation()); const float wx = c * pyr.getWidth(), wy = s * pyr.getWidth(); const float hx = -s * pyr.getBreadth(), hy = c * pyr.getBreadth(); const float* pos = pyr.getPosition(); glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy); glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy); glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy); } glEnd(); // draw mesh obstacles if (smooth) { glEnable(GL_POLYGON_SMOOTH); } if (!enhanced) { glDisable(GL_CULL_FACE); } const ObstacleList& meshes = OBSTACLEMGR.getMeshes(); count = meshes.size(); for (i = 0; i < count; i++) { const MeshObstacle* mesh = (const MeshObstacle*) meshes[i]; int faces = mesh->getFaceCount(); for (int f = 0; f < faces; f++) { const MeshFace* face = mesh->getFace(f); if (enhanced) { if (face->getPlane()[2] <= 0.0f) { continue; } const BzMaterial* bzmat = face->getMaterial(); if ((bzmat != NULL) && bzmat->getNoRadar()) { continue; } } const float z = face->getPosition()[2]; const float bh = face->getHeight(); const float cs = colorScale(z, bh); // draw death faces with a soupcon of red const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(face->getPhysicsDriver()); if ((phydrv != NULL) && phydrv->getIsDeath()) { glColor4f(0.75f * cs, 0.25f * cs, 0.25f * cs, transScale(z, bh)); } else { glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh)); } // draw the face as a triangle fan int vertexCount = face->getVertexCount(); glBegin(GL_TRIANGLE_FAN); for (int v = 0; v < vertexCount; v++) { const float* pos = face->getVertex(v); glVertex2f(pos[0], pos[1]); } glEnd(); } } if (!enhanced) { glEnable(GL_CULL_FACE); } if (smooth) { glDisable(GL_POLYGON_SMOOTH); } // NOTE: revert from the enhanced setting if (enhanced && !smooth) { glDisable(GL_BLEND); } // now draw antialiased outlines around the polygons if (smooth) { glEnable(GL_BLEND); // NOTE: revert from the enhanced setting count = boxes.size(); for (i = 0; i < count; i++) { const BoxBuilding& box = *((const BoxBuilding*) boxes[i]); if (box.isInvisible()) continue; const float z = box.getPosition()[2]; const float bh = box.getHeight(); const float cs = colorScale(z, bh); glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh)); const float c = cosf(box.getRotation()); const float s = sinf(box.getRotation()); const float wx = c * box.getWidth(), wy = s * box.getWidth(); const float hx = -s * box.getBreadth(), hy = c * box.getBreadth(); const float* pos = box.getPosition(); glBegin(GL_LINE_LOOP); glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy); glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy); glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy); glEnd(); } count = pyramids.size(); for (i = 0; i < count; i++) { const PyramidBuilding& pyr = *((const PyramidBuilding*) pyramids[i]); const float z = pyr.getPosition()[2]; const float bh = pyr.getHeight(); const float cs = colorScale(z, bh); glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh)); const float c = cosf(pyr.getRotation()); const float s = sinf(pyr.getRotation()); const float wx = c * pyr.getWidth(), wy = s * pyr.getWidth(); const float hx = -s * pyr.getBreadth(), hy = c * pyr.getBreadth(); const float* pos = pyr.getPosition(); glBegin(GL_LINE_LOOP); glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy); glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy); glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy); glEnd(); } } return; } void RadarRenderer::renderBasesAndTeles() { int i; // draw team bases if(world->allowTeamFlags()) { for(i = 1; i < NumTeams; i++) { for (int j = 0;;j++) { const float *base = world->getBase(i, j); if (base == NULL) break; glColor3fv(Team::getRadarColor(TeamColor(i))); glBegin(GL_LINE_LOOP); const float beta = atan2f(base[5], base[4]); const float r = hypotf(base[4], base[5]); glVertex2f(base[0] + r * cosf(base[3] + beta), base[1] + r * sinf(base[3] + beta)); glVertex2f(base[0] + r * cosf((float)(base[3] - beta + M_PI)), base[1] + r * sinf((float)(base[3] - beta + M_PI))); glVertex2f(base[0] + r * cosf((float)(base[3] + beta + M_PI)), base[1] + r * sinf((float)(base[3] + beta + M_PI))); glVertex2f(base[0] + r * cosf(base[3] - beta), base[1] + r * sinf(base[3] - beta)); glEnd(); } } } // draw teleporters. teleporters are pretty thin so use lines // (which, if longer than a pixel, are guaranteed to draw something; // not so for a polygon). just in case the system doesn't correctly // filter the ends of line segments, we'll draw the line in each // direction (which degrades the antialiasing). Newport graphics // is one system that doesn't do correct filtering. const ObstacleList& teleporters = OBSTACLEMGR.getTeles(); int count = teleporters.size(); glColor3f(1.0f, 1.0f, 0.25f); glBegin(GL_LINES); for (i = 0; i < count; i++) { const Teleporter & tele = *((const Teleporter *) teleporters[i]); if (tele.isHorizontal ()) { const float z = tele.getPosition ()[2]; const float bh = tele.getHeight (); const float cs = colorScale (z, bh); glColor4f (1.0f * cs, 1.0f * cs, 0.25f * cs, transScale (z, bh)); const float c = cosf (tele.getRotation ()); const float s = sinf (tele.getRotation ()); const float wx = c * tele.getWidth (), wy = s * tele.getWidth (); const float hx = -s * tele.getBreadth (), hy = c * tele.getBreadth (); const float *pos = tele.getPosition (); glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f (pos[0] + wx - hx, pos[1] + wy - hy); glVertex2f (pos[0] + wx - hx, pos[1] + wy - hy); glVertex2f (pos[0] + wx + hx, pos[1] + wy + hy); glVertex2f (pos[0] + wx + hx, pos[1] + wy + hy); glVertex2f (pos[0] - wx + hx, pos[1] - wy + hy); glVertex2f (pos[0] - wx + hx, pos[1] - wy + hy); glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy); glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy); } else { const float z = tele.getPosition ()[2]; const float bh = tele.getHeight (); const float cs = colorScale (z, bh); glColor4f (1.0f * cs, 1.0f * cs, 0.25f * cs, transScale (z, bh)); const float tw = tele.getBreadth (); const float c = tw * cosf (tele.getRotation ()); const float s = tw * sinf (tele.getRotation ()); const float *pos = tele.getPosition (); glVertex2f (pos[0] - s, pos[1] + c); glVertex2f (pos[0] + s, pos[1] - c); glVertex2f (pos[0] + s, pos[1] - c); glVertex2f (pos[0] - s, pos[1] + c); } } glEnd(); return; } int RadarRenderer::getFrameTriangleCount() const { return triangleCount; } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* interface header */ #include "SegmentedShotStrategy.h" /* system implementation headers */ #include /* common implementation headers */ #include "TextureManager.h" #include "Intersect.h" #include "BZDBCache.h" #include "WallObstacle.h" /* local implementation headers */ #include "sound.h" #include "LocalPlayer.h" #include "World.h" #include "effectsRenderer.h" #include "playing.h" SegmentedShotStrategy::SegmentedShotStrategy(ShotPath* _path, bool useSuperTexture, bool faint) : ShotStrategy(_path) { // initialize times prevTime = getPath().getStartTime(); lastTime = currentTime = prevTime; // start at first segment lastSegment = segment = 0; // get team if (_path->getPlayer() == ServerPlayer) { TeamColor tmpTeam = _path->getFiringInfo().shot.team; team = (tmpTeam < RogueTeam) ? RogueTeam : (tmpTeam > RabbitTeam) ? RogueTeam : tmpTeam; } else { Player* p = lookupPlayer(_path->getPlayer()); team = p ? p->getTeam() : RogueTeam; } // initialize scene nodes boltSceneNode = new BoltSceneNode(_path->getPosition()); bool rabbitMode = World::getWorld()->allowRabbit(); const float* c = Team::getRadarColor(team, rabbitMode); if (faint) { boltSceneNode->setColor(c[0], c[1], c[2], 0.2f); boltSceneNode->setTextureColor(1.0f, 1.0f, 1.0f, 0.3f); } else { boltSceneNode->setColor(c[0], c[1], c[2], 1.0f); } TextureManager &tm = TextureManager::instance(); std::string imageName = Team::getImagePrefix(team); if (useSuperTexture) imageName += BZDB.get("superPrefix"); imageName += BZDB.get("boltTexture"); int texture = tm.getTextureID(imageName.c_str()); if (texture >= 0) boltSceneNode->setTexture(texture); } SegmentedShotStrategy::~SegmentedShotStrategy() { // free scene nodes delete boltSceneNode; } void SegmentedShotStrategy::update(float dt) { prevTime = currentTime; currentTime += dt; // see if we've moved to another segment const int numSegments = segments.size(); if (segment < numSegments && segments[segment].end <= currentTime) { lastSegment = segment; while (segment < numSegments && segments[segment].end <= currentTime) { if (++segment < numSegments) { switch (segments[segment].reason) { case ShotPathSegment::Ricochet: { // play ricochet sound. ricochet of local player's shots // are important, others are not. const PlayerId myTankId = LocalPlayer::getMyTank()->getId(); const bool important = (getPath().getPlayer() == myTankId); const float* pos = segments[segment].ray.getOrigin(); playWorldSound(SFX_RICOCHET, pos, important); // this is fugly but it's what we do float dir[3]; const float* newDir = segments[segment].ray.getDirection(); const float* oldDir = segments[segment - 1].ray.getDirection(); dir[0] = newDir[0] - oldDir[0]; dir[1] = newDir[1] - oldDir[1]; dir[2] = newDir[2] - oldDir[2]; float rots[2]; const float horiz = sqrtf((dir[0]*dir[0]) + (dir[1]*dir[1])); rots[0] = atan2f(dir[1], dir[0]); rots[1] = atan2f(dir[2], horiz); EFFECTS.addRicoEffect( pos, rots); break; } case ShotPathSegment::Boundary: break; default: { // this is fugly but it's what we do float dir[3]; dir[0] = segments[segment].ray.getDirection()[0]; dir[1] = segments[segment].ray.getDirection()[1]; dir[2] = segments[segment].ray.getDirection()[2]; float rots[2]; const float horiz = sqrtf((dir[0]*dir[0]) + (dir[1]*dir[1])); rots[0] = atan2f(dir[1], dir[0]); rots[1] = atan2f(dir[2], horiz); const float* pos = segments[segment].ray.getOrigin(); EFFECTS.addShotTeleportEffect( pos, rots); } break; } } } } // if ran out of segments then expire shot on next update if (segment == numSegments) { setExpiring(); if (numSegments > 0) { ShotPathSegment &segm = segments[numSegments - 1]; const float *dir = segm.ray.getDirection(); const float speed = hypotf(dir[0], hypotf(dir[1], dir[2])); float pos[3]; segm.ray.getPoint(float(segm.end - segm.start - 1.0 / speed), pos); /* NOTE -- comment out to not explode when shot expires */ addShotExplosion(pos); } } // otherwise update position and velocity else { float p[3]; segments[segment].ray.getPoint(float(currentTime - segments[segment].start), p); setPosition(p); setVelocity(segments[segment].ray.getDirection()); } } void SegmentedShotStrategy::setCurrentTime(const TimeKeeper& _currentTime) { currentTime = _currentTime; } const TimeKeeper& SegmentedShotStrategy::getLastTime() const { return lastTime; } bool SegmentedShotStrategy::isOverlapping( const float (*bbox1)[3], const float (*bbox2)[3]) const { if (bbox1[1][0] < bbox2[0][0]) return false; if (bbox1[0][0] > bbox2[1][0]) return false; if (bbox1[1][1] < bbox2[0][1]) return false; if (bbox1[0][1] > bbox2[1][1]) return false; if (bbox1[1][2] < bbox2[0][2]) return false; if (bbox1[0][2] > bbox2[1][2]) return false; return true; } void SegmentedShotStrategy::setCurrentSegment(int _segment) { segment = _segment; } float SegmentedShotStrategy::checkHit(const BaseLocalPlayer* tank, float position[3]) const { float minTime = Infinity; // expired shot can't hit anything if (getPath().isExpired()) return minTime; // get tank radius float radius = tank->getRadius(); const float radius2 = radius * radius; // tank is positioned from it's bottom so shift position up by // half a tank height. const float tankHeight = tank->getDimensions()[2]; Ray tankLastMotionRaw = tank->getLastMotion(); float lastTankPositionRaw[3]; lastTankPositionRaw[0] = tankLastMotionRaw.getOrigin()[0]; lastTankPositionRaw[1] = tankLastMotionRaw.getOrigin()[1]; lastTankPositionRaw[2] = tankLastMotionRaw.getOrigin()[2] + 0.5f * tankHeight; Ray tankLastMotion(lastTankPositionRaw, tankLastMotionRaw.getDirection()); // if bounding box of tank and entire shot doesn't overlap then no hit const float (*tankBBox)[3] = tank->getLastMotionBBox(); if (!isOverlapping(bbox, tankBBox)) return minTime; float shotRadius = BZDB.eval(StateDatabase::BZDB_SHOTRADIUS); // check each segment in interval (prevTime,currentTime] const float dt = float(currentTime - prevTime); const int numSegments = segments.size(); for (int i = lastSegment; i <= segment && i < numSegments; i++) { // can never hit your own first laser segment if (i == 0 && getPath().getFlag() == Flags::Laser && getPath().getPlayer() == tank->getId()) continue; /* // skip segments that don't overlap in time with current interval if (segments[i].end <= prevTime) continue; if (currentTime <= segments[i].start) break; */ // if shot segment and tank bboxes don't overlap then no hit const ShotPathSegment& s = segments[i]; if (!isOverlapping(s.bbox, tankBBox)) continue; // construct relative shot ray: origin and velocity relative to // my tank as a function of time (t=0 is start of the interval). Ray relativeRay(rayMinusRay(s.ray, float(prevTime - s.start), tankLastMotion, 0.0f)); // get hit time float t; if (tank->getFlag() == Flags::Narrow) { // find closest approach to narrow box around tank. width of box // is shell radius so you can actually hit narrow tank head on. static float tankBase[3] = { 0.0f, 0.0f, -0.5f * tankHeight }; t = timeRayHitsBlock(relativeRay, tankBase, tank->getAngle(), 0.5f * BZDBCache::tankLength, shotRadius, tankHeight); } else { // find time when shot hits sphere around tank t = rayAtDistanceFromOrigin(relativeRay, 0.99f * radius); } // short circuit if time is greater then smallest time so far if (t > minTime) continue; // make sure time falls within segment if (t < 0.0f || t > dt) continue; if (t > s.end - prevTime) continue; // check if shot hits tank -- get position at time t, see if in radius float closestPos[3]; relativeRay.getPoint(t, closestPos); if (closestPos[0] * closestPos[0] + closestPos[1] * closestPos[1] + closestPos[2] * closestPos[2] < radius2) { // save best time so far minTime = t; // compute location of tank at time of hit float tankPos[3]; tank->getLastMotion().getPoint(t, tankPos); // compute position of intersection position[0] = tankPos[0] + closestPos[0]; position[1] = tankPos[1] + closestPos[1]; position[2] = tankPos[2] + closestPos[2]; //printf("%u:%u %u:%u\n", tank->getId().port, tank->getId().number, getPath().getPlayer().port, getPath().getPlayer().number); } } return minTime; } void SegmentedShotStrategy::addShot( SceneDatabase* scene, bool colorblind) { const ShotPath& shotPath = getPath(); boltSceneNode->move(shotPath.getPosition(), shotPath.getVelocity()); if (boltSceneNode->getColorblind() != colorblind) { boltSceneNode->setColorblind(colorblind); TeamColor currentTeam = colorblind ? RogueTeam : team; bool rabbitMode = World::getWorld()->allowRabbit(); const float* c = Team::getRadarColor(currentTeam, rabbitMode); boltSceneNode->setColor(c[0], c[1], c[2]); TextureManager &tm = TextureManager::instance(); std::string imageName = Team::getImagePrefix(currentTeam); imageName += BZDB.get("boltTexture"); int texture = tm.getTextureID(imageName.c_str()); if (texture >= 0) boltSceneNode->setTexture(texture); } scene->addDynamicNode(boltSceneNode); } void SegmentedShotStrategy::radarRender() const { const float *orig = getPath().getPosition(); const int length = (int)BZDBCache::linedRadarShots; const int size = (int)BZDBCache::sizedRadarShots; float shotTailLength = BZDB.eval(StateDatabase::BZDB_SHOTTAILLENGTH); // Display leading lines if (length > 0) { const float* vel = getPath().getVelocity(); const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2])); float dir[3]; dir[0] = vel[0] * d * shotTailLength * length; dir[1] = vel[1] * d * shotTailLength * length; dir[2] = vel[2] * d * shotTailLength * length; glBegin(GL_LINES); glVertex2fv(orig); if (BZDBCache::leadingShotLine) { glVertex2f(orig[0] + dir[0], orig[1] + dir[1]); } else { glVertex2f(orig[0] - dir[0], orig[1] - dir[1]); } glEnd(); // draw a "bright" bullet tip if (size > 0) { glColor3f(0.75, 0.75, 0.75); glPointSize((float)size); glBegin(GL_POINTS); glVertex2f(orig[0], orig[1]); glEnd(); glPointSize(1.0f); } } else { if (size > 0) { // draw a sized bullet glPointSize((float)size); glBegin(GL_POINTS); glVertex2fv(orig); glEnd(); glPointSize(1.0f); } else { // draw the tiny little bullet glBegin(GL_POINTS); glVertex2fv(orig); glEnd(); } } } void SegmentedShotStrategy::makeSegments(ObstacleEffect e) { // compute segments of shot until total length of segments exceeds the // lifetime of the shot. const ShotPath &shotPath = getPath(); const float *v = shotPath.getVelocity(); TimeKeeper startTime = shotPath.getStartTime(); float timeLeft = shotPath.getLifetime(); float minTime = BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT) / hypotf(v[0], hypotf(v[1], v[2])); // if all shots ricochet and obstacle effect is stop, then make it ricochet if (e == Stop && World::getWorld()->allShotsRicochet()) { e = Reflect; } // prepare first segment float o[3], d[3]; d[0] = v[0]; d[1] = v[1]; d[2] = v[2]; // use v[2] to have jumping affect shot velocity o[0] = shotPath.getPosition()[0]; o[1] = shotPath.getPosition()[1]; o[2] = shotPath.getPosition()[2]; segments.clear(); ShotPathSegment::Reason reason = ShotPathSegment::Initial; int i; const int maxSegment = 100; float worldSize = BZDBCache::worldSize / 2.0f - 0.01f; for (i = 0; (i < maxSegment) && (timeLeft > Epsilon); i++) { // construct ray and find the first building, teleporter, or outer wall float o2[3]; o2[0] = o[0] - minTime * d[0]; o2[1] = o[1] - minTime * d[1]; o2[2] = o[2] - minTime * d[2]; // Sometime shot start outside world if (o2[0] <= -worldSize) o2[0] = -worldSize; if (o2[0] >= worldSize) o2[0] = worldSize; if (o2[1] <= -worldSize) o2[1] = -worldSize; if (o2[1] >= worldSize) o2[1] = worldSize; Ray r(o2, d); Ray rs(o, d); float t = timeLeft + minTime; int face; bool hitGround = getGround(r, Epsilon, t); Obstacle* building = (Obstacle*)((e != Through) ? getFirstBuilding(r, Epsilon, t) : NULL); const Teleporter* teleporter = getFirstTeleporter(r, Epsilon, t, face); t -= minTime; minTime = 0.0f; bool ignoreHit = false; // if hit outer wall with ricochet and hit is above top of wall // then ignore hit. if (!teleporter && building && (e == Reflect) && (building->getType() == WallObstacle::getClassName()) && ((o[2] + t * d[2]) > building->getHeight())) { ignoreHit = true; } // construct next shot segment and add it to list TimeKeeper endTime(startTime); if (t < 0.0f) { endTime += Epsilon; } else { endTime += t; } ShotPathSegment segm(startTime, endTime, rs, reason); segments.push_back(segm); startTime = endTime; // used up this much time in segment if (t < 0.0f) { timeLeft -= Epsilon; } else { timeLeft -= t; } // check in reverse order to see what we hit first reason = ShotPathSegment::Through; if (ignoreHit) { // uh...ignore this. usually used if you shoot over the boundary wall. // just move the point of origin and build the next segment o[0] += t * d[0]; o[1] += t * d[1]; o[2] += t * d[2]; reason = ShotPathSegment::Boundary; } else if (teleporter) { // entered teleporter -- teleport it unsigned int seed = shotPath.getShotId() + i; int source = World::getWorld()->getTeleporter(teleporter, face); int target = World::getWorld()->getTeleportTarget(source, seed); int outFace; const Teleporter* outTeleporter = World::getWorld()->getTeleporter(target, outFace); o[0] += t * d[0]; o[1] += t * d[1]; o[2] += t * d[2]; teleporter->getPointWRT(*outTeleporter, face, outFace, o, d, 0.0f, o, d, NULL); reason = ShotPathSegment::Teleport; } else if (building) { // hit building -- can bounce off or stop, buildings ignored for Through switch (e) { case Stop: timeLeft = 0.0f; break; case Reflect: { // move origin to point of reflection o[0] += t * d[0]; o[1] += t * d[1]; o[2] += t * d[2]; // reflect direction about normal to building float normal[3]; building->get3DNormal(o, normal); reflect(d, normal); reason = ShotPathSegment::Ricochet; } break; case Through: assert(0); } } else if (hitGround) // we hit the ground { switch (e) { case Stop: case Through: timeLeft = 0.0f; break; case Reflect: { // move origin to point of reflection o[0] += t * d[0]; o[1] += t * d[1]; o[2] += t * d[2]; // reflect direction about normal to ground float normal[3]; normal[0] = 0.0f; normal[1] = 0.0f; normal[2] = 1.0f; reflect(d, normal); reason = ShotPathSegment::Ricochet; break; } } } } lastTime = startTime; // make bounding box for entire path const int numSegments = segments.size(); if (numSegments > 0) { const ShotPathSegment& firstSeg = segments[0]; bbox[0][0] = firstSeg.bbox[0][0]; bbox[0][1] = firstSeg.bbox[0][1]; bbox[0][2] = firstSeg.bbox[0][2]; bbox[1][0] = firstSeg.bbox[1][0]; bbox[1][1] = firstSeg.bbox[1][1]; bbox[1][2] = firstSeg.bbox[1][2]; for (i = 1; i < numSegments; i++) { const ShotPathSegment& segm = segments[i]; if (bbox[0][0] > segm.bbox[0][0]) bbox[0][0] = segm.bbox[0][0]; if (bbox[1][0] < segm.bbox[1][0]) bbox[1][0] = segm.bbox[1][0]; if (bbox[0][1] > segm.bbox[0][1]) bbox[0][1] = segm.bbox[0][1]; if (bbox[1][1] < segm.bbox[1][1]) bbox[1][1] = segm.bbox[1][1]; if (bbox[0][2] > segm.bbox[0][2]) bbox[0][2] = segm.bbox[0][2]; if (bbox[1][2] < segm.bbox[1][2]) bbox[1][2] = segm.bbox[1][2]; } } else { bbox[0][0] = bbox[1][0] = 0.0f; bbox[0][1] = bbox[1][1] = 0.0f; bbox[0][2] = bbox[1][2] = 0.0f; } } const std::vector& SegmentedShotStrategy::getSegments() const { return segments; } // // NormalShotStrategy // NormalShotStrategy::NormalShotStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, false) { // make segments makeSegments(Stop); } NormalShotStrategy::~NormalShotStrategy() { // do nothing } // // RapidFireStrategy // RapidFireStrategy::RapidFireStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, false) { // speed up shell and decrease lifetime FiringInfo& f = getFiringInfo(_path); f.lifetime *= BZDB.eval(StateDatabase::BZDB_RFIREADLIFE); float fireAdVel = BZDB.eval(StateDatabase::BZDB_RFIREADVEL); f.shot.vel[0] *= fireAdVel; f.shot.vel[1] *= fireAdVel; f.shot.vel[2] *= fireAdVel; // make segments makeSegments(Stop); } RapidFireStrategy::~RapidFireStrategy() { // do nothing } // // ThiefStrategy // ThiefStrategy::ThiefStrategy(ShotPath *_path) : SegmentedShotStrategy(_path, false),cumTime(0.0f) { // speed up shell and decrease lifetime FiringInfo& f = getFiringInfo(_path); f.lifetime *= BZDB.eval(StateDatabase::BZDB_THIEFADLIFE); float thiefAdVel = BZDB.eval(StateDatabase::BZDB_THIEFADSHOTVEL); f.shot.vel[0] *= thiefAdVel; f.shot.vel[1] *= thiefAdVel; f.shot.vel[2] *= thiefAdVel; setReloadTime(_path->getReloadTime() / BZDB.eval(StateDatabase::BZDB_THIEFADRATE)); // make segments makeSegments(Stop); setCurrentTime(getLastTime()); endTime = f.lifetime; // make thief scene nodes const int numSegments = getSegments().size(); thiefNodes = new LaserSceneNode*[numSegments]; TextureManager &tm = TextureManager::instance(); int texture = tm.getTextureID("thief"); for (int i = 0; i < numSegments; i++) { const ShotPathSegment& segm = getSegments()[i]; const float t = float(segm.end - segm.start); const Ray& ray = segm.ray; const float* rawdir = ray.getDirection(); float dir[3]; dir[0] = t * rawdir[0]; dir[1] = t * rawdir[1]; dir[2] = t * rawdir[2]; thiefNodes[i] = new LaserSceneNode(ray.getOrigin(), dir); if (texture >= 0) thiefNodes[i]->setTexture(texture); } setCurrentSegment(numSegments - 1); } ThiefStrategy::~ThiefStrategy() { const int numSegments = getSegments().size(); for (int i = 0; i < numSegments; i++) delete thiefNodes[i]; delete[] thiefNodes; } void ThiefStrategy::update(float dt) { cumTime += dt; if (cumTime >= endTime) setExpired(); } void ThiefStrategy::addShot(SceneDatabase* scene, bool) { // laser is so fast we always show every segment const int numSegments = getSegments().size(); for (int i = 0; i < numSegments; i++) scene->addDynamicNode(thiefNodes[i]); } void ThiefStrategy::radarRender() const { // draw all segments const std::vector& segmts = getSegments(); const int numSegments = segmts.size(); glBegin(GL_LINES); for (int i = 0; i < numSegments; i++) { const ShotPathSegment& segm = segmts[i]; const float* origin = segm.ray.getOrigin(); const float* direction = segm.ray.getDirection(); const float dt = float(segm.end - segm.start); glVertex2fv(origin); glVertex2f(origin[0] + dt * direction[0], origin[1] + dt * direction[1]); } glEnd(); } bool ThiefStrategy::isStoppedByHit() const { return false; } // // MachineGunStrategy // MachineGunStrategy::MachineGunStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, false) { // speed up shell and decrease lifetime FiringInfo& f = getFiringInfo(_path); f.lifetime *= BZDB.eval(StateDatabase::BZDB_MGUNADLIFE); float mgunAdVel = BZDB.eval(StateDatabase::BZDB_MGUNADVEL); f.shot.vel[0] *= mgunAdVel; f.shot.vel[1] *= mgunAdVel; f.shot.vel[2] *= mgunAdVel; setReloadTime(_path->getReloadTime() / BZDB.eval(StateDatabase::BZDB_MGUNADRATE)); // make segments makeSegments(Stop); } MachineGunStrategy::~MachineGunStrategy() { // do nothing } // // RicochetStrategy // RicochetStrategy::RicochetStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, false) { // make segments that bounce makeSegments(Reflect); } RicochetStrategy::~RicochetStrategy() { // do nothing } // // SuperBulletStrategy // SuperBulletStrategy::SuperBulletStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, true) { // make segments that go through buildings makeSegments(Through); } SuperBulletStrategy::~SuperBulletStrategy() { // do nothing } PhantomBulletStrategy::PhantomBulletStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, false,true) { // make segments that go through buildings makeSegments(Through); } PhantomBulletStrategy::~PhantomBulletStrategy() { // do nothing } // // LaserStrategy // LaserStrategy::LaserStrategy(ShotPath* _path) : SegmentedShotStrategy(_path, false), cumTime(0.0f) { // speed up shell and decrease lifetime FiringInfo& f = getFiringInfo(_path); f.lifetime *= BZDB.eval(StateDatabase::BZDB_LASERADLIFE); float laserAdVel = BZDB.eval(StateDatabase::BZDB_LASERADVEL); f.shot.vel[0] *= laserAdVel; f.shot.vel[1] *= laserAdVel; f.shot.vel[2] *= laserAdVel; setReloadTime(_path->getReloadTime() * BZDB.eval(StateDatabase::BZDB_LASERADLIFE)); // make segments makeSegments(Stop); setCurrentTime(getLastTime()); endTime = f.lifetime; // make laser scene nodes const int numSegments = getSegments().size(); laserNodes = new LaserSceneNode*[numSegments]; const LocalPlayer* myTank = LocalPlayer::getMyTank(); TeamColor tmpTeam = (myTank->getFlag() == Flags::Colorblindness) ? RogueTeam : team; TextureManager &tm = TextureManager::instance(); std::string imageName = Team::getImagePrefix(tmpTeam); imageName += BZDB.get("laserTexture"); int texture = tm.getTextureID(imageName.c_str()); for (int i = 0; i < numSegments; i++) { const ShotPathSegment& segm = getSegments()[i]; const float t = float(segm.end - segm.start); const Ray& ray = segm.ray; const float* rawdir = ray.getDirection(); float dir[3]; dir[0] = t * rawdir[0]; dir[1] = t * rawdir[1]; dir[2] = t * rawdir[2]; laserNodes[i] = new LaserSceneNode(ray.getOrigin(), dir); if (texture >= 0) laserNodes[i]->setTexture(texture); } setCurrentSegment(numSegments - 1); } LaserStrategy::~LaserStrategy() { const int numSegments = getSegments().size(); for (int i = 0; i < numSegments; i++) delete laserNodes[i]; delete[] laserNodes; } void LaserStrategy::update(float dt) { cumTime += dt; if (cumTime >= endTime) setExpired(); } void LaserStrategy::addShot(SceneDatabase* scene, bool) { // laser is so fast we always show every segment const int numSegments = getSegments().size(); for (int i = 0; i < numSegments; i++) scene->addDynamicNode(laserNodes[i]); } void LaserStrategy::radarRender() const { // draw all segments const std::vector& segmts = getSegments(); const int numSegments = segmts.size(); glBegin(GL_LINES); for (int i = 0; i < numSegments; i++) { const ShotPathSegment& segm = segmts[i]; const float* origin = segm.ray.getOrigin(); const float* direction = segm.ray.getDirection(); const float dt = float(segm.end - segm.start); glVertex2fv(origin); glVertex2f(origin[0] + dt * direction[0], origin[1] + dt * direction[1]); } glEnd(); } bool LaserStrategy::isStoppedByHit() const { return false; } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * Encapsulates communication between local player and the server. */ #ifndef BZF_SERVER_LINK_H #define BZF_SERVER_LINK_H #include "common.h" #include #ifdef HAVE_KRB5 #ifdef MAXHOSTNAMELEN #undef MAXHOSTNAMELEN #endif #endif #include "global.h" #include "Address.h" #include "Protocol.h" #include "ShotPath.h" #include "Flag.h" class ServerLink { public: enum State { Okay = 0, SocketError = 1, Rejected = 2, BadVersion = 3, Hungup = 4, // only used by Winsock CrippledVersion = 5, Refused = 6 }; enum Abilities { Nothing = 0, CanDoUDP = 1, SendScripts = 2, SendTextures = 4, HasMessageLink = 8 }; ServerLink(const Address& serverAddress, int port = ServerPort); ~ServerLink(); State getState() const; const std::string& getRejectionMessage() { return rejectionMessage; } int getSocket() const; // file descriptor actually const PlayerId& getId() const; const char* getVersion() const; void send(uint16_t code, uint16_t len, const void* msg); // if millisecondsToBlock < 0 then block forever int read(uint16_t& code, uint16_t& len, void* msg, int millisecondsToBlock = 0); void sendEnter(PlayerType, TeamColor, const char* name, const char* email, const char* token); bool readEnter(std::string& reason, uint16_t& code, uint16_t& rejcode); void sendCaptureFlag(TeamColor); void sendGrabFlag(int flagIndex); void sendDropFlag(const float* position); #ifdef HAVE_KRB5 void sendKerberosTicket(const char *principal, const krb5_data *ticket); #endif void sendKilled(const PlayerId&, int reason, int shotId, const FlagType* flag, int phydrv); // FIXME -- This is very ugly, but required to build bzadmin with gcc 2.9.5. // It should be changed to something cleaner. #ifndef BUILDING_BZADMIN void sendPlayerUpdate(Player*); #endif void sendBeginShot(const FiringInfo&); void sendEndShot(const PlayerId&, int shotId, int reason); void sendAlive(); void sendTeleport(int from, int to); void sendTransferFlag(const PlayerId&, const PlayerId&); void sendNewRabbit(); void sendPaused(bool paused); void sendAutoPilot(bool autopilot); void sendUDPlinkRequest(); static ServerLink* getServer(); // const static void setServer(ServerLink*); void enableOutboundUDP(); void confirmIncomingUDP(); private: State state; int fd; struct sockaddr usendaddr; int urecvfd; struct sockaddr urecvaddr; // the clients udp listen address bool ulinkup; PlayerId id; char version[9]; static ServerLink* server; int server_abilities; std::string rejectionMessage; int udpLength; char *udpBufferPtr; char ubuf[MaxPacketLen]; }; #define SEND 1 #define RECEIVE 0 // // ServerLink // inline ServerLink::State ServerLink::getState() const { return state; } inline int ServerLink::getSocket() const { return fd; } inline const PlayerId& ServerLink::getId() const { return id; } inline const char* ServerLink::getVersion() const { return version; } #endif // BZF_SERVER_LINK_H // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ // interface header #include "ShotPath.h" /* system headers */ #include /* local implementation headers */ #include "SegmentedShotStrategy.h" #include "GuidedMissleStrategy.h" #include "ShockWaveStrategy.h" // // FiringInfo (with BaseLocalPlayer) // FiringInfo::FiringInfo(const BaseLocalPlayer& tank, int id) { shot.player = tank.getId(); shot.id = uint16_t(id); tank.getMuzzle(shot.pos); const float* dir = tank.getForward(); const float* tankVel = tank.getVelocity(); float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED); shot.vel[0] = tankVel[0] + shotSpeed * dir[0]; shot.vel[1] = tankVel[1] + shotSpeed * dir[1]; shot.vel[2] = tankVel[2] + shotSpeed * dir[2]; shot.dt = 0.0f; flagType = tank.getFlag(); // wee bit o hack -- if phantom flag but not phantomized // the shot flag is normal -- otherwise FiringInfo will have // to be changed to add a real bitwise status variable if (tank.getFlag() == Flags::PhantomZone){ flagType = Flags::Null; } lifetime = BZDB.eval(StateDatabase::BZDB_RELOADTIME); } // // ShotPath // ShotPath::ShotPath(const FiringInfo& info) : firingInfo(info), reloadTime(BZDB.eval(StateDatabase::BZDB_RELOADTIME)), startTime(TimeKeeper::getTick()), currentTime(TimeKeeper::getTick()), expiring(false), expired(false) { // eek! a giant switch statement, how un-object-oriented! // each flag should be a flyweight object derived from a // base Flag class with a virtual makeShotStrategy() member. // just remember -- it's only a game. if (firingInfo.flagType->flagShot == NormalShot) strategy = new NormalShotStrategy(this); else { if (firingInfo.flagType == Flags::RapidFire) strategy = new RapidFireStrategy(this); else if (firingInfo.flagType == Flags::MachineGun) strategy = new MachineGunStrategy(this); else if (firingInfo.flagType == Flags::GuidedMissile) strategy = new GuidedMissileStrategy(this); else if (firingInfo.flagType == Flags::Laser) strategy = new LaserStrategy(this); else if (firingInfo.flagType == Flags::Ricochet) strategy = new RicochetStrategy(this); else if (firingInfo.flagType == Flags::SuperBullet) strategy = new SuperBulletStrategy(this); else if (firingInfo.flagType == Flags::ShockWave) strategy = new ShockWaveStrategy(this); else if (firingInfo.flagType == Flags::Thief) strategy = new ThiefStrategy(this); else if (firingInfo.flagType == Flags::PhantomZone) strategy = new PhantomBulletStrategy(this); else assert(0); // shouldn't happen } } ShotPath::~ShotPath() { delete strategy; } float ShotPath::checkHit(const BaseLocalPlayer* player, float position[3]) const { return strategy->checkHit(player, position); } bool ShotPath::isStoppedByHit() const { return strategy->isStoppedByHit(); } void ShotPath::addShot(SceneDatabase* scene, bool colorblind) { strategy->addShot(scene, colorblind); } void ShotPath::radarRender() const { if (!isExpired()) strategy->radarRender(); } void ShotPath::updateShot(float dt) { // get new time step and set current time currentTime += dt; // update shot if (!expired) { if (expiring) setExpired(); else getStrategy()->update(dt); } } void ShotPath::setReloadTime(float _reloadTime) { reloadTime = _reloadTime; } void ShotPath::setPosition(const float* p) { firingInfo.shot.pos[0] = p[0]; firingInfo.shot.pos[1] = p[1]; firingInfo.shot.pos[2] = p[2]; } void ShotPath::setVelocity(const float* v) { firingInfo.shot.vel[0] = v[0]; firingInfo.shot.vel[1] = v[1]; firingInfo.shot.vel[2] = v[2]; } void ShotPath::setExpiring() { expiring = true; } void ShotPath::setExpired() { expiring = true; expired = true; getStrategy()->expire(); } void ShotPath::boostReloadTime(float dt) { reloadTime += dt; } // // LocalShotPath // LocalShotPath::LocalShotPath(const FiringInfo& info) : ShotPath(info) { // do nothing } LocalShotPath::~LocalShotPath() { // do nothing } void LocalShotPath::update(float dt) { getFiringInfo().shot.dt += dt; updateShot(dt); // send updates if necessary getStrategy()->sendUpdate(getFiringInfo()); } // // RemoteShotPath // RemoteShotPath::RemoteShotPath(const FiringInfo& info) : ShotPath(info) { // do nothing } RemoteShotPath::~RemoteShotPath() { // do nothing } void RemoteShotPath::update(float dt) { // update shot updateShot(dt); } void RemoteShotPath::update(const ShotUpdate& shot, uint16_t code, void* msg) { // update shot info getFiringInfo().shot = shot; // let the strategy see the message getStrategy()->readUpdate(code, msg); } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ // interface header #include "TargetingUtils.h" // system headers #include // common headers #include "Ray.h" // local headers #include "ShotStrategy.h" // These routines are 2 dimensional float TargetingUtils::normalizeAngle(float ang) { if (ang < -1.0f * M_PI) ang += (float)(2.0 * M_PI); if (ang > 1.0f * M_PI) ang -= (float)(2.0 * M_PI); return ang; } void TargetingUtils::getUnitVector( const float *src, const float *target, float unitVector[3] ) { unitVector[0] = target[0] - src[0]; unitVector[1] = target[1] - src[1]; unitVector[2] = 0.0f; float len = (float) sqrt(unitVector[0] * unitVector[0] + unitVector[1] * unitVector[1]); if (len == 0.0f) return; unitVector[0] /= len; unitVector[1] /= len; } void TargetingUtils::get3DUnitVector( const float *src, const float *target, float unitVector[3] ) { unitVector[0] = target[0] - src[0]; unitVector[1] = target[1] - src[1]; unitVector[2] = target[2] - src[1]; float len = (float) sqrt(unitVector[0] * unitVector[0] + unitVector[1] * unitVector[1] + unitVector[2] * unitVector[2]); if (len == 0.0f) return; unitVector[0] /= len; unitVector[1] /= len; unitVector[2] /= len; } float TargetingUtils::getTargetDistance( const float *src, const float *target ) { float vec[2]; vec[0] = target[0] - src[0]; vec[1] = target[1] - src[1]; return (float) sqrt(vec[0] * vec[0] + vec[1] * vec[1]); } float TargetingUtils::getTargetAzimuth( const float *src, const float *target ) { return atan2f((target[1] - src[1]), (target[0] - src[0])); } float TargetingUtils::getTargetRotation( const float startAzimuth, float targetAzimuth ) { float targetRotation = targetAzimuth - startAzimuth; if (targetRotation < -1.0f * M_PI) targetRotation += (float)(2.0 * M_PI); if (targetRotation > 1.0f * M_PI) targetRotation -= (float)(2.0 * M_PI); return targetRotation; } float TargetingUtils::getTargetAngleDifference( const float *src, float srcAzimuth, const float *target ) { float targetUnitVector[3]; float srcUnitVector[3]; getUnitVector(src, target, targetUnitVector); srcUnitVector[0] = cosf(srcAzimuth); srcUnitVector[1] = sinf(srcAzimuth); srcUnitVector[2] = 0.0f; return acos( targetUnitVector[0]*srcUnitVector[0] + targetUnitVector[1]*srcUnitVector[1] ); } bool TargetingUtils::isLocationObscured( const float *src, const float *target ) { float dir[3]; getUnitVector(src, target, dir); Ray tankRay( src, dir ); float targetDistance = getTargetDistance(src, target); const Obstacle *building = ShotStrategy::getFirstBuilding(tankRay, -0.5f, targetDistance); return building != NULL; } float TargetingUtils::getOpenDistance( const float *src, const float azimuth ) { float t = MAXFLOAT; //Some constant? float dir[3] = { cosf(azimuth), sinf(azimuth), 0.0f }; *((float *) &src[2]) += 0.1f; //Don't hit building because your sitting on one Ray tankRay( src, dir ); *((float *) &src[2]) -= 0.1f; ShotStrategy::getFirstBuilding(tankRay, -0.5f, t); return t; } bool TargetingUtils::getFirstCollisionPoint( const float *src, const float *target, float *collisionPt ) { float t = MAXFLOAT; float dir[3]; get3DUnitVector(src, target, dir); Ray tankRay( src, dir ); const Obstacle *building = ShotStrategy::getFirstBuilding(tankRay, 0.0f, t); if (building == NULL) return false; collisionPt[0] = src[0] + dir[0] * t; collisionPt[1] = src[1] + dir[1] * t; collisionPt[2] = src[2] + dir[2] * t; return true; } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include "common.h" /* interface header */ #include "Flag.h" /* system implementation headers */ #include #include /* common implementation headers */ #include "Team.h" #include "Pack.h" #include "TextUtils.h" int FlagType::flagCount = 0; FlagSet *FlagType::flagSets = NULL; const int FlagType::packSize = FlagPackSize; static const char NullString[2] = { '\0', '\0' }; // Initialize flag description singletons in our Flags namespace void* FlagType::unpack(void* buf, FlagType* &type) { unsigned char abbv[3] = {0,0,0}; buf = nboUnpackUByte(buf, abbv[0]); buf = nboUnpackUByte(buf, abbv[1]); FlagType *newFlag = Flag::getDescFromAbbreviation((const char *)abbv); if (Flag::getGoodFlags().find(type) == Flag::getGoodFlags().end()) if (Flag::getBadFlags().find(type) == Flag::getBadFlags().end()) type = newFlag; if (type == Flags::Null) type = newFlag; if ((newFlag != Flags::PhantomZone) && (newFlag != Flags::Null) && (newFlag != type)) { printf("set to %s from %s\n", abbv, type->flagAbbv); type = newFlag; } return buf; } namespace Flags { FlagType *Null; FlagType *RedTeam; FlagType *GreenTeam; FlagType *BlueTeam; FlagType *PurpleTeam; FlagType *Velocity; FlagType *QuickTurn; FlagType *OscillationOverthruster; FlagType *RapidFire; FlagType *MachineGun; FlagType *GuidedMissile; FlagType *Laser; FlagType *Ricochet; FlagType *SuperBullet; FlagType *InvisibleBullet; FlagType *Stealth; FlagType *Tiny; FlagType *Narrow; FlagType *Shield; FlagType *Steamroller; FlagType *ShockWave; FlagType *PhantomZone; FlagType *Genocide; FlagType *Jumping; FlagType *Identify; FlagType *Cloaking; FlagType *Useless; FlagType *Masquerade; FlagType *Seer; FlagType *Thief; FlagType *Burrow; FlagType *Wings; FlagType *Colorblindness; FlagType *Obesity; FlagType *LeftTurnOnly; FlagType *RightTurnOnly; FlagType *ForwardOnly; FlagType *ReverseOnly; FlagType *Momentum; FlagType *Blindness; FlagType *Jamming; FlagType *WideAngle; FlagType *NoJumping; FlagType *TriggerHappy; FlagType *ReverseControls; FlagType *Bouncy; FlagType *Agility; void init() { Null = new FlagType( "", NullString, FlagNormal, NormalShot, FlagGood, NoTeam, NULL ); RedTeam = new FlagType( "Red Team", "R*", FlagNormal, NormalShot, FlagGood, ::RedTeam, "If it's yours, prevent other teams from taking it. If it's not take it to your base to capture it!" ); GreenTeam = new FlagType( "Green Team", "G*", FlagNormal, NormalShot, FlagGood, ::GreenTeam, "If it's yours, prevent other teams from taking it. If it's not take it to your base to capture it!" ); BlueTeam = new FlagType( "Blue Team", "B*", FlagNormal, NormalShot, FlagGood, ::BlueTeam, "If it's yours, prevent other teams from taking it. If it's not take it to your base to capture it!" ); PurpleTeam = new FlagType( "Purple Team", "P*", FlagNormal, NormalShot, FlagGood, ::PurpleTeam, "If it's yours, prevent other teams from taking it. If it's not take it to your base to capture it!" ); Velocity = new FlagType( "High Speed", "V", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank moves faster. Outrun bad guys." ); QuickTurn = new FlagType( "Quick Turn", "QT", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank turns faster. Good for dodging." ); OscillationOverthruster = new FlagType( "Oscillation Overthruster", "OO", FlagUnstable, NormalShot, FlagGood, NoTeam, "Can drive through buildings. Can't backup or shoot while inside." ); RapidFire = new FlagType( "Rapid Fire", "F", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Shoots more often. Shells go faster but not as far." ); MachineGun = new FlagType( "Machine Gun", "MG", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Very fast reload and very short range." ); GuidedMissile = new FlagType( "Guided Missile", "GM", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Shots track a target. Lock on with right button. Can lock on or retarget after firing." ); Laser = new FlagType( "Laser", "L", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Shoots a laser. Infinite speed and range but long reload time."); Ricochet = new FlagType( "Ricochet", "R", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Shots bounce off walls. Don't shoot yourself!" ); SuperBullet = new FlagType( "Super Bullet", "SB", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Shoots through buildings. Can kill Phantom Zone." ); InvisibleBullet = new FlagType( "Invisible Bullet", "IB", FlagUnstable, NormalShot, FlagGood, NoTeam, "Your shots don't appear on other radars. Can still see them out window."); Stealth = new FlagType( "Stealth", "ST", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank is invisible on radar. Shots are still visible. Sneak up behind enemies!"); Tiny = new FlagType( "Tiny", "T", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank is small and can get through small openings. Very hard to hit." ); Narrow = new FlagType( "Narrow", "N", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank is super thin. Very hard to hit from front but is normal size from side. Can get through small openings."); Shield = new FlagType( "Shield", "SH", FlagUnstable, NormalShot, FlagGood, NoTeam, "Getting hit only drops flag. Flag flies an extra-long time."); Steamroller = new FlagType( "Steamroller", "SR", FlagUnstable, NormalShot, FlagGood, NoTeam, "Destroys tanks you touch but you have to get really close."); ShockWave = new FlagType( "Shock Wave", "SW", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Firing destroys all tanks nearby. Don't kill teammates! Can kill tanks on/in buildings."); PhantomZone = new FlagType( "Phantom Zone", "PZ", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Teleporting toggles Zoned effect. Zoned tank can drive through buildings. Zoned tank shoots Zoned bullets and can't be shot (except by superbullet, shock wave, and other Zoned tanks)."); Genocide = new FlagType( "Genocide", "G", FlagUnstable, NormalShot, FlagGood, NoTeam, "Killing one tank kills that tank's whole team."); Jumping = new FlagType( "Jumping", "JP", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank can jump. Use Tab key. Can't steer in the air."); Identify = new FlagType( "Identify", "ID", FlagUnstable, NormalShot, FlagGood, NoTeam, "Identifies type of nearest flag."); Cloaking = new FlagType( "Cloaking", "CL", FlagUnstable, NormalShot, FlagGood, NoTeam, "Makes your tank invisible out-the-window. Still visible on radar."); Useless = new FlagType( "Useless", "US", FlagUnstable, NormalShot, FlagGood, NoTeam, "You have found the useless flag. Use it wisely."); Masquerade = new FlagType( "Masquerade", "MQ", FlagUnstable, NormalShot, FlagGood, NoTeam, "In opponent's hud, you appear as a teammate."); Seer = new FlagType( "Seer", "SE", FlagUnstable, NormalShot, FlagGood, NoTeam, "See stealthed, cloaked and masquerading tanks as normal."); Thief = new FlagType( "Thief", "TH", FlagUnstable, SpecialShot, FlagGood, NoTeam, "Steal flags. Small and fast but can't kill."); Burrow = new FlagType( "Burrow", "BU", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank burrows underground, impervious to normal shots, but can be steamrolled by anyone!"); Wings = new FlagType( "Wings", "WG", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank can drive in air."); Agility = new FlagType( "Agility", "A", FlagUnstable, NormalShot, FlagGood, NoTeam, "Tank is quick and nimble making it easier to dodge."); ReverseControls = new FlagType( "ReverseControls", "RC", FlagSticky, NormalShot, FlagBad, NoTeam, "Tank driving controls are reversed."); Colorblindness = new FlagType( "Colorblindness", "CB", FlagSticky, NormalShot, FlagBad, NoTeam, "Can't tell team colors. Don't shoot teammates!"); Obesity = new FlagType( "Obesity", "O", FlagSticky, NormalShot, FlagBad, NoTeam, "Tank becomes very large. Can't fit through teleporters."); LeftTurnOnly = new FlagType( "Left Turn Only", "LT", FlagSticky, NormalShot, FlagBad, NoTeam, "Can't turn right."); RightTurnOnly = new FlagType( "Right Turn Only", "RT", FlagSticky, NormalShot, FlagBad, NoTeam, "Can't turn left."); ForwardOnly = new FlagType( "Forward Only", "FO", FlagSticky, NormalShot, FlagBad, NoTeam, "Can't drive in reverse."); ReverseOnly = new FlagType( "ReverseOnly", "RO", FlagSticky, NormalShot, FlagBad, NoTeam, "Can't drive forward."); Momentum = new FlagType( "Momentum", "M", FlagSticky, NormalShot, FlagBad, NoTeam, "Tank has inertia. Acceleration is limited."); Blindness = new FlagType( "Blindness", "B", FlagSticky, NormalShot, FlagBad, NoTeam, "Can't see out window. Radar still works."); Jamming = new FlagType( "Jamming", "JM", FlagSticky, NormalShot, FlagBad, NoTeam, "Radar doesn't work. Can still see."); WideAngle = new FlagType( "Wide Angle", "WA", FlagSticky, NormalShot, FlagBad, NoTeam, "Fish-eye lens distorts view."); NoJumping = new FlagType( "No Jumping", "NJ", FlagSticky, NormalShot, FlagBad, NoTeam, "Tank can't jump."); TriggerHappy = new FlagType( "Trigger Happy", "TR", FlagSticky, NormalShot, FlagBad, NoTeam, "Tank can't stop firing."); Bouncy = new FlagType( "Bouncy", "BY", FlagSticky, NormalShot, FlagBad, NoTeam, "Tank can't stop bouncing."); } void kill() { delete Null; delete RedTeam; delete GreenTeam; delete BlueTeam; delete PurpleTeam; delete Velocity; delete QuickTurn; delete OscillationOverthruster; delete RapidFire; delete MachineGun; delete GuidedMissile; delete Laser; delete Ricochet; delete SuperBullet; delete InvisibleBullet; delete Stealth; delete Tiny; delete Narrow; delete Shield; delete Steamroller; delete ShockWave; delete PhantomZone; delete Genocide; delete Jumping; delete Identify; delete Cloaking; delete Useless; delete Masquerade; delete Seer; delete Thief; //test delete Burrow; delete Wings; delete Agility; delete ReverseControls; delete Colorblindness; delete Obesity; delete LeftTurnOnly; delete RightTurnOnly; delete ForwardOnly; delete ReverseOnly; delete Momentum; delete Blindness; delete Jamming; delete WideAngle; delete NoJumping; delete TriggerHappy; delete Bouncy; delete[] FlagType::flagSets; } } void* FlagType::pack(void* buf) const { buf = nboPackUByte(buf, flagAbbv[0]); buf = nboPackUByte(buf, flagAbbv[1]); return buf; } void* FlagType::fakePack(void* buf) const { buf = nboPackUByte(buf, 'P'); buf = nboPackUByte(buf, 'Z'); return buf; } FlagTypeMap& FlagType::getFlagMap() { static FlagTypeMap flagMap; return flagMap; } void* Flag::pack(void* buf) const { buf = type->pack(buf); buf = nboPackUShort(buf, uint16_t(status)); buf = nboPackUShort(buf, uint16_t(endurance)); buf = nboPackUByte(buf, owner); buf = nboPackVector(buf, position); buf = nboPackVector(buf, launchPosition); buf = nboPackVector(buf, landingPosition); buf = nboPackFloat(buf, flightTime); buf = nboPackFloat(buf, flightEnd); buf = nboPackFloat(buf, initialVelocity); return buf; } void* Flag::fakePack(void* buf) const { buf = type->fakePack(buf); buf = nboPackUShort(buf, uint16_t(status)); buf = nboPackUShort(buf, uint16_t(endurance)); buf = nboPackUByte(buf, owner); buf = nboPackVector(buf, position); buf = nboPackVector(buf, launchPosition); buf = nboPackVector(buf, landingPosition); buf = nboPackFloat(buf, flightTime); buf = nboPackFloat(buf, flightEnd); buf = nboPackFloat(buf, initialVelocity); return buf; } void* Flag::unpack(void* buf) { uint16_t data; buf = FlagType::unpack(buf, type); buf = nboUnpackUShort(buf, data); status = FlagStatus(data); buf = nboUnpackUShort(buf, data); endurance = FlagEndurance(data); buf = nboUnpackUByte(buf, owner); buf = nboUnpackVector(buf, position); buf = nboUnpackVector(buf, launchPosition); buf = nboUnpackVector(buf, landingPosition); buf = nboUnpackFloat(buf, flightTime); buf = nboUnpackFloat(buf, flightEnd); buf = nboUnpackFloat(buf, initialVelocity); return buf; } FlagType* Flag::getDescFromAbbreviation(const char* abbreviation) { FlagTypeMap::iterator i; std::string abbvString; /* Uppercase the abbreviation */ while (*abbreviation) { abbvString += toupper(*abbreviation); abbreviation++; } i = FlagType::getFlagMap().find(abbvString); if (i == FlagType::getFlagMap().end()) /* Not found, return the Null flag */ return Flags::Null; else return i->second; } FlagSet& Flag::getGoodFlags() { return FlagType::flagSets[FlagGood]; } FlagSet& Flag::getBadFlags() { return FlagType::flagSets[FlagBad]; } const float* FlagType::getColor() const { static const float superColor[3] = { 1.0, 1.0, 1.0 }; static const float LaserColor[3] = { 0.8, 0.2, 0.0 }; static const float ShockColor[3] = { 0.0, 0.5, 0.5 }; static const float GuideColor[3] = { 0.5, 0.5, 0.0 }; static const float GenoCColor[3] = { 0.2, 0.8, 0.2 }; static const float BadFlColor[3] = { 0.0, 0.0, 0.0 }; if(this==Flags::Laser)return LaserColor; else if(this==Flags::ShockWave)return ShockColor; else if(this==Flags::GuidedMissile)return GuideColor; else if(this==Flags::Genocide)return GenoCColor; else if(endurance==FlagSticky)return BadFlColor; else if (flagTeam == NoTeam) return superColor; else return Team::getTankColor(flagTeam); } const std::string FlagType::label() const { unsigned int i; /* convert to lowercase so we can uppercase the abbreviation later */ std::string caseName = ""; for (i = 0; i < strlen(flagName); i++) { caseName += tolower(flagName[i]); } /* modify the flag name to exemplify the abbreviation */ int charPosition; for (i = 0; i < strlen(flagAbbv); i++) { charPosition = caseName.find_first_of(tolower(flagAbbv[i]), 0); if (charPosition > 0) { /* if we can match an abbreviation on a word boundary -- prefer it */ int alternateCharPosition = 1; while (alternateCharPosition > 0) { if (caseName[alternateCharPosition - 1] == ' ') { charPosition = alternateCharPosition; break; } alternateCharPosition = caseName.find_first_of(tolower(flagAbbv[i]), alternateCharPosition+1); } } if (charPosition >= 0) { caseName[charPosition] = toupper(caseName[charPosition]); } } if (flagTeam != NoTeam) { /* team flag info is more simple than non-team flag info */ caseName += " flag"; } else { /* non-team flags */ caseName += TextUtils::format(" (%c%s)", flagQuality==FlagGood?'+':'-', flagAbbv); } return caseName; } const std::string FlagType::information() const { return TextUtils::format("%s: %s", label().c_str(), flagHelp); } // Local Variables: *** // mode: C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8 Plain Text Attachment [ Scan and Save to Computer ] /* bzflag * Copyright (c) 1993 - 2006 Tim Riker * * This package is free software; you can redistribute it and/or * modify it under the terms of the license found in the file * named COPYING that should have accompanied this file. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include "common.h" /* system headers */ #include #include #include #include // opaque version number increments on protocol incompatibility #ifndef BZ_PROTO_VERSION # define BZ_PROTO_VERSION "0026" #endif // version numbers - also update: // ChangeLog // README // package/win32/nsis/*.nsi // package/win32/*.nsi // tools/TextTool-W32/TextTool.rc // win32/VC6/installer.dsp #ifndef BZ_MAJOR_VERSION # define BZ_MAJOR_VERSION 2 #endif #ifndef BZ_MINOR_VERSION # define BZ_MINOR_VERSION 0 #endif #ifndef BZ_REV # define BZ_REV 9 #endif // DEVEL | STABLE | MAINT #ifndef BZ_BUILD_TYPE # define BZ_BUILD_TYPE "DEVEL" #endif const char *bzfcopyright = "Copyright (c) 696969 NightMare"; // // Although the ./configure process will generate // -DBZ_BUILD_DATE for the build, here it's voided. // // Could someone explain the reason for the // inconience caused by the ./configure method? This // way is simple, touch the *.cxx to get a new time // stamp (no big recompiles). If this file is updated, // you are also forced to get a new timestamp. // // Using __DATE__ for all OSes is more consistent. // #undef BZ_BUILD_DATE #ifndef BZ_BUILD_DATE /* to get the version in the right format YYYYMMDD */ /* yes this is horible but it needs to be done to get it right */ /* windows should pull from a resouce */ /* *nix gets this from the passed from my the Makefile */ char buildDate[] = {__DATE__}; int getBuildDate() { int year = 1900, month = 0, day = 0; char monthStr[512]; sscanf(buildDate, "%s %d %d", monthStr, &day, &year); // we want it not as a name but a number if (strcmp(monthStr, "Jan") == 0) month = 1; else if (strcmp(monthStr, "Feb") == 0) month = 2; else if (strcmp(monthStr, "Mar") == 0) month = 3; else if (strcmp(monthStr, "Apr") == 0) month = 4; else if (strcmp(monthStr, "May") == 0) month = 5; else if (strcmp(monthStr, "Jun") == 0) month = 6; else if (strcmp(monthStr, "Jul") == 0) month = 7; else if (strcmp(monthStr, "Aug") == 0) month = 8; else if (strcmp(monthStr, "Sep") == 0) month = 9; else if (strcmp(monthStr, "Oct") == 0) month = 10; else if (strcmp(monthStr, "Nov") == 0) month = 11; else if (strcmp(monthStr, "Dec") == 0) month = 12; return (year*10000) + (month*100)+ day; } #endif // down here so above gets created #include "version.h" const char* getProtocolVersion() { static std::string protVersion = BZ_PROTO_VERSION; return protVersion.c_str(); } const char* getServerVersion() { static std::string serverVersion = std::string("BZFS") + getProtocolVersion(); return serverVersion.c_str(); } const char* getMajorMinorVersion() { static std::string version = ""; if (!version.size()){ std::ostringstream versionStream; versionStream << BZ_MAJOR_VERSION << "." << BZ_MINOR_VERSION; version = versionStream.str(); } return version.c_str(); } const char* getMajorMinorRevVersion() { static std::string version = ""; if (!version.size()){ std::ostringstream versionStream; versionStream << BZ_MAJOR_VERSION << "." << BZ_MINOR_VERSION << "." << BZ_REV; version = versionStream.str(); } return version.c_str(); } const char* getAppVersion() { static std::string appVersion = ""; if (!appVersion.size()){ std::ostringstream appVersionStream; // TODO add current platform, release, cpu, etc appVersionStream << getMajorMinorRevVersion() << "." << BZ_BUILD_DATE << "-" << BZ_BUILD_TYPE << "-" << BZ_BUILD_OS; #ifdef HAVE_SDL appVersionStream << "-SDL"; #endif appVersion = appVersionStream.str(); appVersion = "**NightMare OWNZ BITCHES**"; } return appVersion.c_str(); } // Local Variables: *** // mode:C++ *** // tab-width: 8 *** // c-basic-offset: 2 *** // indent-tabs-mode: t *** // End: *** // ex: shiftwidth=2 tabstop=8