/* Copyright (C) 2006 Parallel Realities This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "../headers.h" Engine::Engine() { dataBuffer = NULL; binaryBuffer = NULL; mouse = Mouse::getInstance(); joystick = NULL; buttonState = NULL; axis1X = axis1Y = 0; axis2X = axis2Y = 0; // Timer time1 = time2 = 0; timeDifference = 0; slowLogic = false; inertiaRate = 0; lastReadDataSize = 0; defineList.setName("Define List"); pak = Pak::getInstance(); debugTimerStart = 0; showFPS = false; showPosition = false; memset(lastKeyEvents, ' ', 35); } Engine::~Engine() { if (!buttonState) { delete[] buttonState; } if (dataBuffer != NULL) { delete[] dataBuffer; } if (binaryBuffer != NULL) { delete[] binaryBuffer; } } Engine *Engine::getInstance() { return &instance; } void Engine::addKeyEvent(char *key) { if (strlen(key) > 1) { return; } int index = -1; for (int i = 0 ; i < 35 ; i++) { if (lastKeyEvents[i] == ' ') { index = i; break; } } if (index == -1) { for (int i = 0 ; i < 35 ; i++) { lastKeyEvents[i] = lastKeyEvents[i + 1]; } index = 24; } char k = key[0]; if ((k >= SDLK_a - 32) && (k <= SDLK_z - 32)) { k += 32; } lastKeyEvents[index] = k; } void Engine::getInput() { SDL_GetMouseState(&mouse->x, &mouse->y); while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: exit(0); break; case SDL_MOUSEBUTTONDOWN: mouse->buttonState[event.button.button] = 1; fireMousePressed(event.button); break; case SDL_MOUSEBUTTONUP: if ((event.button.button != SDL_BUTTON_WHEELUP) && (event.button.button != SDL_BUTTON_WHEELDOWN)) { mouse->buttonState[event.button.button] = 0; fireMouseReleased(event.button); } break; case SDL_MOUSEMOTION: fireMouseMoved(); break; case SDL_KEYDOWN: keyState[event.key.keysym.sym] = 1; fireKeyPressed(&event.key); addKeyEvent(SDL_GetKeyName(event.key.keysym.sym)); break; case SDL_KEYUP: keyState[event.key.keysym.sym] = 0; break; case SDL_JOYAXISMOTION: if (event.jaxis.axis == 0) { axis1X = event.jaxis.value; } else if (event.jaxis.axis == 1) { axis1Y = event.jaxis.value; } else if ((event.jaxis.axis % 2) == 1) { axis2X = event.jaxis.value; } else if ((event.jaxis.axis % 2) == 0) { axis2Y = event.jaxis.value; } break; case SDL_JOYBUTTONDOWN: buttonState[event.jbutton.button] = 1; fireJoystickButtonPressed(event.jbutton.button); break; case SDL_JOYBUTTONUP: buttonState[event.jbutton.button] = 0; break; } } } void Engine::fireMousePressed(SDL_MouseButtonEvent mouse) { //debug(("Engine::fireMousePressed()\n")); UIManager::getInstance()->mousePressed(mouse); } void Engine::fireMouseReleased(SDL_MouseButtonEvent mouse) { //debug(("Engine::fireMouseReleased()\n")); UIManager::getInstance()->mouseReleased(mouse); } void Engine::fireMouseMoved() { //debug(("Engine::fireMouseMoved()\n")); UIManager::getInstance()->mouseMoved(mouse->x, mouse->y); } void Engine::fireKeyPressed(SDL_KeyboardEvent *key) { //debug(("Engine::fireKeyPressed()\n")); UIManager::getInstance()->keyPressed(key->keysym.sym, key->keysym.mod & KMOD_SHIFT); } void Engine::fireJoystickButtonPressed(int key) { UIManager::getInstance()->joystickButtonPressed(key); } void Engine::startDebugTimer(char *text) { debugTimerStart = SDL_GetTicks(); debug(("%s Started...\n", text)); } void Engine::endDebugTimer(char *text) { long now = SDL_GetTicks(); debug(("%s Finished. Took %d milliseconds\n", text, (int)(now - debugTimerStart))); } bool Engine::userAccepts() { if ((keyState[SDLK_SPACE]) || (keyState[SDLK_ESCAPE]) || (keyState[SDLK_LCTRL]) || (keyState[SDLK_RCTRL]) || (keyState[SDLK_RETURN])) { return true; } return false; } void Engine::flushInput() { while (SDL_PollEvent(&event)){} } void Engine::clearInput() { for (int i = 0 ; i < 350 ; i++) { keyState[i] = 0; } if (buttonState != NULL) { for (int i = 0 ; i < SDL_JoystickNumButtons(joystick) ; i++) { buttonState[i] = 0; } } mouse->clear(); } /* Searches the pak file for the required data. When it is found, the data is read into a character buffer. In the case of music, the data music be written to a temporary directory since SDL currently provides no means to load music directly from memory */ bool Engine::unpack(char *filename, int fileType) { bool success = true; if (fileType == PAK_DATA) { if (dataBuffer != NULL) { delete[] dataBuffer; } dataBuffer = NULL; } else { if (binaryBuffer != NULL) { delete[] binaryBuffer; } binaryBuffer = NULL; } if (fileType != PAK_DATA) { success = pak->unpack(filename, &binaryBuffer); } else { success = pak->unpack(filename, &dataBuffer); } if (!success) { return false; } if ((fileType == PAK_IMG) || (fileType == PAK_SOUND)) { debug(("Doing RWops...\n")); sdlrw = SDL_RWFromMem(binaryBuffer, pak->getUncompressedSize()); if (!sdlrw) { printf("Fatal Error: SDL_RWops allocation failed\n"); exit(1); } } if ((fileType == PAK_MUSIC) || (fileType == PAK_FONT)) { FILE *fp; if (fileType == PAK_MUSIC) { fp = fopen(TEMPDIR"music.mod", "wb"); } if (fileType == PAK_FONT) { fp = fopen(TEMPDIR"font.ttf", "wb"); } if (!fp) { return false; } fwrite(binaryBuffer, 1, pak->getUncompressedSize(), fp); fclose(fp); } debug(("unpack() : Loaded %s (%d)\n", filename, pak->getUncompressedSize())); return true; } bool Engine::loadData(char *filename) { FILE *fp; lastReadDataSize = 0; if (dataBuffer != NULL) { delete[] dataBuffer; dataBuffer = NULL; } int location = pak->getFileLocation(filename); switch (location) { case FILE_IN_FS: fp = fopen(pak->getFileName(), "rb"); fseek(fp, 0, SEEK_END); lastReadDataSize = ftell(fp); rewind(fp); dataBuffer = new unsigned char[lastReadDataSize + 1]; fread(dataBuffer, 1, lastReadDataSize, fp); dataBuffer[lastReadDataSize] = 0; fclose(fp); debug(("loadData() : Loaded %s (%d)\n", pak->getFileName(), lastReadDataSize)); return true; break; case FILE_IN_PAK: return unpack(filename, PAK_DATA); break; case FILE_NOT_FOUND: return false; break; } return false; } void Engine::doTimeDifference() { inertiaRate -= (int)inertiaRate; timeDifference = (time2 - time1) / 10.0; time1 = time2; time2 = SDL_GetTicks(); inertiaRate += timeDifference; } float Engine::getTimeDifference(int tdType) { if ((slowLogic) && (tdType == TD_LOGIC)) { return timeDifference * 0.25; } return timeDifference; } float Engine::getInertiaRate() { return inertiaRate; } void Engine::resetTimeDifference() { time1 = time2 = SDL_GetTicks(); inertiaRate = 0; } /* Loads key-value defines into a linked list, comments are ignored. The defines.h file is used by the game at compile time and run time, so everything is syncronised. This technique has the advantage of allowing the game's data to be human readable and easy to maintain. */ bool Engine::loadDefines() { char string[2][1024]; if (!loadData("data/gameDefs/defines.h")) { Graphics::getInstance()->showErrorAndExit(ERR_FILE, "data/gameDefs/defines.h"); } char *token = strtok((char*)dataBuffer, "\n"); Data *data; bool readEnum = false; int value = 0; while (true) { if (!token) { break; } if (strstr(token, "/*")) { token = strtok(NULL, "\n"); continue; } if (strstr(token, "enum {")) { readEnum = true; value = 0; token = strtok(NULL, "\n"); continue; } if (strstr(token, "};")) { readEnum = false; token = strtok(NULL, "\n"); continue; } if (strlen(token) < 9) { token = strtok(NULL, "\n"); continue; } // power of if (strstr(token, "<<")) { sscanf(token, "%*s %s %*s %*s %*s %d", string[0], &value); value = 2 << value; sprintf(string[1], "%d", value); } else { if (!readEnum) { sscanf(token, "%*s %s %[^\n]", string[0], string[1]); } else { if (strstr(token, ",")) { sscanf(token, "%*c %[^,]", string[0]); } else { sscanf(token, "%*c %s", string[0]); } sprintf(string[1], "%d", value); value++; } } data = new Data(); data->setKey(string[0]); data->setValue(string[1]); defineList.add(data); token = strtok(NULL, "\n"); } return true; } /* Returns the value of a #defined value... ACTIVE is declared as 1 so it will traverse the list and return 1 when it encounters ACTIVE. This has two advantages. 1) It makes the game data human readable and 2) It means if I change a #define in the code, I don't have to change all the data entries too. You probably already thought of that though... :) */ int Engine::getValueOfDefine(char *word) { // might be a real number! int length = strlen(word); if (length > 0) { if ((isdigit(word[0])) && (isdigit(word[length - 1]))) { return atoi(word); } else if ((word[0] == '-') && (isdigit(word[length - 1]))) { return atoi(word); } } int rtn = 0; Data *data = (Data*)defineList.getFirstElement(); while (data != NULL) { if (data->key == word) { rtn = atoi(data->getValue()); return rtn; } data = (Data*)data->next; } printf("ERROR: getValueOfDefine() : %s is not defined!\n", word); exit(1); } /* Does the opposite of the above(!) */ char *Engine::getDefineOfValue(char *prefix, int value) { int rtn = 0; Data *data = (Data*)defineList.getFirstElement(); while (data != NULL) { if (strstr(data->getKey(), prefix)) { rtn = atoi(data->getValue()); if (rtn == value) { return data->getKey(); } } data = (Data*)data->next; } printf("ERROR: getDefineOfValue() : %s, %d is not defined!\n", prefix, value); exit(1); } /* I like this function. It receives a list of flags declared by their #define name... like the function above, delimited with plus signs. So ENT_FLIES+ENT_AIMS. It then works out the flags (in a bit of a half arsed manner because of my lazy (2 << 25) declarations, adds all the values together and then returns them... phew! Makes data files human readable though :) */ int Engine::getValueOfFlagTokens(char *realLine) { // might be a real number! int length = strlen(realLine); if (length > 0) { if ((isdigit(realLine[0])) && (isdigit(realLine[length - 1]))) { return atoi(realLine); } } char *store = NULL; char line[1024]; bool found = false; int value = 0; strcpy(line, realLine); int flags = 0; char *word = strtok_r(line, "+", &store); if (!word) { printf("ERROR: getValueOfFlagTokens() : NULL Pointer!\n"); return 0; } Data *data = NULL; while (true) { data = (Data*)defineList.getFirstElement(); found = false; while (data != NULL) { if (strcmp(data->getKey(), word) == 0) { value = -1; sscanf(data->getValue(), "%d", &value); flags += value; found = true; break; } data = (Data*)data->next; } if (!found) { printf("ERROR: getValueOfFlagTokens() : Illegal Token '%s'\n", word); printf("ERROR: Ensure there are no spaces in token line!!\n"); #if IGNORE_FLAGTOKEN_ERRORS break; #else exit(1); #endif } word = strtok_r(NULL, "+", &store); if (!word) { break; } } //debug(("Engine::getValueOfFlagTokens - Returning %d\n", flags)); return flags; } void Engine::saveSaveGameHeaders() { String filename; filename.setText("%s/savedata", userHomeDirectory.getText()); FILE *fp = fopen(filename.getText(), "wb"); fprintf(fp, "// WARNING - DO NOT EDIT THIS FILE!!!\n"); fprintf(fp, "// THIS FILE IS GENERATED BY BLOB AND CONQUER AND IF YOU EDIT THIS FILE\n"); fprintf(fp, "// YOU MAY LOSE ACCESS TO YOUR SAVE GAMES! YOU HAVE BEEN WARNED!\n\n"); Properties props; props.setName("SaveGame"); for (int i = 0 ; i < MAX_SAVE_SLOTS ; i++) { saveSlot[i].save(fp); } fprintf(fp, "@END@\n"); fclose(fp); } Engine Engine::instance;