diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7be95ab --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,180 @@ +cmake_minimum_required(VERSION 3.5) + +set(GAME_NAME planetFleet) +project(${GAME_NAME}) + +option(BUILD_GAME_TESTS "Build game unit tests" OFF) + +set(STATES + source/core/appStates/activeGameState.cpp + source/core/appStates/inGameAppState.cpp + source/core/appStates/guiAppState.cpp + source/core/appStates/mapEditorAppState.cpp + source/core/appStates/loadingAppState.cpp) + +set(CONSOLE + source/core/console/console.cpp + source/core/console/abstractCommand.cpp + source/core/console/addUnitCommand.cpp + source/core/console/addResourceCommand.cpp + source/core/console/addTechnologyCommand.cpp + source/core/console/toggleDebugCommand.cpp) + +set(GAME_SRC + source/core/game/game.cpp + source/core/game/gameManager.cpp) + +set( + CORE_SRC + source/core/pathfinding/pathfinder.cpp + source/utils/util.cpp + source/Core/defConfigs.cpp + source/core/controllers/cameraController.cpp + source/core/controllers/gameObjectFrameController.cpp) + ${STATES} ${CONSOLE} ${PATHFINDING_SRC} ${UTILS_SRC} ${GAME} + +set(UNIT_BUTTONS + source/ui/buttons/unitButton.cpp + source/ui/buttons/tradeButton.cpp + source/ui/buttons/buildButton.cpp + source/ui/buttons/trainButton.cpp + source/ui/buttons/researchButton.cpp + source/ui/buttons/orderButton.cpp + source/ui/buttons/stateToggleButton.cpp) + +set(OPTIONS_BUTTONS + source/ui/buttons/optionsButton.cpp + source/ui/buttons/tabButton.cpp + source/ui/buttons/okButton.cpp + source/ui/buttons/defaultsButton.cpp) + +set(TRADING_BUTTONS + source/ui/buttons/activeStateBackButton.cpp + source/ui/buttons/playerTradeButton.cpp + source/ui/buttons/tradingScreenButton.cpp + source/ui/buttons/offerButton.cpp + source/ui/buttons/resourceAmmountButton.cpp) + +set(MENU_BUTTONS + source/ui/buttons/mainMenuButton.cpp + source/ui/buttons/singlePlayerButton.cpp + source/ui/buttons/exitButton.cpp + source/ui/buttons/backButton.cpp + source/ui/buttons/playButton.cpp) + +set(MAP_EDITOR_BUTTONS + source/ui/buttons/mapEditorButton.cpp + source/ui/buttons/newMapButton.cpp + source/ui/buttons/loadMapButton.cpp + source/ui/buttons/exportButton.cpp) + +set(BUTTONS + source/ui/buttons/statsButton.cpp + source/ui/buttons/activeStateButton.cpp + source/ui/buttons/minimapButton.cpp + ${TRADING_BUTTONS} ${OPTIONS_BUTTONS} ${MENU_BUTTONS} + ${MAP_EDITOR_BUTTONS} ${UNIT_BUTTONS}) + +set(LISTBOXES + source/ui/listboxes/skyboxTextureListbox.cpp + source/ui/listboxes/landTextureListbox.cpp + source/ui/listboxes/gameObjectListbox.cpp + source/ui/listboxes/mapListbox.cpp) + +set(GUI_SRC + source/gui/tooltip.cpp + source/gui/concreteGuiManager.cpp + source/gui/gameObjectFrame.cpp + ${BUTTONS} ${LISTBOXES}) + +set(ENVIRONMENT_SRC + source/gameplay/Private/environment.cpp + source/gameplay/Private/fxManager.cpp + source/gameplay/Private/explosion.cpp + source/gameplay/Private/map.cpp +) + +set(PLAYER_SRC + source/gameplay/Private/player.cpp + source/gameplay/Private/trader.cpp +) + +set(PROJECTILES + source/gameplay/projectile.cpp + source/gameplay/missile.cpp + source/gameplay/cruiseMissile.cpp + source/gameplay/shell.cpp + source/gameplay/depthCharge.cpp + source/gameplay/torpedo.cpp) + +set(VEHICLES + source/gameplay/vehicle.cpp + source/gameplay/submarine.cpp + source/gameplay/engineer.cpp + source/gameplay/resourceRover.cpp + source/gameplay/freezer.cpp + source/gameplay/vessel.cpp) + +set(STRUCTURES + source/gameplay/structure.cpp + source/gameplay/factory.cpp + source/gameplay/pointDefense.cpp + source/gameplay/extractor.cpp + source/gameplay/researchStruct.cpp + source/gameplay/iceSheet.cpp) + +set(UNITS_SRC + source/gameplay/unit.cpp + source/gameplay/garrisonable.cpp + ${VEHICLES} ${STRUCTURES} +) + +set(GAME_OBJECT_SRC + source/gameplay/Private/resourceDeposit.cpp + ${UNITS} ${PROJECTILES} +) + +set(GAMEPLAY_SRC + source/gameplay/Private/gameObject.cpp + source/gameplay/Private/gameObjectFactory.cpp + source/gameplay/Private/tradeCenter.cpp + source/gameplay/Private/destructable.cpp + source/gameplay/Private/weapon.cpp + ${ENVIRONMENT_SRC} ${PLAYER_SRC} ${GAME_OBJECT_SRC}) + + +set(GAME_SRC ${CORE_SRC} ${GUI_SRC} ${GAMEPLAY_SRC}) + +add_executable(${GAME_NAME} source/core/main.cpp ${GAME_SRC}) + +target_include_directories(${GAME_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/source/core + ${CMAKE_CURRENT_SOURCE_DIR}/source/gameplay + ${CMAKE_CURRENT_SOURCE_DIR}/source/gui + ${CMAKE_CURRENT_SOURCE_DIR}/source/utils) + +target_link_libraries(${GAME_NAME} PRIVATE vb01 gameBase) + +if(BUILD_GAME_TESTS) + if(UNIX) + include_directories(/usr/include/cppunit) + link_directories(/usr/lib) + elseif(WIN32) + include_directories("C:/Program Files (x86)/cppunit/include") + link_directories("C:/Program Files (x86)/cppunit/lib") + endif() + + set(TEST_SRC + tests/testMain.cpp + tests/pathfinderTest.cpp + ${GAME_SRC}) + + add_executable(battleshipTests ${TEST_SRC}) + target_include_directories(planetFleetTests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/source/core + ${CMAKE_CURRENT_SOURCE_DIR}/source/gameplay + ${CMAKE_CURRENT_SOURCE_DIR}/source/gui + ${CMAKE_CURRENT_SOURCE_DIR}/source/utils) + ${CMAKE_CURRENT_SOURCE_DIR}/source/tests) + target_link_libraries(planetFleetTests PRIVATE vb01 gameBase cppunit) +endif() diff --git a/source/core/appStates/activeGameState.cpp b/source/core/appStates/activeGameState.cpp new file mode 100644 index 0000000..1e65dc9 --- /dev/null +++ b/source/core/appStates/activeGameState.cpp @@ -0,0 +1,857 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "activeGameState.h" +#include "inGameAppState.h" +#include "defConfigs.h" +#include "structure.h" +#include "game.h" +#include "util.h" +#include "tooltip.h" +#include "gameObjectFrameController.h" +#include "gameObjectFactory.h" +#include "cameraController.h" +#include "unitButton.h" +#include "extractor.h" +#include "resourceDeposit.h" +#include "resourceRover.h" +#include "concreteGuiManager.h" + +using namespace vb01; +using namespace vb01Gui; +using namespace std; + +namespace battleship{ + using namespace configData; + using namespace gameBase; + + ActiveGameState::ActiveGameState(GuiAppState *gs, int plId) : AbstractAppState( + AppStateType::ACTIVE_STATE, + configData::calcSumBinds(AppStateType::ACTIVE_STATE, true), + configData::calcSumBinds(AppStateType::ACTIVE_STATE, false), + GameManager::getSingleton()->getPath() + scripts[(int)ScriptFiles::OPTIONS]), guiState(gs), playerId(plId), depth(1){ + initDragbox(); + + mainPlayer = Game::getSingleton()->getPlayer(playerId); + } + + ActiveGameState::~ActiveGameState() { + removeDragbox(); + } + + void ActiveGameState::initDragbox(){ + Root *root = Root::getSingleton(); + + Material *mat = new Material(root->getLibPath() + "gui"); + mat->addBoolUniform("texturingEnabled", false); + mat->addBoolUniform("diffuseColorEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4(.5, .5, .5, .5)); + + Quad *dragbox = new Quad(Vector3::VEC_ZERO, false); + dragbox->setMaterial(mat); + dragboxNode = new Node(); + dragboxNode->attachMesh(dragbox); + + root->getGuiNode()->attachChild(dragboxNode); + } + + void ActiveGameState::initCursor(){ + string basePath = GameManager::getSingleton()->getPath() + "Textures/Icons/Cursors/"; + sol::state_view SOL_LUA_VIEW = generateView(); + + string pt = SOL_LUA_VIEW["pointerTex"], + at = SOL_LUA_VIEW["attackTex"], + gt = SOL_LUA_VIEW["garrisonTex"], + st = SOL_LUA_VIEW["supplyTex"], + ht = SOL_LUA_VIEW["hackTex"], + ngt = SOL_LUA_VIEW["noGarrisonTex"], + nst = SOL_LUA_VIEW["noSupplyTex"], + nht = SOL_LUA_VIEW["noHackTex"], + p1[]{basePath + pt}, + p2[]{basePath + at}, + p3[]{basePath + gt}, + p4[]{basePath + st}, + p5[]{basePath + ht}, + p6[]{basePath + ngt}, + p7[]{basePath + nst}, + p8[]{basePath + nht}; + pointerTex = new Texture(p1, 1, false); + attackTex = new Texture(p2, 1, false); + garrisonTex = new Texture(p3, 1, false); + supplyTex = new Texture(p4, 1, false); + hackTex = new Texture(p5, 1, false); + noGarrisonTex = new Texture(p6, 1, false); + noSupplyTex = new Texture(p7, 1, false); + noHackTex = new Texture(p8, 1, false); + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->addBoolUniform("texturingEnabled", true); + mat->addTexUniform("diffuseMap", pointerTex, false); + + Quad *quad = new Quad(Vector3(20, 20, 0), false); + quad->setMaterial(mat); + + cursorNode = new Node(); + cursorNode->attachMesh(quad); + root->getGuiNode()->attachChild(cursorNode); + } + + void ActiveGameState::removeDragbox(){ + Root::getSingleton()->getRootNode()->dettachChild(dragboxNode); + delete dragboxNode; + } + + void ActiveGameState::onAttached() { + AbstractAppState::onAttached(); + vector buttons = guiButtons; + buttons.insert(buttons.end(), unitButtons.begin(), unitButtons.end()); + + ConcreteGuiManager::getSingleton()->readLuaScreenScript( + "activeGameState.lua", + buttons, + vector{}, + vector{}, + vector{}, + vector{}, + guiRects, + guiTexts, + "music = generateFactionMusic(" + to_string(mainPlayer->getFaction()) + ")" + ); + + if(!cursorNode) initCursor(); + } + + void ActiveGameState::onDettached() { + AbstractAppState::onDettached(); + } + + void ActiveGameState::updateCursor(){ + Vector2 cursorPos = getCursorPos(); + cursorNode->setPosition(Vector3(cursorPos.x, cursorPos.y, .8)); + + if(mainPlayer->getNumSelectedUnits() > 0){ + bool ownGameObj = (gameObjHoveredOn && gameObjHoveredOn->getPlayer() == mainPlayer); + bool alliedGameObj = (gameObjHoveredOn && !ownGameObj && gameObjHoveredOn->getPlayer()->getTeam() == mainPlayer->getTeam()); + bool gameObjUnit = (gameObjHoveredOn && gameObjHoveredOn->getType() == GameObject::Type::UNIT); + bool gameObjTransport = (gameObjUnit && ((Unit*)gameObjHoveredOn)->getNumGarrisonSlots() > 0); + bool canGarrison = true; + + if(gameObjUnit && gameObjTransport) + for(int i = 0; i < mainPlayer->getNumSelectedUnits(); i++) + if(!((Unit*)gameObjHoveredOn)->canGarrison((Vehicle*)mainPlayer->getSelectedUnit(i))){ + canGarrison = false; + break; + } + + vector offers = (gameObjHoveredOn && alliedGameObj ? mainPlayer->getTradeOffers(gameObjHoveredOn->getPlayer()) : vector{}); + bool tradeResource[NUM_RESOURCES][2], trade = false; + + for(int i = 0; i < NUM_RESOURCES; i++) + for(int j = 0; j < 2; j++){ + if(!offers.empty() && offers[0]->tradeResources[i][j] > 0){ + tradeResource[i][j] = true; + trade = true; + } + else tradeResource[i][j] = false; + } + + int objClass = (gameObjUnit ? int(((Unit*)gameObjHoveredOn)->getUnitClass()) : -1); + bool tradeCenter = ((UnitClass)objClass == UnitClass::TRADE_CENTER); + bool refinery = ((UnitClass)objClass == UnitClass::REFINERY); + bool lab = ((UnitClass)objClass == UnitClass::LAB); + bool ownExtractor = (ownGameObj && (UnitClass)objClass == UnitClass::EXTRACTOR); + + bool roverSelected = (mainPlayer->getSelectedUnit(0)->getUnitClass() == UnitClass::RESOURCE_ROVER); + ResourceRover *rover = (roverSelected ? (ResourceRover*)mainPlayer->getSelectedUnit(0) : nullptr); + int unloadFlag = (int)shiftPressed; + + if(!forceCursorState){ + if(gameObjUnit && gameObjTransport){ + orderPossible = (ownGameObj && canGarrison); + cursorState = CursorState::GARRISON; + } + else if(roverSelected && ownExtractor){ + orderPossible = (((Extractor*)gameObjHoveredOn)->getDeposit()->getAmmount() > 0); + cursorState = CursorState::SUPPLY; + } + else if(roverSelected && (tradeCenter || refinery || lab) && (ownGameObj || (alliedGameObj && trade))){ + bool tradeRes = false; + int resId; + + switch((UnitClass)objClass){ + case UnitClass::TRADE_CENTER: + tradeRes = tradeResource[(int)ResourceType::WEALTH][unloadFlag]; + resId = (int)ResourceType::WEALTH; + break; + case UnitClass::REFINERY: + tradeRes = tradeResource[(int)ResourceType::REFINEDS][unloadFlag]; + resId = (int)ResourceType::REFINEDS; + break; + case UnitClass::LAB: + tradeRes = tradeResource[(int)ResourceType::RESEARCH][unloadFlag]; + resId = (int)ResourceType::RESEARCH; + break; + } + + bool canLoad = (rover && rover->calcTotalLoad() < rover->getCapacity()); + orderPossible = (unloadFlag ? rover->getLoad(resId) > 0 : canLoad); + + if(alliedGameObj) orderPossible = tradeRes && orderPossible; + + cursorState = (unloadFlag ? CursorState::UNLOAD : CursorState::LOAD); + } + else if(gameObjUnit && !(ownGameObj || alliedGameObj)){ + orderPossible = true; + cursorState = CursorState::ATTACK; + } + else + cursorState = CursorState::NORMAL; + } + else{ + if(cursorState == CursorState::GARRISON) + orderPossible = (ownGameObj && gameObjTransport && canGarrison); + else if(cursorState == CursorState::SUPPLY) + orderPossible = (roverSelected && ownExtractor); + else if(cursorState == CursorState::LOAD || cursorState == CursorState::UNLOAD){ + int ulf = int(cursorState == CursorState::UNLOAD); + bool tradeRefs = tradeResource[(int)ResourceType::REFINEDS][ulf]; + bool tradeWealth = tradeResource[(int)ResourceType::WEALTH][ulf]; + bool tradeTech = tradeResource[(int)ResourceType::RESEARCH][ulf]; + + bool canLoad = (rover->calcTotalLoad() < rover->getCapacity()); + bool loadResource = ( + cursorState == CursorState::LOAD ? canLoad : ( + rover->getLoad((int)ResourceType::REFINEDS) > 0 || + rover->getLoad((int)ResourceType::WEALTH) > 0 || + rover->getLoad((int)ResourceType::RESEARCH) > 0 + ) + ); + + bool transferResource = ( + (refinery && loadResource && (ownGameObj || (alliedGameObj && tradeRefs))) || + (tradeCenter && loadResource && (ownGameObj || (alliedGameObj && tradeWealth))) || + (lab && loadResource && (ownGameObj || (alliedGameObj && tradeTech))) + ); + orderPossible = (roverSelected && transferResource); + } + else if(cursorState == CursorState::HACK) + orderPossible = (!ownGameObj && gameObjUnit && mainPlayer->getSelectedUnit(0)->getUnitClass() == UnitClass::CYBORG_ENGINEER); + else if(controlPressed) + cursorState = CursorState::ATTACK; + else + cursorState = CursorState::NORMAL; + } + } + + cursorNode->setVisible(cursorState != CursorState::NORMAL); + Texture *tex = nullptr; + + switch(cursorState){ + case CursorState::ATTACK: + tex = attackTex; + break; + case CursorState::GARRISON: + tex = (orderPossible ? garrisonTex : noGarrisonTex); + break; + case CursorState::SUPPLY: + case CursorState::LOAD: + case CursorState::UNLOAD: + tex = (orderPossible ? supplyTex : noSupplyTex); + break; + case CursorState::HACK: + tex = (orderPossible ? hackTex : noHackTex); + break; + } + + cursorNode->getMesh(0)->getMaterial()->setTexUniform("diffuseMap", tex, false); + } + + void ActiveGameState::update() { + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + guiManager->getText("refineds")->setText(to_wstring(mainPlayer->getResource(ResourceType::REFINEDS))); + guiManager->getText("wealth")->setText(to_wstring(mainPlayer->getResource(ResourceType::WEALTH))); + guiManager->getText("research")->setText(to_wstring(mainPlayer->getResource(ResourceType::RESEARCH))); + + updateCursor(); + Vector2 cursorPos = getCursorPos(); + + float eps = .01; + + if(!isSelectionBox && selectMouseClicked && (fabs(clickPoint.x - cursorPos.x) > eps || fabs(clickPoint.y - cursorPos.y) > eps) && getTime() - lastSelectMouseClicked > 10) + isSelectionBox = true; + else if (isSelectionBox) + updateDragBox(); + + dragboxNode->setVisible(isSelectionBox); + + updateGameObjHoveredOn(); + + vector selectedUnits = mainPlayer->getSelectedUnits(); + GameObjectFrameController *fc = GameObjectFrameController::getSingleton(); + + if(!selectingDestOrient && orderMouseClicked && !selectedUnits.empty() && getTime() - lastOrderMouseClicked > 100){ + if(targets.empty()) castRayToTerrain(); + + if(!buildableStructSelected) + for(int i = 0; i < selectedUnits.size(); i++) + fc->addGameObjectFrame(GameObjectFrame(selectedUnits[i]->getId(), GameObject::Type::UNIT, mainPlayer, selectedUnits[i], targets[0].pos)); + + selectingDestOrient = true; + fc->setRotating(true); + } + else if(selectingDestOrient && !orderMouseClicked){ + selectingDestOrient = false; + + fc->toggleFrameTransformations(false, false, false); + fc->removeGameObjectFrames(); + } + + if(!tradingScreen){ + guiTexts = guiManager->getTexts(); + guiRects = guiManager->getGuiRectangles(); + guiButtons = guiManager->getButtons(); + } + + renderUnits(); + + vector currSelectedUnits = mainPlayer->getSelectedUnits(); + + if(prevSelectedUnits != currSelectedUnits){ + addUnitGui(); + prevSelectedUnits = currSelectedUnits; + } + + if(fc->isRotating() || fc->isPlacingOnSurface() || fc->isPlacingVertically()) + fc->update(); + + CameraController *camCtr = CameraController::getSingleton(); + + if(!camCtr->isLookingAround()) + camCtr->updateCameraPosition(); + } + + void ActiveGameState::updateGameObjHoveredOn(){ + vector gameObjs; + vector players = Game::getSingleton()->getPlayers(true); + + for(Player *pl : players){ + vector resDeps = pl->getResourceDeposits(); + vector units = pl->getUnits(); + + for(ResourceDeposit *rd : resDeps) + gameObjs.push_back((GameObject*)rd); + + for(Unit *u : units) + gameObjs.push_back((GameObject*)u); + } + + for(GameObject *gameObj : gameObjs) + if(isGameObjSelectable(gameObj, false)){ + gameObjHoveredOn = gameObj; + return; + } + + gameObjHoveredOn = nullptr; + } + + void ActiveGameState::deselectUnits(){ + mainPlayer->deselectUnits(); + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + ufCtr->removeGameObjectFrames(); + ufCtr->toggleFrameTransformations(false, false, false); + + ConcreteGuiManager::getSingleton()->readLuaScreenScript("activeGameState.lua"); + } + + bool ActiveGameState::selectedUnitsAmongst(vector units){ + vector selUnits = mainPlayer->getSelectedUnits(); + + for(Unit *unit : units) + if(find(selUnits.begin(), selUnits.end(), unit) != selUnits.end()) + return true; + + return false; + } + + bool ActiveGameState::isGameObjSelectable(GameObject *obj, bool useDragBox){ + if(!obj->getModel()->isVisible()) return false; + + Node *hitboxNode = obj->getHitbox(); + Box *hitbox = (Box*)hitboxNode->getMesh(0); + + Vector3 hitboxSize = hitbox->getSize(); + float width = hitboxSize.x, height = hitboxSize.y, length = hitboxSize.z; + const int NUM_CORNERS = 8; + + Vector3 corners[NUM_CORNERS]{ + Vector3(-.5 * width, -.5 * height, -.5 * length), + Vector3(-.5 * width, -.5 * height, .5 * length), + Vector3(.5 * width, -.5 * height, .5 * length), + Vector3(.5 * width, -.5 * height, -.5 * length), + Vector3(-.5 * width, .5 * height, -.5 * length), + Vector3(-.5 * width, .5 * height, .5 * length), + Vector3(.5 * width, .5 * height, .5 * length), + Vector3(.5 * width, .5 * height, -.5 * length), + }; + + Vector2 cornersOnScreen[NUM_CORNERS]; + Vector3 hitboxOffset = hitboxNode->getPosition(); + + for(int i = 0; i < NUM_CORNERS; i++){ + Vector3 cornerInWorld = + obj->getPos() + + obj->getLeftVec() * (corners[i].x + hitboxOffset.x) + + obj->getUpVec() * (corners[i].y + hitboxOffset.y) + + obj->getDirVec() * (corners[i].z + hitboxOffset.z); + + Vector3 screenSpace3d = spaceToScreen3d(cornerInWorld); + + if(fabs(screenSpace3d.z) > 1) return false; + + cornersOnScreen[i] = Vector2(screenSpace3d.x, screenSpace3d.y); + } + + vector selectionPoints; + Vector3 dragboxOrigin = Vector3::VEC_ZERO, dragboxSize = Vector3::VEC_ZERO; + + if(useDragBox){ + dragboxOrigin = dragboxNode->getPosition(); + dragboxSize = ((Quad*)dragboxNode->getMesh(0))->getSize(); + Vector2 objScreenPos = spaceToScreen(obj->getPos()); + + if( + (dragboxOrigin.x < objScreenPos.x && objScreenPos.x < dragboxOrigin.x + dragboxSize.x) && + (dragboxOrigin.y < objScreenPos.y && objScreenPos.y < dragboxOrigin.y + dragboxSize.y) + ) + return true; + + selectionPoints = vector{ + Vector2(dragboxOrigin.x, dragboxOrigin.y), + Vector2(dragboxOrigin.x + dragboxSize.x, dragboxOrigin.y), + Vector2(dragboxOrigin.x + dragboxSize.x, dragboxOrigin.y + dragboxSize.y), + Vector2(dragboxOrigin.x, dragboxOrigin.y + dragboxSize.y) + }; + } + else + selectionPoints = vector{getCursorPos()}; + + int numAboveEdges = 0, numBelowEdges = 0; + + for(int i = 0; i < selectionPoints.size(); i++){ + const int NUM_EDGES = 12; + int edges[NUM_EDGES][2]{{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}}; + + for(int j = 0; j < NUM_EDGES && (numAboveEdges == 0 || numBelowEdges == 0); j++){ + Vector2 vertA = cornersOnScreen[edges[j][0]], vertB = cornersOnScreen[edges[j][1]]; + float edgeHorLen = fabs(vertA.x - vertB.x), edgeVertLen = fabs(vertA.y - vertB.y); + + if(fabs(selectionPoints[i].x - vertA.x) < edgeHorLen && fabs(selectionPoints[i].x - vertB.x) < edgeHorLen){ + if(vertA.x > selectionPoints[i].x) swap(vertA, vertB); + + float horOffset = (selectionPoints[i].x - vertA.x) / edgeHorLen; + float height = vertA.y + (vertA.y < vertB.y ? 1 : -1) * horOffset * edgeVertLen; + + (selectionPoints[i].y > height ? numAboveEdges : numBelowEdges)++; + } + } + } + + return (numAboveEdges > 0 && numBelowEdges > 0); + } + + void ActiveGameState::renderUnits() { + vector friendlyUnits = mainPlayer->getFriendlyUnits(); + Game *game = Game::getSingleton(); + + for (Player *p : game->getPlayers(true)){ + vector units = p->getUnits(); + + for (Unit *u : units) { + if(u->isVehicle() && ((Vehicle*)u)->getGarrisonable()) continue; + + bool unitVisible = (game->isDebug() || mainPlayer->isObjectVisible((GameObject*)u, friendlyUnits)); + u->getModel()->setVisible(unitVisible); + } + } + } + + void ActiveGameState::updateDragBox() { + Vector2 mousePos = getCursorPos(), dragBoxOrigin, dragBoxEnd; + + if (mousePos.x >= clickPoint.x && mousePos.y >= clickPoint.y) { + dragBoxOrigin = Vector2(clickPoint.x, clickPoint.y); + dragBoxEnd = Vector2(mousePos.x, mousePos.y); + } + else if (mousePos.x < clickPoint.x && mousePos.y > clickPoint.y) { + dragBoxOrigin = Vector2(mousePos.x, clickPoint.y); + dragBoxEnd = Vector2(clickPoint.x, mousePos.y); + } + else if (mousePos.x >= clickPoint.x && mousePos.y < clickPoint.y) { + dragBoxOrigin = Vector2(clickPoint.x, mousePos.y); + dragBoxEnd = Vector2(mousePos.x, clickPoint.y); + } + else { + dragBoxOrigin = Vector2(mousePos.x, mousePos.y); + dragBoxEnd = Vector2(clickPoint.x, clickPoint.y); + } + + Vector3 size = Vector3(dragBoxEnd.x - dragBoxOrigin.x, dragBoxEnd.y - dragBoxOrigin.y, 0); + Quad *dragbox = (Quad*)dragboxNode->getMesh(0); + dragbox->setSize(size); + dragbox->updateVerts(dragbox->getMeshBase()); + dragboxNode->setPosition(Vector3(dragBoxOrigin.x, dragBoxOrigin.y, 0)); + } + + //TODO fix ejectable unit selection with multiple transports selected + //TODO replace shiftPressed with a dedicated buy flag + void ActiveGameState::issueOrder(Order::TYPE type, vector targets, bool addOrder) { + depth = 1; + Vector3 destDir = Vector3::VEC_ZERO; + + if(selectingDestOrient){ + GameObjectFrame &objFrame = GameObjectFrameController::getSingleton()->getGameObjectFrame(0); + destDir = objFrame.getDirVec(); + targets[0].pos = objFrame.getPos(); + } + + mainPlayer->issueOrder(type, destDir, targets, addOrder); + this->targets.clear(); + + if(cursorState != CursorState::ATTACK) + forceCursorState = false; + } + + void ActiveGameState::castRayToTerrain() { + Camera *cam = Root::getSingleton()->getCamera(); + Vector3 camPos = cam->getPosition(); + Vector3 endPos = screenToSpace(getCursorPos()); + + Vector3 rayDir = (endPos - camPos).norm(); + Map *map = Map::getSingleton(); + vector results = map->raycastTerrain(camPos, rayDir, true); + + if(!results.empty()) + targets.push_back(Order::Target(nullptr, results[0].pos)); + } + + void ActiveGameState::enableUnitState(Unit::State state){ + vector selectedUnits = mainPlayer->getSelectedUnits(); + + for(Unit *unit : selectedUnits) + unit->setState(state); + } + + void ActiveGameState::addUnitGui(){ + vector currSelectedUnits = mainPlayer->getSelectedUnits(); + + if(currSelectedUnits.empty()) return; + + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + + sol::state_view SOL_LUA_VIEW = generateView(); + SOL_LUA_VIEW.script("_mainUnitId = " + to_string(currSelectedUnits[0]->getId())); + guiManager->readLuaScreenScriptDel("unitGui.lua", unitButtons); + + SOL_LUA_VIEW.script("numGui = #gui"); + int numButtons = SOL_LUA_VIEW["numGui"]; + + for(int i = 0; i < numButtons; i++) + unitButtons.push_back(guiManager->getButtons()[guiManager->getButtons().size() - (i + 1)]); + } + + void ActiveGameState::onAction(int bind, bool isPressed) { + if(tradingScreen || !ConcreteGuiManager::getSingleton()->findClickedButtons().empty()) return; + + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + vector selectedUnits = mainPlayer->getSelectedUnits(); + + switch((Bind)bind){ + case Bind::DRAG_BOX: + selectMouseClicked = isPressed; + + if(isPressed){ + clickPoint = getCursorPos(); + lastSelectMouseClicked = getTime(); + } + else{ + if(!shiftPressed) deselectUnits(); + + if(canSelectHoveredOnGameObj()) + mainPlayer->selectUnit((Unit*)gameObjHoveredOn); + + selectingDestOrient = false; + + if(isSelectionBox){ + if(!shiftPressed) deselectUnits(); + + vector units = mainPlayer->getUnits(); + + for(Unit *un : units) + if(isGameObjSelectable(un, true)) + mainPlayer->selectUnit(un); + } + + isSelectionBox = false; + Quad *quad = (Quad*)dragboxNode->getMesh(0); + quad->setSize(Vector3::VEC_ZERO); + quad->updateVerts(quad->getMeshBase()); + + ufCtr->toggleFrameTransformations(false, false, false); + ufCtr->removeGameObjectFrames(); + } + + break; + case Bind::ROTATE_OBJ_FRAME: + orderMouseClicked = isPressed; + + if(isPressed) + lastOrderMouseClicked = getTime(); + else if(!(isPressed || selectedUnits.empty())){ + if(selectingPatrolPoints){ + castRayToTerrain(); + + if(!(targets.empty() || selectingPatrolPoints)){ + Order::TYPE type = Order::TYPE::MOVE; + + if(controlPressed || (targets[0].unit && targets[0].unit->getPlayer()->getTeam() != mainPlayer->getTeam())) + type = Order::TYPE::ATTACK; + else if(ufCtr->isPlacingOnSurface() && !selectingDestOrient) + type = Order::TYPE::BUILD; + + issueOrder(type, targets, shiftPressed); + } + } + else if(!isSelectionBox){ + bool ownGameObj = (gameObjHoveredOn && gameObjHoveredOn->getPlayer()->getTeam() == mainPlayer->getTeam()); + + if(cursorState == CursorState::GARRISON){ + if(orderPossible) issueOrder(Order::TYPE::GARRISON, vector{Order::Target((Unit*)gameObjHoveredOn, gameObjHoveredOn->getPos())}, shiftPressed); + } + else if(cursorState == CursorState::SUPPLY){ + if(orderPossible) issueOrder(Order::TYPE::SUPPLY, vector{Order::Target((Unit*)gameObjHoveredOn)}, shiftPressed); + } + else if(cursorState == CursorState::LOAD){ + if(orderPossible) issueOrder(Order::TYPE::LOAD, vector{Order::Target((Unit*)gameObjHoveredOn)}, shiftPressed); + } + else if(cursorState == CursorState::UNLOAD){ + if(orderPossible) issueOrder(Order::TYPE::UNLOAD, vector{Order::Target((Unit*)gameObjHoveredOn)}, shiftPressed); + } + else if(cursorState == CursorState::ATTACK){ + if(controlPressed && !gameObjHoveredOn){ + castRayToTerrain(); + issueOrder(Order::TYPE::ATTACK, targets, shiftPressed); + } + else{ + if(orderPossible) issueOrder(Order::TYPE::ATTACK, vector{Order::Target((Unit*)gameObjHoveredOn, gameObjHoveredOn->getPos())}, shiftPressed); + } + } + else if(cursorState == CursorState::HACK){ + if(orderPossible) issueOrder(Order::TYPE::HACK, vector{Order::Target((Unit*)gameObjHoveredOn)}, shiftPressed); + } + else if(ufCtr->isPlacingOnSurface()){ + Order::TYPE type; + + switch(selectedUnits[0]->getUnitClass()){ + case UnitClass::ROBO_ENGINEER: + case UnitClass::CYBORG_ENGINEER: + case UnitClass::FREEZER: + type = Order::TYPE::BUILD; + break; + default: + return; + } + + targets.clear(); + + for(int i = 0; i < ufCtr->getNumGameObjectFrames(); i++){ + GameObjectFrame gmObjFr = ufCtr->getGameObjectFrame(i); + + if(gmObjFr.status == GameObjectFrame::BLOCKED_BY_DIFF_TERR) continue; + + Unit *buildStruct = GameObjectFactory::createUnit(mainPlayer, gmObjFr.getId(), gmObjFr.getPos(), gmObjFr.getRot()); + issueOrder(type, vector{Order::Target(buildStruct, gmObjFr.getPos())}, shiftPressed); + } + + buildableStructSelected = false; + } + else{ + if(targets.empty()) castRayToTerrain(); + + issueOrder(Order::TYPE::MOVE, targets, shiftPressed); + } + } + } + + break; + case Bind::MOVE_CAMERA: + break; + case Bind::ENABLE_CHASE_STATE: + if(isPressed) enableUnitState(Unit::State::CHASE); + break; + case Bind::ENABLE_STAND_GROUND_STATE: + if(isPressed) enableUnitState(Unit::State::STAND_GROUND); + break; + case Bind::ENABLE_HOLD_FIRE_STATE: + if(isPressed) enableUnitState(Unit::State::HOLD_FIRE); + break; + case Bind::EJECT_GARRISON: + if(isPressed) issueOrder(Order::TYPE::EJECT, vector{}, shiftPressed); + break; + case Bind::LAUNCH: + if(isPressed){ + castRayToTerrain(); + issueOrder(Order::TYPE::LAUNCH, targets, shiftPressed); + } + + break; + /* + case Bind::HACK: + if(isPressed){ + if(gameObjHoveredOn && gameObjHoveredOn->getType() == GameObject::Type::UNIT && gameObjHoveredOn->getPlayer()->getTeam() != mainPlayer->getTeam()) + issueOrder(Order::TYPE::HACK, vector{Order::Target((Unit*)gameObjHoveredOn)}, shiftPressed); + + break; + } + */ + case Bind::SHIFT_SUB_DEPTH: + ufCtr->toggleFrameTransformations(false, false, isPressed); + break; + case Bind::ZOOM_IN: + if (zooms > -NUM_MAX_ZOOMS) { + Camera *cam = Root::getSingleton()->getCamera(); + cam->setPosition(cam->getPosition() + cam->getDirection().norm() * .5f); + zooms--; + } + break; + case Bind::ZOOM_OUT: + if (zooms < NUM_MAX_ZOOMS) { + Camera *cam = Root::getSingleton()->getCamera(); + cam->setPosition(cam->getPosition() - (cam->getDirection().norm() * .5f)); + zooms++; + } + break; + case Bind::LOOK_AROUND: + if(!ufCtr->isPlacingOnSurface()) + CameraController::getSingleton()->setLookingAround(isPressed); + break; + case Bind::HALT: + if(isPressed) mainPlayer->haltUnits(); + break; + //TODO reimplement submarine diving mechanic + case Bind::LEFT_CONTROL: + if(isPressed) cursorState = CursorState::ATTACK; + + forceCursorState = isPressed; + controlPressed = isPressed; + orderPossible = isPressed; + break; + case Bind::LEFT_SHIFT: + { + shiftPressed = isPressed; + + ufCtr->setPaintSelecting(shiftPressed); + + if(isPressed){ + Vector3 startPos = Root::getSingleton()->getCamera()->getPosition(); + vector results = Map::getSingleton()->raycastTerrain(startPos, (screenToSpace(getCursorPos()) - startPos).norm(), true); + + if(!results.empty()) + ufCtr->setPaintSelectRowStart(results[0].pos); + } + } + break; + case Bind::SELECT_PATROL_POINTS: + /* + if(isPressed && mainPlayer->getNumSelectedUnits() > 0){ + targets.push_back(Order::Target(nullptr, mainPlayer->getSelectedUnit(0)->getPos())); + selectingPatrolPoints = isPressed; + } + */ + + break; + case Bind::GROUP_0: + case Bind::GROUP_1: + case Bind::GROUP_2: + case Bind::GROUP_3: + case Bind::GROUP_4: + case Bind::GROUP_5: + case Bind::GROUP_6: + case Bind::GROUP_7: + case Bind::GROUP_8: + case Bind::GROUP_9: + if(isPressed){ + int group = bind - Bind::GROUP_0; + + if(controlPressed) + unitGroups[group] = mainPlayer->getSelectedUnits(); + else{ + if(!shiftPressed) + mainPlayer->selectUnits(unitGroups[group]); + else + for(Unit *u : unitGroups[group]) + mainPlayer->selectUnit(u); + } + } + + break; + case Bind::DESELECT_STRUCTURE: + forceCursorState = false; + buildableStructSelected = false; + ufCtr->toggleFrameTransformations(false, false, false); + ufCtr->removeGameObjectFrames(); + break; + } + } + + void ActiveGameState::onAnalog(int bind, float strength) { + CameraController *camCtr = CameraController::getSingleton(); + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + + switch((Bind)bind){ + case Bind::LOOK_UP: + case Bind::LOOK_DOWN: + if(camCtr->isLookingAround()) { + Vector3 dirProj = Root::getSingleton()->getCamera()->getDirection(); + dirProj = Vector3(dirProj.x, 0, dirProj.z).norm(); + CameraController::getSingleton()->orientCamera(Vector3(0, 1, 0).cross(dirProj), strength); + } + else if(ufCtr->isPlacingVertically()){ + depth -= .2 * strength; + + if(depth < 0) depth = 0; + else if(depth > 1) depth = 1; + } + + break; + case Bind::LOOK_LEFT: + case Bind::LOOK_RIGHT: + if(camCtr->isLookingAround()) + CameraController::getSingleton()->orientCamera(Vector3(0, 1, 0), strength); + else if(ufCtr->isRotating()) + ufCtr->rotateGameObjectFrames(100 * strength); + + break; + } + } + + void ActiveGameState::onRawMouseWheelScroll(bool up){ + CameraController::getSingleton()->zoomCamera(up); + } +} diff --git a/source/core/appStates/activeGameState.h b/source/core/appStates/activeGameState.h new file mode 100644 index 0000000..dd76498 --- /dev/null +++ b/source/core/appStates/activeGameState.h @@ -0,0 +1,112 @@ +#ifndef ACTIVE_GAME_STATE_H +#define ACTIVE_GAME_STATE_H + +#include "guiAppState.h" +#include "player.h" +#include "map.h" +#include "unit.h" + +#include + +namespace vb01{ + class Node; + class Texture; +} + +namespace vb01Gui{ + class Button; +} + +namespace battleship{ + class GameObject; + class Unit; + class UnitButton; + + class ActiveGameState : public gameBase::AbstractAppState { + public: + enum CursorState{ + NORMAL, + ATTACK, + GARRISON, + SUPPLY, + LOAD, + UNLOAD, + HACK + }; + + ActiveGameState(GuiAppState*, int); + ~ActiveGameState(); + void onAttached(); + void onDettached(); + void update(); + void onAction(int, bool); + void onAnalog(int, float); + void onRawMouseWheelScroll(bool); + inline CursorState getCursorState(){return cursorState;} + inline void setCursorState(CursorState cs){this->cursorState = cs;} + inline void addButton(vb01Gui::Button *b){guiButtons.push_back(b);} + inline std::vector getGuiButtons(){return guiButtons;} + inline std::vector getGuiRects(){return guiRects;} + inline std::vector getGuiTexts(){return guiTexts;} + inline Player* getPlayer(){return mainPlayer;} + inline std::vector& getUnitGroup(int i){return unitGroups[i];} + inline void setBuildableStructSelected(bool bss){this->buildableStructSelected = bss;} + inline bool isBuildableStructSelected(){return buildableStructSelected;} + inline float getDepth(){return depth;} + inline void setForceCursorState(bool force){this->forceCursorState = force;} + inline bool isForceCursorState(){return forceCursorState;} + inline void setTradingScreen(bool ts){this->tradingScreen = ts;} + inline bool isTradingScreen(){return tradingScreen;} + inline void setOfferScreen(bool os){this->offerScreen = os;} + inline bool isOfferScreen(){return offerScreen;} + private: + bool selectedUnitsAmongst(std::vector); + void updateGameObjHoveredOn(); + void initCursor(); + void updateCursor(); + void initDragbox(); + void removeDragbox(); + bool isGameObjSelectable(GameObject*, bool); + void deselectUnits(); + void renderUnits(); + void updateDragBox(); + void updateStructureFrames(); + void castRayToTerrain(); + void issueOrder(Order::TYPE, std::vector, bool); + void enableUnitState(Unit::State); + void addUnitGui(); + inline bool canSelectHoveredOnGameObj(){return gameObjHoveredOn && gameObjHoveredOn->isSelectable() && gameObjHoveredOn->getPlayer() == mainPlayer;} + + CursorState cursorState = CursorState::NORMAL; + Player *mainPlayer; + GuiAppState *guiState; + GameObject *gameObjHoveredOn = nullptr; + vb01::Vector2 clickPoint; + std::vector unitGroups[9], prevSelectedUnits; + std::vector targets; + vb01::Node *dragboxNode = nullptr; + std::vector guiRects; + std::vector guiTexts; + std::vector guiButtons, unitButtons; + bool isSelectionBox = false; + bool shiftPressed = false; + bool controlPressed = false; + bool selectMouseClicked = false; + bool orderMouseClicked = false; + bool buildableStructSelected = false; + bool selectingPatrolPoints = false; + bool selectingDestOrient = false; + bool forceCursorState = false; + bool orderPossible = false; + bool tradingScreen = false; + bool offerScreen = false; + int playerId, zooms = 0; + const int NUM_MAX_ZOOMS = 10; + float depth = 1; + vb01::s64 lastSelectMouseClicked = 0, lastOrderMouseClicked = 0; + vb01::Node *cursorNode = nullptr; + vb01::Texture *pointerTex = nullptr, *attackTex = nullptr, *garrisonTex = nullptr, *noGarrisonTex = nullptr, *supplyTex = nullptr, *noSupplyTex = nullptr, *hackTex = nullptr, *noHackTex = nullptr; + }; +} + +#endif diff --git a/source/core/appStates/guiAppState.cpp b/source/core/appStates/guiAppState.cpp new file mode 100644 index 0000000..378cb0e --- /dev/null +++ b/source/core/appStates/guiAppState.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include +#include + +#include "util.h" +#include "defConfigs.h" +#include "gameManager.h" +#include "concreteGuiManager.h" +#include "guiAppState.h" + +using namespace gameBase; +using namespace vb01; +using namespace vb01Gui; +using namespace std; + +namespace battleship{ + using namespace configData; + + GuiAppState::GuiAppState() : AbstractAppState( + AppStateType::GUI_STATE, + configData::calcSumBinds(AppStateType::GUI_STATE, true), + configData::calcSumBinds(AppStateType::GUI_STATE, false), + GameManager::getSingleton()->getPath() + scripts[(int)ScriptFiles::OPTIONS]){ + } + + GuiAppState::~GuiAppState() {} + + void GuiAppState::update() { + ConcreteGuiManager::getSingleton()->update(); + } + + void GuiAppState::onAttached() { + AbstractAppState::onAttached(); + } + + void GuiAppState::onDettached() { + AbstractAppState::onDettached(); + } + + void GuiAppState::onAction(int bind, bool isPressed) { + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + + switch((Bind)bind){ + case Bind::LEFT_CLICK: + leftMousePressed = isPressed; + + if(isPressed) + guiManager->updateGui(); + + break; + case Bind::SCROLLING_UP:{ + Listbox *l = getOpenListbox(); + if(l) l->scrollUp(); + break; + } + case Bind::SCROLLING_DOWN:{ + Listbox *l = getOpenListbox(); + if(l) l->scrollDown(); + break; + } + case Bind::DELETE_CHAR:{ + Textbox *t = getOpenTextbox(); + if(t)t->setDeleteCharacters(isPressed); + break; + } + } + } + + void GuiAppState::onRawKeyPress(int ch){ + for(Button *b : ConcreteGuiManager::getSingleton()->getButtons()) + if(b->getTrigger() == ch){ + b->onClick(); + break; + } + } + + void GuiAppState::onRawCharPress(u32 codepoint){ + Textbox *currentTextbox = getOpenTextbox(); + + if(currentTextbox) + currentTextbox->type(codepoint); + } + + void GuiAppState::onRawMousePress(int trigger){ + } + + Textbox* GuiAppState::getOpenTextbox() { + vector textboxes = ConcreteGuiManager::getSingleton()->getTextboxes(); + + for (int i = 0; i < textboxes.size(); i++) + if (textboxes[i]->isEnabled()) + return textboxes[i]; + + return nullptr; + } + + Listbox* GuiAppState::getOpenListbox() { + vector listboxes = ConcreteGuiManager::getSingleton()->getListboxes(); + + for (int i = 0; i < listboxes.size(); i++) + if (listboxes[i]->isOpen()) + return listboxes[i]; + + return nullptr; + } + + void GuiAppState::onAnalog(int bind, float strength) {} + + void GuiAppState::onRawMouseWheelScroll(bool up){ + Listbox *openListbox = getOpenListbox(); + + if(!openListbox) return; + + if(up) openListbox->scrollUp(); + else openListbox->scrollDown(); + } +} diff --git a/source/core/appStates/guiAppState.h b/source/core/appStates/guiAppState.h new file mode 100644 index 0000000..b2fd3d7 --- /dev/null +++ b/source/core/appStates/guiAppState.h @@ -0,0 +1,42 @@ +#pragma once +#ifndef GUI_APP_STATE_H +#define GUI_APP_STATE_H + +#include +#include +#include +#include +#include + +#include + +#include + +#include "binds.h" +#include "tooltip.h" + +namespace battleship{ + class GuiAppState : public gameBase::AbstractAppState { + public: + GuiAppState(); + ~GuiAppState(); + void onAttached(); + void onDettached(); + void update(); + inline bool isLeftMousePressed(){return leftMousePressed;} + private: + virtual void onAction(int, bool); + virtual void onAnalog(int, float); + virtual void onRawKeyPress(int); + virtual void onRawCharPress(vb01::u32); + virtual void onRawMousePress(int); + virtual void onRawMouseWheelScroll(bool); + vb01Gui::Textbox* getOpenTextbox(); + vb01Gui::Listbox* getOpenListbox(); + + bool leftMousePressed = false, backspacePressed = false; + protected: + }; +} + +#endif diff --git a/source/core/appStates/inGameAppState.cpp b/source/core/appStates/inGameAppState.cpp new file mode 100644 index 0000000..04d7817 --- /dev/null +++ b/source/core/appStates/inGameAppState.cpp @@ -0,0 +1,103 @@ +#include + +#include + +#include +#include +#include +#include + +#include + +#include "defConfigs.h" +#include "inGameAppState.h" +#include "game.h" +#include "console.h" +#include "gameObjectFrameController.h" +#include "concreteGuiManager.h" +#include "vessel.h" + +#include + +using namespace vb01; +using namespace vb01Gui; +using namespace std; + +namespace battleship{ + using namespace configData; + using namespace gameBase; + + InGameAppState::ResumeButton::ResumeButton(Vector3 pos, Vector2 size) : Button(pos, size, "Resume", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true) {} + + void InGameAppState::ResumeButton::onClick() { + Game::getSingleton()->togglePause(); + } + + InGameAppState::ConsoleButton::ConsoleButton(Vector3 pos, Vector2 size) : Button(pos, size, "Console", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true) { } + + void InGameAppState::ConsoleButton::onClick() { + ConcreteGuiManager::getSingleton()->readLuaScreenScript("console.lua"); + } + + InGameAppState::ConsoleButton::ConsoleButton::ConsoleCommandEntryButton::ConsoleCommandEntryButton(Textbox *t, Listbox *l, Vector3 pos, Vector2 size, string name) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", 257, true) { + textbox = t; + listbox = l; + } + + void InGameAppState::ConsoleButton::ConsoleButton::ConsoleCommandEntryButton::onClick() { + Console::execute(wstringToString(textbox->getText())); + } + + InGameAppState::InGameAppState(std::string mn) : AbstractAppState( + AppStateType::IN_GAME_STATE, + configData::calcSumBinds(AppStateType::IN_GAME_STATE, true), + configData::calcSumBinds(AppStateType::IN_GAME_STATE, false), + GameManager::getSingleton()->getPath() + scripts[(int)ScriptFiles::OPTIONS] + ), + mapName(mn), + playerId(0){} + + void InGameAppState::onAttached() { + AbstractAppState::onAttached(); + + Map *map = Map::getSingleton(); + map->load(mapName); + map->loadPlayersGameObjects(); + + Camera *cam = Root::getSingleton()->getCamera(); + cam->setPosition(Map::getSingleton()->getSpawnPoint(playerId) + Vector3(1, 1, 1) * configData::CAMERA_DISTANCE); + cam->lookAt(Vector3(0, -1, -1).norm(), Vector3(0, 1, -1).norm()); + + GameManager *gm = GameManager::getSingleton(); + StateManager *stateManager = gm->getStateManager(); + GuiAppState *guiState = ((GuiAppState*)stateManager->getAppStateByType((int)AppStateType::GUI_STATE)); + activeState = new ActiveGameState(guiState, playerId); + stateManager->attachAppState(activeState); + + Game::getSingleton()->initLuaPlayers(); + //Map::Minimap::getSingleton()->updateImage(); + } + + void InGameAppState::onDettached() { + StateManager *sm = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)sm->getAppStateByType(int(AppStateType::ACTIVE_STATE)); + sm->dettachAppState(activeState); + delete activeState; + } + + void InGameAppState::update() { + Game::getSingleton()->update(); + Map::getSingleton()->update(); + } + + void InGameAppState::onAction(int bind, bool isPressed) { + switch((Bind)bind){ + case Bind::TOGGLE_MAIN_MENU: + if(isPressed && !(GameObjectFrameController::getSingleton()->isPlacingOnSurface() || activeState->isForceCursorState())) + Game::getSingleton()->togglePause(); + break; + } + } + + void InGameAppState::onAnalog(int bind, float str) {} +} diff --git a/source/core/appStates/inGameAppState.h b/source/core/appStates/inGameAppState.h new file mode 100644 index 0000000..2a33a90 --- /dev/null +++ b/source/core/appStates/inGameAppState.h @@ -0,0 +1,60 @@ +#pragma once +#ifndef IN_GAME_APP_STATE_H +#define IN_GAME_APP_STATE_H + +#include "gameManager.h" +#include "player.h" +#include "map.h" +#include "activeGameState.h" +#include "guiAppState.h" +#include "exitButton.h" +#include "optionsButton.h" + +#include + +//TODO refactor dependence on ActiveGameState +namespace battleship{ + class InGameAppState : public gameBase::AbstractAppState { + public: + class ResumeButton : public vb01Gui::Button { + public: + ResumeButton(vb01::Vector3, vb01::Vector2); + void onClick(); + private: + }; + + class ConsoleButton : public vb01Gui::Button { + public: + class ConsoleCommandEntryButton : public Button { + public: + ConsoleCommandEntryButton(vb01Gui::Textbox*, vb01Gui::Listbox*, vb01::Vector3, vb01::Vector2, std::string); + void onClick(); + private: + vb01Gui::Textbox *textbox; + vb01Gui::Listbox *listbox; + }; + + ConsoleButton(vb01::Vector3, vb01::Vector2); + void onClick(); + private: + }; + + InGameAppState(std::string); + ~InGameAppState(){} + void onAttached(); + void onDettached(); + void update(); + void onAction(int, bool); + void onAnalog(int, float); + std::vector getSelectedUnits(Player*); + inline ActiveGameState* getActiveState(){return activeState;} + private: + bool isMainMenuActive = false; + std::vector modelPaths; + int playerId; + std::string mapName = ""; + ActiveGameState* activeState; + }; +} + +#endif diff --git a/source/core/appStates/loadingAppState.cpp b/source/core/appStates/loadingAppState.cpp new file mode 100644 index 0000000..dd655dc --- /dev/null +++ b/source/core/appStates/loadingAppState.cpp @@ -0,0 +1,51 @@ +#include "loadingAppState.h" +#include "concreteGuiManager.h" +#include "gameManager.h" +#include "defConfigs.h" + +#include +#include +#include +#include + +#include + +namespace battleship{ + using namespace configData; + using namespace gameBase; + using namespace vb01; + using namespace std; + + LoadingAppState::LoadingAppState(AbstractAppState *newSt, string newScr) : AbstractAppState( + AppStateType::LOADING_STATE, + configData::calcSumBinds(AppStateType::LOADING_STATE, true), + configData::calcSumBinds(AppStateType::LOADING_STATE, false), + GameManager::getSingleton()->getPath() + scripts[(int)ScriptFiles::OPTIONS] + ), + newState(newSt), + newScreen(newScr) + {} + + void LoadingAppState::onDettached(){ + ConcreteGuiManager::getSingleton()->readLuaScreenScript(newScreen); + GameManager::getSingleton()->getStateManager()->attachAppState(newState); + + AbstractAppState::onDettached(); + } + + void LoadingAppState::update(){ + Node *loadingBar = ConcreteGuiManager::getSingleton()->getGuiRectangle("_loadingBar"); + Quad *quad = (Quad*)loadingBar->getMesh(0); + quad->setSize(Vector3((1 - (float)loadableAssets.size() / initNumAssets) * 200.f, 20, 0)); + quad->updateVerts(quad->getMeshBase()); + + if(getTime() - lastUpdateTime > 5){ + AssetManager::getSingleton()->load(loadableAssets[0]); + loadableAssets.erase(loadableAssets.begin()); + + if(loadableAssets.empty()) GameManager::getSingleton()->getStateManager()->dettachAppState(this); + + lastUpdateTime = getTime(); + } + } +} diff --git a/source/core/appStates/loadingAppState.h b/source/core/appStates/loadingAppState.h new file mode 100644 index 0000000..8f0c10a --- /dev/null +++ b/source/core/appStates/loadingAppState.h @@ -0,0 +1,28 @@ +#ifndef LOADING_APP_STATE_H +#define LOADING_APP_STATE_H + +#include +#include + +namespace battleship{ + class LoadingAppState : public gameBase::AbstractAppState{ + public: + LoadingAppState(gameBase::AbstractAppState*, std::string); + ~LoadingAppState(){} + void onAttached(){} + void onDettached(); + void update(); + inline void setLoadableAssets(std::vector ls){ + this->loadableAssets = ls; + initNumAssets = ls.size(); + } + private: + int initNumAssets; + std::vector loadableAssets; + gameBase::AbstractAppState *newState = nullptr; + std::string newScreen = ""; + vb01::s64 lastUpdateTime = 0; + }; +} + +#endif diff --git a/source/core/appStates/mapEditorAppState.cpp b/source/core/appStates/mapEditorAppState.cpp new file mode 100644 index 0000000..44a58b8 --- /dev/null +++ b/source/core/appStates/mapEditorAppState.cpp @@ -0,0 +1,800 @@ +#include "mapEditorAppState.h" +#include "gameManager.h" +#include "defConfigs.h" +#include "cameraController.h" +#include "resourceDeposit.h" +#include "gameObjectFactory.h" +#include "gameObjectFrameController.h" +#include "player.h" +#include "game.h" +#include "map.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + +#include + +#include + +#include +#include +#include + +#include + +namespace battleship{ + using namespace tinyxml2; + using namespace configData; + using namespace gameBase; + using namespace vb01; + using namespace vb01Gui; + using namespace std; + using namespace std::filesystem; + + MapEditorAppState::MapEditor::MapEditor(string name, Vector2 size, bool newMap){ + this->newMap = newMap; + + string basePath = GameManager::getSingleton()->getPath(); + prepareTextures(basePath + "Textures/Skyboxes/", true, skyTextures); + prepareTextures(basePath + "Textures/Landmass/", false, landmassTextures); + prepareTextures(basePath + "Textures/Water/", false, waterTextures); + + AssetManager *assetManager = AssetManager::getSingleton(); + assetManager->load(basePath + "Models/Units/", true); + assetManager->load(basePath + "Models/Resources/", true); + assetManager->load(basePath + DEFAULT_TEXTURE); + + map = Map::getSingleton(); + Game *game = Game::getSingleton(); + + if(newMap){ + game->addPlayer(new Player(0, 0, 0, Vector3(1, 1, 1))); + map->create(name, Vector3(size.x, 0, size.y)); + generatePlane(size); + } + else{ + map->load(name); + + int numPlayers = map->getNumSpawnPoints(); + int numVerts = 3 * map->getNodeParent()->getChild(0)->getMesh(0)->getMeshBase().numTris; + oldLandmassVertHeights = new float[numVerts]; + + for(int i = 0; i < numPlayers; i++) + game->addPlayer(new Player(0, 0, 0, Vector3(1, 1, 1))); + + map->loadPlayersGameObjects(); + } + } + + void MapEditorAppState::MapEditor::updateCircleRadius(bool increase){ + circleRadius += (increase ? INCREASE_RATE : -INCREASE_RATE); + + if(circleRadius > MAX_RADIUS) + circleRadius = MAX_RADIUS; + else if(circleRadius < MIN_RADIUS) + circleRadius = MIN_RADIUS; + } + + void MapEditorAppState::MapEditor::toggleSelection(Node *terrNode, bool select){ + terrNode->getMesh(0)->setWireframe(select); + selectedTerrainNode = (select ? terrNode : nullptr); + } + + void MapEditorAppState::MapEditor::castSelectionRay(){ + /* + Camera *cam = Root::getSingleton()->getCamera(); + Vector3 startPos = cam->getPosition(); + Vector3 endPos = screenToSpace(getCursorPos()); + + Map *map = Map::getSingleton(); + vector results = RayCaster::cast(startPos, (endPos - startPos).norm(), map->getNodeParent()); + + if(selectedTerrainNode) + toggleSelection(selectedTerrainNode, false); + + if(!results.empty()){ + for(int i = 0; i < map->getNodeParent()->getNumChildren(); i++) + if(map->getNodeParent()->getChild(i) == results[i].mesh->getNode()){ + toggleSelection(map->getNodeParent()->getChild(i), true); + break; + } + } + */ + } + + void MapEditorAppState::MapEditor::createWaterbody(){ + Material *mat = new Material(Root::getSingleton()->getLibPath() + "texture"); + mat->addBoolUniform("lightingEnabled", false); + mat->addBoolUniform("texturingEnabled", true); + mat->addTexUniform("textures[0]", waterTextures[0], false); + + Vector3 size = Vector3(30, 30, 1); + Quad *quad = new Quad(size, true); + quad->setMaterial(mat); + + Vector3 pos = 12 * Vector3::VEC_J; + Node *node = new Node(pos); + node->attachMesh(quad); + + Map *map = Map::getSingleton(); + Node *terrNodeParent = map->getNodeParent(); + terrNodeParent->attachChild(node); + toggleSelection(node, true); + } + + void MapEditorAppState::MapEditor::moveTerrainObject(float strength){ + if(fabs(strength) < .00001) return; + + Vector3 axis; + + switch(transformAxis){ + case TransformAxis::X_AXIS: + axis = Vector3::VEC_I; + break; + case TransformAxis::Y_AXIS: + axis = -Vector3::VEC_J; + break; + case TransformAxis::Z_AXIS: + axis = Vector3::VEC_K; + break; + } + + Vector3 pos = selectedTerrainNode->getPosition() + 10 * strength * axis; + selectedTerrainNode->setPosition(pos); + } + + void MapEditorAppState::MapEditor::scaleTerrainObject(float strength){ + if(fabs(strength) < .00001) return; + + Vector2 axis; + + switch(transformAxis){ + case TransformAxis::X_AXIS: + axis = Vector2::VEC_I; + break; + case TransformAxis::Z_AXIS: + axis = Vector2::VEC_J; + break; + } + + Quad *quad = (Quad*)selectedTerrainNode->getMesh(0); + Vector3 size = quad->getSize() + 10 * strength * Vector3(axis.x, axis.y, 0); + quad->setSize(Vector3(size.x, size.y, 1)); + quad->updateVerts(quad->getMeshBase()); + } + + void MapEditorAppState::MapEditor::pushLandmassVerts(float strength){ + Mesh *mesh = map->getNodeParent()->getChild(0)->getMesh(0); + MeshData meshData = mesh->getMeshBase(); + MeshData::Vertex *verts = meshData.vertices; + int numVerts = meshData.numTris * 3; + pushPos.y += 10 * strength; + + for(int i = 0; i < numVerts; i++){ + Vector3 distVec = *verts[i].pos - pushPos; + distVec.y = 0; + float dist = distVec.getLength(); + + if(dist < circleRadius) + verts[i].pos->y = oldLandmassVertHeights[i] + pushPos.y - (pushPos.y / circleRadius) * dist; + } + + mesh->updateVerts(meshData); + + int minId = 0, maxId = 0; + + for(int i = 0; i < numVerts; i++){ + if(verts[i].pos->y < verts[minId].pos->y) + minId = i; + + if(verts[i].pos->y > verts[maxId].pos->y) + maxId = i; + } + + Vector3 mapSize = map->getMapSize(); + map->setBaseHeight(verts[minId].pos->y); + map->setMapSize(Vector3(mapSize.x, verts[maxId].pos->y - verts[minId].pos->y, mapSize.z)); + } + + void MapEditorAppState::MapEditor::generatePlane(Vector2 size){ + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "texture"); + mat->addBoolUniform("texturingEnabled", false); + mat->addBoolUniform("lightingEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4(1, 0, 0, 1)); + + Quad *mesh = new Quad(Vector3(size.x, size.y, 0), true, NUM_SUBDIVS, NUM_SUBDIVS); + mesh->setMaterial(mat); + + Node *node = new Node(); + node->attachMesh(mesh); + map->getNodeParent()->attachChild(node); + + oldLandmassVertHeights = new float[3 * mesh->getMeshBase().numTris]; + } + + void MapEditorAppState::MapEditor::prepareTextures(string basePath, bool skybox, vector &textures){ + AssetManager *assetManager = AssetManager::getSingleton(); + assetManager->load(basePath, skybox); + vector texturePaths; + + for(int i = 0; i < assetManager->getNumAssets(); i++){ + string assetPath = assetManager->getAsset(i)->path; + + if(assetPath.length() >= basePath.length() && assetPath.substr(0, basePath.length()) == basePath) + texturePaths.push_back(assetPath); + } + + if(skybox){ + const int numTex = 6; + + for(int i = 0; i < texturePaths.size() / numTex; i++){ + string paths[]{ + texturePaths[i * numTex], + texturePaths[i * numTex + 1], + texturePaths[i * numTex + 2], + texturePaths[i * numTex + 3], + texturePaths[i * numTex + 4], + texturePaths[i * numTex + 5], + }; + + textures.push_back(new Texture(paths, numTex, true)); + } + } + else{ + for(int i = 0; i < texturePaths.size(); i++){ + string paths[]{texturePaths[i]}; + textures.push_back(new Texture(paths, 1, false)); + } + } + } + + void MapEditorAppState::MapEditor::generateLandmassXml(){ + XMLDocument *doc = new XMLDocument(); + + char *nodeTagName = "node"; + XMLElement *rootEl = doc->NewElement(nodeTagName); + rootEl->SetAttribute("name", "terrain"); + XMLNode *rootTag = doc->InsertEndChild(rootEl); + + XMLElement *nodeEl = doc->NewElement(nodeTagName); + nodeEl->SetAttribute("name", "plane"); + Node *landmassNode = map->getNodeParent()->getChild(0); + nodeEl->SetAttribute("px", landmassNode->getPosition().x); + nodeEl->SetAttribute("py", landmassNode->getPosition().y); + nodeEl->SetAttribute("pz", landmassNode->getPosition().z); + nodeEl->SetAttribute("rw", landmassNode->getOrientation().w); + nodeEl->SetAttribute("rx", landmassNode->getOrientation().x); + nodeEl->SetAttribute("ry", landmassNode->getOrientation().y); + nodeEl->SetAttribute("rz", landmassNode->getOrientation().z); + nodeEl->SetAttribute("sx", landmassNode->getScale().x); + nodeEl->SetAttribute("sy", landmassNode->getScale().y); + nodeEl->SetAttribute("sz", landmassNode->getScale().z); + XMLNode *nodeTag = rootTag->InsertEndChild(nodeEl); + + XMLElement *meshEl = doc->NewElement("mesh"); + MeshData meshData = map->getNodeParent()->getChild(0)->getMesh(0)->getMeshBase(); + meshEl->SetAttribute("name", "mesh"); + meshEl->SetAttribute("num_vertex_pos", 3 * meshData.numTris); + meshEl->SetAttribute("num_faces", meshData.numTris); + meshEl->SetAttribute("num_vertex_groups", 0); + meshEl->SetAttribute("num_shape_keys", 0); + XMLNode *meshTag = nodeTag->InsertEndChild(meshEl); + + //TODO optimize vertex position extraction + /* + const int numSideQuads = NUM_SUBDIVS + 1, numSideVerts = numSideQuads + 1; + Vector2 subQuadSize = Vector2(size.x / numSideQuads, size.y / numSideQuads); + */ + int numVerts = 3 * meshData.numTris; + + for(int i = 0; i < numVerts; i++){ + XMLElement *vertEl = doc->NewElement("vertdata"); + vertEl->SetAttribute("px", meshData.vertices[i].pos->x); + vertEl->SetAttribute("py", meshData.vertices[i].pos->y); + vertEl->SetAttribute("pz", meshData.vertices[i].pos->z); + /* + vertEl->SetAttribute("nx", meshData.vertices[i].norm.x); + vertEl->SetAttribute("ny", meshData.vertices[i].norm.y); + vertEl->SetAttribute("nz", meshData.vertices[i].norm.z); + */ + XMLNode *vertNode = meshTag->InsertEndChild(vertEl); + } + + for(int i = 0; i < numVerts; i++){ + XMLElement *vertEl = doc->NewElement("vert"); + vertEl->SetAttribute("id", i); + vertEl->SetAttribute("uvx", meshData.vertices[i].uv.x); + vertEl->SetAttribute("uvy", meshData.vertices[i].uv.y); + vertEl->SetAttribute("tx", meshData.vertices[i].tan.x); + vertEl->SetAttribute("ty", meshData.vertices[i].tan.y); + vertEl->SetAttribute("tz", meshData.vertices[i].tan.z); + vertEl->SetAttribute("bx", meshData.vertices[i].biTan.x); + vertEl->SetAttribute("by", meshData.vertices[i].biTan.y); + vertEl->SetAttribute("bz", meshData.vertices[i].biTan.z); + XMLNode *vertTag = meshTag->InsertEndChild(vertEl); + } + + string name = GameManager::getSingleton()->getPath() + "Models/Maps/" + map->getMapName() + "/" + map->getMapName() + ".xml"; + doc->SaveFile(name.c_str()); + } + + //TODO add diagnally adjacent edges to underwater cells + vector MapEditorAppState::MapEditor::generateMapCells(){ + Vector3 mapSize = map->getMapSize(); + Vector3 cellSize = map->getCellSize(); + Vector3 startPos = -.5 * (Vector3(mapSize.x, 0, mapSize.z) - Vector3(cellSize.x, 0, cellSize.z)); + + int numHorCells = int(mapSize.x / cellSize.x); + int numVertCells = int(mapSize.z / cellSize.z); + vector cells; + vector> waterBodyBedPoints; + Node *terrainNode = map->getNodeParent(); + + + for(int i = 0; i < numVertCells; i++) + for(int j = 0; j < numHorCells; j++){ + Vector3 rayPos = startPos + Vector3(cellSize.x * j, 100, cellSize.z * i); + vector res = RayCaster::cast(rayPos, -Vector3::VEC_J, terrainNode->getChild(0), 0, configData::DIST_FROM_RAY); + + Map::Cell::Type type = Map::Cell::Type::LAND; + Vector3 pos = res[0].pos; + + for(int k = 1; k < terrainNode->getNumChildren(); k++){ + Vector3 waterPos = terrainNode->getChild(k)->getPosition(); + Vector3 waterSize = ((Quad*)terrainNode->getChild(k)->getMesh(0))->getSize(); + + if(pos.y < waterPos.y && fabs(res[0].pos.x - waterPos.x) < .5 * waterSize.x && fabs(res[0].pos.z - waterPos.z) < .5 * waterSize.y){ + type = Map::Cell::Type::WATER; + pos.y = waterPos.y; + waterBodyBedPoints.push_back(pair(numVertCells * j + i, res[0].pos.y)); + break; + } + } + + vector edges = Map::generateAdjacentNodeEdges(numVertCells, i, numHorCells, j, 10); + Map::Cell cell = Map::Cell(pos, type, edges); + cells.push_back(cell); + } + + vector surfaceWaterCells; + int currUnderWaterCellId = cells.size(); + int weight = 20; + + for(pair p : waterBodyBedPoints){ + int numUnderWaterCells = (int)((cells[p.first].pos.y - p.second) / cellSize.y); + + if(numUnderWaterCells > 0){ + cells[p.first].edges.push_back(Map::Edge(weight, p.first, currUnderWaterCellId)); + + for(int i = 0; i < numUnderWaterCells; i++, currUnderWaterCellId++) + cells[p.first].underWaterCellIds.push_back(currUnderWaterCellId); + + surfaceWaterCells.push_back(cells[p.first]); + } + } + + for(int i = 0; i < surfaceWaterCells.size(); i++){ + for(int j = 0; j < surfaceWaterCells[i].underWaterCellIds.size(); j++){ + int aboveCellId = (j == 0 ? surfaceWaterCells[i].edges[0].srcCellId : surfaceWaterCells[i].underWaterCellIds[j - 1]); + vector edges = vector{Map::Edge(weight, surfaceWaterCells[i].underWaterCellIds[j], aboveCellId)}; + + if(surfaceWaterCells[i].underWaterCellIds.size() > j + 1) + edges.push_back(Map::Edge(weight, surfaceWaterCells[i].underWaterCellIds[j], surfaceWaterCells[i].underWaterCellIds[j + 1])); + + for(int k = 0; k < surfaceWaterCells[i].edges.size() - 1; k++){ + Map::Cell adjacentUnderwaterCell = cells[surfaceWaterCells[i].edges[k].destCellId]; + + if(adjacentUnderwaterCell.underWaterCellIds.size() >= j + 1){ + edges.push_back(Map::Edge(weight, surfaceWaterCells[i].underWaterCellIds[j], adjacentUnderwaterCell.underWaterCellIds[j])); + } + } + + Vector3 cellPos = surfaceWaterCells[i].pos - Vector3::VEC_J * cellSize.y * (j + 1); + cells.push_back(Map::Cell(cellPos, Map::Cell::Type::WATER, edges)); + } + } + + return cells; + } + + void MapEditorAppState::MapEditor::generateMinimap(string mapFolder, vector &cells){ + Vector3 mapSize = map->getMapSize(), cellSize = map->getCellSize(); + int width = int(mapSize.x / cellSize.x); + int height = int(mapSize.z / cellSize.z); + int numChannels = 3; + int size = width * height * numChannels; + int cellId = 0; + float baseHeight = map->getBaseHeight(); + + u8 *imgData = new u8[size]; + + for(u8 *p = imgData; p != imgData + size; p+= numChannels, cellId++){ + float min = .6, max = 1.; + float heightFactor = min + (max - min) * (cells[cellId].pos.y - baseHeight) / mapSize.y; + Vector3 color = (cells[cellId].type == Map::Cell::Type::WATER ? Vector3::VEC_K : Vector3::VEC_J); + + *p = color.x * heightFactor * 255.f; + *(p + 1) = color.y * heightFactor * 255.f; + *(p + 2) = color.z * heightFactor * 255.f; + } + + stbi_write_jpg(string(mapFolder + "minimap.jpg").c_str(), width, height, numChannels, imgData, 100); + } + + string MapEditorAppState::MapEditor::generatePlayerTableStr(Player* player){ + string mapScript = ""; + vector resourceDeposits = player->getResourceDeposits(); + + if(!resourceDeposits.empty()){ + mapScript += "\t\t\tresourceDeposits = {\n"; + + for(ResourceDeposit *rd : resourceDeposits){ + Vector3 pos = rd->getPos(); + string posStr = "x = " + to_string(pos.x) + ", y = " + to_string(pos.y) + ", z = " + to_string(pos.z); + + Quaternion rot = rd->getRot(); + string rotStr = "w = " + to_string(rot.w) + ", x = " + to_string(rot.x) + ", y = " + to_string(rot.y) + ", z = " + to_string(rot.z); + + mapScript += "\t\t\t\t{id = " + to_string(rd->getId()) + ", pos = {" + posStr + "}, rot = {" + rotStr + "}},\n"; + } + + mapScript += "\t\t\t},\n"; + } + + vector units = player->getUnits(); + + if(!units.empty()){ + mapScript += "\t\t\tunits = {\n"; + + for(Unit *unit : units){ + string idStr = "id = " + to_string(unit->getId()); + + Vector3 unitPos = unit->getPos(); + string posStr = "pos = {x = " + to_string(unitPos.x) + ", y = " + to_string(unitPos.y) + ", z = " + to_string(unitPos.z) + "}"; + + Quaternion unitRot = unit->getRot(); + string rotStr = "rot = {w = " + to_string(unitRot.w) + ", x = " + to_string(unitRot.x) + ", y = " + to_string(unitRot.y) + ", z = " + to_string(unitRot.z) + "}"; + + mapScript += "\t\t\t\t{" + idStr + ", " + posStr + ", " + rotStr + "},\n"; + } + + mapScript += "\t\t\t}\n"; + } + + return mapScript; + } + + void MapEditorAppState::MapEditor::generateMapScript(vector &cells){ + int numWaterBodies = map->getNodeParent()->getNumChildren() - 1; + + string mapScript = "metadata = {\n\tlights = {"; + + for(Node *light : map->getLights()){ + mapScript += "\n\t\t{type = " + to_string((int)light->getLight(0)->getLightType()); + + if(light->getLight(0)->getLightType() == Light::Type::DIRECTIONAL){ + Vector3 dir = light->getGlobalAxis(2); + mapScript += ", dir = {x = " + to_string(dir.x) + ", y = " + to_string(dir.y) + "z = " + to_string(dir.z) + "}"; + } + + Vector3 color = light->getLight(0)->getColor(); + mapScript += ", color = {x = " + to_string(color.x) + ", y = " + to_string(color.y) + ", z = " + to_string(color.z) + "}},"; + } + + mapScript += "\n\t},\n"; + Vector3 mapSize = map->getMapSize(); + mapScript += "\tsize = {x = " + to_string(mapSize.x) + ", y = " + to_string(mapSize.y) + ", z = " + to_string(mapSize.z) + "},\n"; + mapScript += "\timpassibleNodeValue = " + to_string(IMPASS_NODE_VAL) + ",\n"; + + int numSpawnPoints = map->getNumSpawnPoints(); + mapScript += "\tspawnPoints = {\n"; + + for(int i = 0; i < numSpawnPoints; i++){ + Vector3 sp = map->getSpawnPoint(i); + mapScript += "\t\t{x = " + to_string(sp.x) + ", y = " + to_string(sp.y) + ", z = " + to_string(sp.z) + "},\n"; + } + + mapScript += "\t},\n"; + mapScript += "\tchoosablePlayers = {"; + Game *game = Game::getSingleton(); + + for(Player *player : game->getPlayers()) + mapScript += "\n\t\t{\n" + generatePlayerTableStr(player) + "\n\t\t},\n"; + + mapScript += "\t},\n"; + mapScript += "\tcivilianPlayer = {\n" + generatePlayerTableStr(game->getCivilianPlayer()) + "\n\t},\n"; + + Root *root = Root::getSingleton(); + string skyboxPath = ""; + + if(root->getSkybox()){ + Texture *tex = ((Material::TextureUniform*)root->getSkybox()->getMaterial()->getUniform("tex"))->value; + string path = tex->getPath()[0]; + int slashId = path.find_last_of('/'); + skyboxPath = path.substr(0, slashId); + + slashId = skyboxPath.find_last_of('/'); + skyboxPath = skyboxPath.substr(slashId + 1); + } + + mapScript += "\tskybox = \"" + skyboxPath + "\",\n"; + mapScript += "\tterrain = {model = \"" + map->getMapName() + ".xml\", albedo = \"" + map->getMapName() + ".jpg\"},\n"; + mapScript += "\twaterbodies = {\n"; + + for(int i = 0; i < numWaterBodies; i++){ + Node *waterNode = map->getNodeParent()->getChild(i + 1); + Vector3 pos = waterNode->getPosition(); + Vector3 size = ((Quad*)waterNode->getMesh(0))->getSize(); + mapScript += + "\t\t{pos = {x = " + to_string(pos.x) + ", y = " + to_string(pos.y) + ", z = " + to_string(pos.z) + "},\ + size = {x = " + to_string(size.x) + ", y = " + to_string(size.y) + "}, albedo = \"water.png\"},"; + } + + mapScript += "\t}\n}"; + + string cellsScript = "cells = {\n"; + + for(Map::Cell cell : cells){ + Vector3 p = cell.pos; + cellsScript += "\t\t{type = " + to_string((int)cell.type) + ", pos = {x = " + to_string(p.x) + ", y = " + to_string(p.y) + ", z = " + to_string(p.z) + "}, numEdges = " + to_string(cell.edges.size()) + ", edges = {"; + + for(Map::Edge edge : cell.edges) + cellsScript += "{srcCellId = " + to_string(edge.srcCellId) + ", destCellId = " + to_string(edge.destCellId) + ", weight = " + to_string(edge.weight) + "}, "; + + int numSubCells = cell.underWaterCellIds.size(); + cellsScript += "}, numUnderWaterCells = " + to_string(numSubCells) + ","; + + if(numSubCells > 0){ + cellsScript += "underWaterCellId = {"; + + for(int subCellId : cell.underWaterCellIds) + cellsScript += to_string(subCellId) + ", "; + + cellsScript += "}"; + } + + cellsScript += "\t\t},\n"; + } + + cellsScript += "\t}"; + + string basePath = GameManager::getSingleton()->getPath() + "Models/Maps/" + map->getMapName() + "/"; + + std::ofstream outFile(basePath + map->getMapName() + ".lua"); + outFile << mapScript; + outFile.close(); + + outFile = std::ofstream(basePath + "cells.lua"); + outFile << cellsScript; + outFile.close(); + } + + void MapEditorAppState::MapEditor::exportMap(){ + string assetsPath = GameManager::getSingleton()->getPath(); + string mapFolder = assetsPath + "Models/Maps/" + map->getMapName() + "/"; + create_directory(mapFolder); + copy_file(assetsPath + DEFAULT_TEXTURE, mapFolder + map->getMapName() + ".jpg", filesystem::copy_options::overwrite_existing); + + vector cells = generateMapCells(); + generateLandmassXml(); + generateMapScript(cells); + generateMinimap(mapFolder, cells); + } + + void MapEditorAppState::MapEditor::togglePush(bool push){ + this->pushing = push; + + if(push){ + MeshData meshData = map->getNodeParent()->getChild(0)->getMesh(0)->getMeshBase(); + int numVerts = 3 * meshData.numTris; + + for(int i = 0; i < numVerts; i++) + oldLandmassVertHeights[i] = meshData.vertices[i].pos->y; + } + } + + MapEditorAppState::MapEditorAppState(string name, Vector2 size, bool newMap) : AbstractAppState( + AppStateType::MAP_EDITOR, + configData::calcSumBinds(AppStateType::MAP_EDITOR, true), + configData::calcSumBinds(AppStateType::MAP_EDITOR, false), + GameManager::getSingleton()->getPath() + scripts[ScriptFiles::OPTIONS]){ + mapName = name; + mapSize = size; + this->newMap = newMap; + } + + void MapEditorAppState::update(){ + radiusText->setText(L"Radius: " + to_wstring(mapEditor->getCircleRadius())); + weightsText->setText(L"Weights generated: " + to_wstring(mapEditor->isWeightsGenerated())); + + CameraController *camCtr = CameraController::getSingleton(); + + if(!(camCtr->isLookingAround() || mapEditor->isPushing())) + camCtr->updateCameraPosition(); + + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + + if(ufCtr->isPlacingOnSurface()) + ufCtr->update(); + } + + void MapEditorAppState::onAttached(){ + AbstractAppState::onAttached(); + mapEditor = new MapEditor(mapName, mapSize, newMap); + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "text"); + mat->addBoolUniform("texturingEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4::VEC_IJKL); + + string fontPath = GameManager::getSingleton()->getPath() + "Fonts/batang.ttf"; + radiusText = new Text(fontPath, L""); + radiusText->setMaterial(mat); + weightsText = new Text(fontPath, L""); + weightsText->setMaterial(mat); + + Node *radiusNode = new Node(Vector3(0, 100, 0)); + radiusNode->addText(radiusText); + root->getGuiNode()->attachChild(radiusNode); + + Node *weightsNode = new Node(Vector3(0, 200, 0)); + weightsNode->addText(weightsText); + root->getGuiNode()->attachChild(weightsNode); + } + + void MapEditorAppState::onDettached(){} + + void MapEditorAppState::onAction(int bind, bool isPressed){ + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + + switch((Bind)bind){ + case Bind::LOOK_AROUND: + if(!ufCtr->isPlacingOnSurface()) + CameraController::getSingleton()->setLookingAround(isPressed); + + break; + case Bind::ROTATE_OBJ_FRAME: + if(isPressed && ufCtr->isPlacingOnSurface()) + ufCtr->setRotating(true); + else if(!isPressed && ufCtr->isRotating()){ + Player *player = Game::getSingleton()->getPlayer(0); + GameObjectFrame &frame = ufCtr->getGameObjectFrame(0); + Model *model = frame.getModel(); + Vector3 pos = model->getPosition(); + Quaternion rot = model->getOrientation(); + + if(frame.getType() == GameObject::Type::RESOURCE_DEPOSIT) + player->addResourceDeposit(GameObjectFactory::createResourceDeposit(player, frame.getId(), pos, rot)); + else + player->addUnit(GameObjectFactory::createUnit(player, frame.getId(), pos, rot)); + + ufCtr->setRotating(false); + } + + break; + case Bind::DESELECT_STRUCTURE: + ufCtr->removeGameObjectFrames(); + ufCtr->setPlacingOnSurface(false); + ufCtr->setRotating(false); + break; + case Bind::INCREASE_RADIUS: + if(isPressed) mapEditor->updateCircleRadius(true); + break; + case Bind::DECREASE_RADIUS: + if(isPressed) mapEditor->updateCircleRadius(false); + break; + case Bind::LEFT_CLICK: + if(isPressed){ + Camera *cam = Root::getSingleton()->getCamera(); + Vector3 startPos = cam->getPosition(); + Vector2 cursorPos = getCursorPos(); + Vector3 endPos = screenToSpace(cursorPos); + + vector results = RayCaster::cast( + startPos, + (endPos - startPos).norm(), + Root::getSingleton()->getRootNode(), + 0, + configData::DIST_FROM_RAY + ); + + if(cursorPos.y < GameManager::getSingleton()->getHeight() - mapEditor->getGuiThreshold()){ + bool push = !results.empty(); + mapEditor->togglePush(push); + + if(push) + mapEditor->setPushPos(results[0].pos); + + mapEditor->castSelectionRay(); + } + else + mapEditor->togglePush(false); + } + else + mapEditor->togglePush(false); + + break; + case Bind::CREATE_WATERBODY: + if(isPressed) mapEditor->createWaterbody(); + break; + case Bind::MOVE_TERR_OBJ: + if(isPressed){ + mapEditor->setMovingTerrainObject(true); + mapEditor->setScalingTerrainObject(false); + break; + } + case Bind::SCALE_TERR_OBJ: + if(isPressed){ + mapEditor->setMovingTerrainObject(false); + mapEditor->setScalingTerrainObject(true); + break; + } + case Bind::STOP_TERR_OBJ: + if(isPressed){ + mapEditor->setMovingTerrainObject(false); + mapEditor->setScalingTerrainObject(false); + break; + } + case Bind::ENABLE_X_AXIS: + case Bind::ENABLE_Y_AXIS: + case Bind::ENABLE_Z_AXIS: + if(isPressed && (mapEditor->isMovingTerrainObject() || mapEditor->isScalingTerrainObject())){ + MapEditor::TransformAxis axis = MapEditor::TransformAxis((int)bind - (int)Bind::ENABLE_X_AXIS); + mapEditor->setTransformAxis(axis); + } + + break; + } + } + + void MapEditorAppState::onAnalog(int bind, float strength){ + CameraController *camCtr = CameraController::getSingleton(); + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + + switch((Bind)bind){ + case Bind::LOOK_UP: + case Bind::LOOK_DOWN: + if(camCtr->isLookingAround()){ + Vector3 dirProj = Root::getSingleton()->getCamera()->getDirection(); + dirProj = Vector3(dirProj.x, 0, dirProj.z).norm(); + camCtr->orientCamera(Vector3(0, 1, 0).cross(dirProj), strength); + } + + break; + case Bind::LOOK_LEFT: + case Bind::LOOK_RIGHT: + if(camCtr->isLookingAround()) + camCtr->orientCamera(Vector3(0, 1, 0), strength); + else if(ufCtr->isRotating()) + ufCtr->rotateGameObjectFrames(100 * strength); + + break; + case Bind::PUSH_VERTS_UP: + case Bind::PUSH_VERTS_DOWN: + if(mapEditor->isPushing()) mapEditor->pushLandmassVerts(strength); + else if(mapEditor->getSelectedNode() != Map::getSingleton()->getNodeParent()->getChild(0)){ + if(mapEditor->isScalingTerrainObject()) + mapEditor->scaleTerrainObject(strength); + else if(mapEditor->isMovingTerrainObject()) + mapEditor->moveTerrainObject(strength); + } + + break; + } + } + + void MapEditorAppState::onRawMouseWheelScroll(bool up){ + CameraController::getSingleton()->zoomCamera(up); + } +} diff --git a/source/core/appStates/mapEditorAppState.h b/source/core/appStates/mapEditorAppState.h new file mode 100644 index 0000000..64281e0 --- /dev/null +++ b/source/core/appStates/mapEditorAppState.h @@ -0,0 +1,97 @@ +#ifndef MAP_EDITOR_APP_STATE_H +#define MAP_EDITOR_APP_STATE_H + +#include "map.h" +#include "player.h" + +#include + +#include + +#include +#include + +#include + +namespace vb01{ + class Texture; +} + +namespace battleship{ + class MapEditor; + + class MapEditorAppState : public gameBase::AbstractAppState{ + public: + class MapEditor{ + public: + enum TransformAxis{ + X_AXIS, + Y_AXIS, + Z_AXIS + }; + + MapEditor(std::string, vb01::Vector2, bool); + void updateCircleRadius(bool); + void pushLandmassVerts(float); + void castSelectionRay(); + void createWaterbody(); + void moveTerrainObject(float); + void scaleTerrainObject(float); + void exportMap(); + void prepareTerrainObjects(int = 0, int = -1); + void togglePush(bool); + inline vb01::Node* getSelectedNode(){return selectedTerrainNode;} + inline float getGuiThreshold(){return guiThreshold;} + inline float getCircleRadius(){return circleRadius;} + inline bool isPushing(){return pushing;} + inline void setPushPos(vb01::Vector3 p){pushPos = p;} + inline bool isMovingTerrainObject(){return movingTerrainObject;} + inline void setMovingTerrainObject(bool m){movingTerrainObject = m;} + inline bool isScalingTerrainObject(){return scalingTerrainObject;} + inline void setScalingTerrainObject(bool s){scalingTerrainObject = s;} + inline TransformAxis getTransformAxis(){return transformAxis;} + inline void setTransformAxis(TransformAxis m){transformAxis = m;} + inline bool isWeightsGenerated(){return weightsGenerated;} + inline vb01::Texture* getSkyTexture(int i){return skyTextures[i];} + inline vb01::Texture* getLandmassTexture(int i){return landmassTextures[i];} + private: + void generatePlane(vb01::Vector2); + void prepareTextures(std::string, bool, std::vector&); + void toggleSelection(vb01::Node*, bool); + std::vector generateMapCells(); + void generateLandmassXml(); + void generateMinimap(std::string, std::vector&); + std::string generatePlayerTableStr(Player*); + void generateMapScript(std::vector&); + void prepareTerrainObject(vb01::u32**, Map::Cell*, int[3], float, bool); + + Map *map; + vb01::Node *selectedTerrainNode = nullptr; + TransformAxis transformAxis = X_AXIS; + vb01Gui::Listbox *skyListbox = nullptr; + float *oldLandmassVertHeights = nullptr; + bool pushing = false, movingTerrainObject = false, scalingTerrainObject = false, weightsGenerated = false, newMap, cellMarkersVisible = false; + const float MIN_RADIUS = 1, MAX_RADIUS = 100, INCREASE_RATE = 1; + float circleRadius = MIN_RADIUS, guiThreshold = 200; + vb01::Vector3 pushPos = vb01::Vector3::VEC_ZERO; + std::vector skyTextures, landmassTextures, waterTextures; + }; + + MapEditorAppState(std::string, vb01::Vector2, bool); + void update(); + void onAttached(); + void onDettached(); + void onAction(int, bool); + void onAnalog(int, float); + void onRawMouseWheelScroll(bool); + inline MapEditor* getMapEditor(){return mapEditor;} + private: + MapEditor *mapEditor = nullptr; + std::string mapName; + vb01::Vector2 mapSize; + bool newMap; + vb01::Text *radiusText = nullptr, *weightsText = nullptr; + }; +} + +#endif diff --git a/source/core/binds.h b/source/core/binds.h new file mode 100644 index 0000000..97498d2 --- /dev/null +++ b/source/core/binds.h @@ -0,0 +1,62 @@ +#ifndef BINDS_H +#define BINDS_H + +namespace battleship{ + enum Bind{ + LEFT_CLICK, + SCROLLING_UP, + SCROLLING_DOWN, + LEFT, + RIGHT, + DELETE_CHAR, + TOGGLE_MAIN_MENU, + LOOK_UP, + LOOK_DOWN, + LOOK_LEFT, + LOOK_RIGHT, + LOOK_AROUND, + DRAG_BOX, + ROTATE_OBJ_FRAME, + HALT, + ZOOM_IN, + ZOOM_OUT, + LEFT_CONTROL, + LEFT_SHIFT, + SELECT_PATROL_POINTS, + LAUNCH, + SHIFT_SUB_DEPTH, + GROUP_0, + GROUP_1, + GROUP_2, + GROUP_3, + GROUP_4, + GROUP_5, + GROUP_6, + GROUP_7, + GROUP_8, + GROUP_9, + SELECT_STRUCTURE, + DESELECT_STRUCTURE, + DECREASE_RADIUS, + INCREASE_RADIUS, + PUSH_VERTS_UP, + PUSH_VERTS_DOWN, + MOVE_TERR_OBJ, + SCALE_TERR_OBJ, + STOP_TERR_OBJ, + ENABLE_X_AXIS, + ENABLE_Y_AXIS, + ENABLE_Z_AXIS, + CREATE_WATERBODY, + GENERATE_WEIGHTS, + TOGGLE_CELL_MARKERS, + EJECT_GARRISON, + ENABLE_CHASE_STATE, + ENABLE_STAND_GROUND_STATE, + ENABLE_HOLD_FIRE_STATE, + HACK, + MOVE_CAMERA + }; +} + +#endif diff --git a/source/core/console/abstractCommand.cpp b/source/core/console/abstractCommand.cpp new file mode 100644 index 0000000..4c19c45 --- /dev/null +++ b/source/core/console/abstractCommand.cpp @@ -0,0 +1,33 @@ +#include "abstractCommand.h" + +#include +#include + +namespace battleship{ + using namespace std; + + void AbstractCommand::handle(){ + if(cmdStr.find(" ") == -1) + return; + + vector spaceIds; + arguments.clear(); + + for(int i = 0; i < cmdStr.length(); i++) + if(cmdStr[i] == ' ') + spaceIds.push_back(i); + + arguments.push_back(cmdStr.substr(0, spaceIds[0])); + + for(int i = 0; i < spaceIds.size(); i++){ + bool lastSpace = (i == spaceIds.size() - 1); + string argument = cmdStr.substr(spaceIds[i] + 1, lastSpace ? string::npos : spaceIds[i + 1] - spaceIds[i] - 1); + arguments.push_back(argument); + } + } + + void AbstractCommand::execute(){ + handle(); + validate(); + } +} diff --git a/source/core/console/abstractCommand.h b/source/core/console/abstractCommand.h new file mode 100644 index 0000000..8e3a97f --- /dev/null +++ b/source/core/console/abstractCommand.h @@ -0,0 +1,20 @@ +#ifndef ABSTRACT_COMMAND_H +#define ABSTRACT_COMMAND_H + +#include +#include + +namespace battleship{ + class AbstractCommand{ + protected: + AbstractCommand(std::string str) : cmdStr(str){} + virtual void handle(); + virtual void validate(){} + virtual void execute(); + + std::string cmdStr; + std::vector arguments; + }; +} + +#endif diff --git a/source/core/console/addResourceCommand.cpp b/source/core/console/addResourceCommand.cpp new file mode 100644 index 0000000..15bfe6a --- /dev/null +++ b/source/core/console/addResourceCommand.cpp @@ -0,0 +1,30 @@ +#include "addResourceCommand.h" +#include "game.h" +#include "player.h" +#include "trader.h" + +namespace battleship{ + void AddResourceCommand::validate(){ + if(arguments.size() != 3) + return; + + playerId = atoi(arguments[0].c_str()); + + if(playerId != 0 && playerId != 1) + return; + + resourceId = atoi(arguments[1].c_str()); + + if(!(0 <= resourceId && resourceId <= 2)) + return; + + resourceAmmount = atoi(arguments[2].c_str()); + } + + void AddResourceCommand::execute(){ + AbstractCommand::execute(); + + Player *player = Game::getSingleton()->getPlayer(playerId); + player->updateResource(ResourceType(resourceId), resourceAmmount, true); + } +} diff --git a/source/core/console/addResourceCommand.h b/source/core/console/addResourceCommand.h new file mode 100644 index 0000000..711a829 --- /dev/null +++ b/source/core/console/addResourceCommand.h @@ -0,0 +1,18 @@ +#ifndef ADD_RESOURCE_COMMAND_H +#define ADD_RESOURCE_COMMAND_H + +#include "abstractCommand.h" + +namespace battleship{ + class AddResourceCommand : public AbstractCommand{ + public: + AddResourceCommand(std::string argsStr) : AbstractCommand(argsStr){} + void execute(); + private: + void validate(); + + int playerId, resourceId, resourceAmmount; + }; +} + +#endif diff --git a/source/core/console/addTechnologyCommand.cpp b/source/core/console/addTechnologyCommand.cpp new file mode 100644 index 0000000..73468c7 --- /dev/null +++ b/source/core/console/addTechnologyCommand.cpp @@ -0,0 +1,37 @@ +#include "addTechnologyCommand.h" +#include "game.h" +#include "player.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace gameBase; + + void AddTechnologyCommand::validate(){ + if(arguments.size() != 2) + return; + + playerId = atoi(arguments[0].c_str()); + int numPlayers = Game::getSingleton()->getNumPlayers(); + + if(!(0 <= playerId && playerId < numPlayers)) + return; + + sol::state_view SOL_LUA_VIEW = generateView(); + SOL_LUA_VIEW.script("numTechs = #technologies"); + int numTechs = SOL_LUA_VIEW["numTechs"]; + + techId = atoi(arguments[1].c_str()); + + if(!(0 <= techId && techId < numTechs)) + return; + } + + void AddTechnologyCommand::execute(){ + AbstractCommand::execute(); + Game::getSingleton()->getPlayer(playerId)->addTechnology(techId); + } +} diff --git a/source/core/console/addTechnologyCommand.h b/source/core/console/addTechnologyCommand.h new file mode 100644 index 0000000..f60cd45 --- /dev/null +++ b/source/core/console/addTechnologyCommand.h @@ -0,0 +1,18 @@ +#ifndef ADD_TECHNOLOGY_H +#define ADD_TECHNOLOGY_H + +#include "abstractCommand.h" + +namespace battleship{ + class AddTechnologyCommand : public AbstractCommand{ + public: + AddTechnologyCommand(std::string argsStr) : AbstractCommand(argsStr){} + void execute(); + private: + void validate(); + + int playerId, techId; + }; +} + +#endif diff --git a/source/core/console/addUnitCommand.cpp b/source/core/console/addUnitCommand.cpp new file mode 100644 index 0000000..c46452f --- /dev/null +++ b/source/core/console/addUnitCommand.cpp @@ -0,0 +1,63 @@ +#include +#include + +#include "addUnitCommand.h" +#include "console.h" +#include "addUnitCommand.h" +#include "inGameAppState.h" +#include "game.h" +#include "player.h" +#include "gameObjectFactory.h" + +namespace battleship{ + using namespace gameBase; + using namespace vb01; + using namespace std; + + //TODO implement structure build status argument + void AddUnitCommand::validate(){ + if(!(arguments.size() == 2 || arguments.size() == 5 || arguments.size() == 9)) + return; + + playerId = atoi(arguments[0].c_str()); + + if(playerId != 0 && playerId != 1) + return; + + unitId = atoi(arguments[1].c_str()); + + sol::state_view SOL_LUA_VIEW = generateView(); + string varName = "numUnits"; + SOL_LUA_VIEW.script(varName + " = #units"); + int numUnits = SOL_LUA_VIEW[varName]; + + if(!(0 <= unitId && unitId <= numUnits)) + return; + + if(arguments.size() >= 5){ + float x = atoi(arguments[2].c_str()); + float y = atoi(arguments[3].c_str()); + float z = atoi(arguments[4].c_str()); + pos = Vector3(x, y, z); + } + else if(2 < arguments.size() && arguments.size() < 5) + return; + + if(arguments.size() == 9){ + float w = atoi(arguments[5].c_str()); + float x = atoi(arguments[6].c_str()); + float y = atoi(arguments[7].c_str()); + float z = atoi(arguments[8].c_str()); + rot = Quaternion(w, x, y, z); + } + else if(5 < arguments.size() && arguments.size() < 9) + return; + } + + void AddUnitCommand::execute(){ + AbstractCommand::execute(); + + Player* player = Game::getSingleton()->getPlayer(playerId); + player->addUnit(GameObjectFactory::createUnit(player, unitId, pos, rot, 100)); + } +} diff --git a/source/core/console/addUnitCommand.h b/source/core/console/addUnitCommand.h new file mode 100644 index 0000000..b91467f --- /dev/null +++ b/source/core/console/addUnitCommand.h @@ -0,0 +1,23 @@ +#ifndef ADD_UNIT_COMMAND_H +#define ADD_UNIT_COMMAND_H + +#include "abstractCommand.h" + +#include +#include + +namespace battleship{ + class AddUnitCommand : public AbstractCommand{ + public: + AddUnitCommand(std::string argsStr) : AbstractCommand(argsStr){} + void validate(); + void execute(); + private: + int playerId, unitId; + bool posEnabled, rotEnabled; + vb01::Vector3 pos = vb01::Vector3::VEC_ZERO; + vb01::Quaternion rot = vb01::Quaternion::QUAT_W; + }; +} + +#endif diff --git a/source/core/console/console.cpp b/source/core/console/console.cpp new file mode 100644 index 0000000..0b2c38c --- /dev/null +++ b/source/core/console/console.cpp @@ -0,0 +1,28 @@ +#include "console.h" +#include "addUnitCommand.h" +#include "addResourceCommand.h" +#include "addTechnologyCommand.h" +#include "toggleDebugCommand.h" + +using namespace std; + +namespace battleship{ + void Console::execute(string cmdStr) { + int space = cmdStr.find(" "); + string cmdName = cmdStr, argsStr = ""; + + if(space != -1){ + cmdName = cmdStr.substr(0, space); + argsStr = cmdStr.substr(space + 1, string::npos); + } + + if(cmdName == "add-unit") + AddUnitCommand(argsStr).execute(); + else if(cmdName == "add-resource") + AddResourceCommand(argsStr).execute(); + else if(cmdName == "add-technology") + AddTechnologyCommand(argsStr).execute(); + else if(cmdName == "toggle-debug") + ToggleDebugCommand(argsStr).execute(); + } +} diff --git a/source/core/console/console.h b/source/core/console/console.h new file mode 100644 index 0000000..9675c27 --- /dev/null +++ b/source/core/console/console.h @@ -0,0 +1,13 @@ +#ifndef CONSOLE_COMMAND_H +#define CONSOLE_COMMAND_H + +#include + +namespace battleship{ + class Console{ + public: + static void execute(std::string); + }; +} + +#endif diff --git a/source/core/console/toggleDebugCommand.cpp b/source/core/console/toggleDebugCommand.cpp new file mode 100644 index 0000000..5ebf9c9 --- /dev/null +++ b/source/core/console/toggleDebugCommand.cpp @@ -0,0 +1,15 @@ +#include "toggleDebugCommand.h" +#include "game.h" + +namespace battleship{ + void ToggleDebugCommand::validate(){ + if(!arguments.empty()) return; + } + + void ToggleDebugCommand::execute(){ + AbstractCommand::execute(); + + Game *game = Game::getSingleton(); + game->setDebug(!game->isDebug()); + } +} diff --git a/source/core/console/toggleDebugCommand.h b/source/core/console/toggleDebugCommand.h new file mode 100644 index 0000000..794a795 --- /dev/null +++ b/source/core/console/toggleDebugCommand.h @@ -0,0 +1,16 @@ +#ifndef TOGGLE_DEBUG_COMMAND_H +#define TOGGLE_DEBUG_COMMAND_H + +#include "abstractCommand.h" + +namespace battleship{ + class ToggleDebugCommand : public AbstractCommand{ + public: + ToggleDebugCommand(std::string str) : AbstractCommand(str){} + void validate(); + void execute(); + private: + }; +} + +#endif diff --git a/source/core/controllers/cameraController.cpp b/source/core/controllers/cameraController.cpp new file mode 100644 index 0000000..fa6751e --- /dev/null +++ b/source/core/controllers/cameraController.cpp @@ -0,0 +1,72 @@ +#include "cameraController.h" +#include "defConfigs.h" + +#include +#include +#include + +namespace battleship{ + using namespace vb01; + using namespace configData; + + static CameraController *cameraController = nullptr; + + CameraController* CameraController::getSingleton(){ + if(!cameraController) + cameraController = new CameraController(); + + return cameraController; + } + + void CameraController::updateCameraPosition() { + GameManager *gm = GameManager::getSingleton(); + Vector2 cursorPos = getCursorPos(); + + Camera *cam = Root::getSingleton()->getCamera(); + Vector3 camDir = cam->getDirection(); + Vector3 forwVec = Vector3(camDir.x, 0, camDir.z).norm(); + Vector3 camLeft = cam->getLeft(); + camLeft = Vector3(camLeft.x, 0, camLeft.z).norm(); + + int width, height; + glfwGetWindowSize(Root::getSingleton()->getWindow(), &width, &height); + + if (cursorPos.x <= 0 && 0 < cursorPos.y && cursorPos.y < height) + cam->setPosition(cam->getPosition() - camLeft * camPanSpeed); + + if (cursorPos.x >= width && 0 < cursorPos.y && cursorPos.y < height) + cam->setPosition(cam->getPosition() + camLeft * camPanSpeed); + + if (cursorPos.y <= 0 && 0 < cursorPos.x && cursorPos.x < width) + cam->setPosition(cam->getPosition() + forwVec * camPanSpeed); + + if (cursorPos.y >= height && 0 < cursorPos.x && cursorPos.x < width) + cam->setPosition(cam->getPosition() - forwVec * camPanSpeed); + } + + void CameraController::orientCamera(Vector3 rotAxis, double str){ + Quaternion rotQuat = Quaternion(.025 * str, rotAxis); + Camera *cam = Root::getSingleton()->getCamera(); + Vector3 dir = rotQuat * cam->getDirection(), up = rotQuat * cam->getUp(); + cam->lookAt(dir, up); + } + + void CameraController::zoomCamera(bool zoomIn){ + if((zoomIn && numZooms > configData::NUM_MAX_ZOOMS) || (!zoomIn && numZooms < -configData::NUM_MAX_ZOOMS)) return; + + Vector3 newPos; + float offset; + + if(zoomIn){ + numZooms++; + offset = configData::CAMERA_ZOOM_INCREMENT; + } + else{ + numZooms--; + offset = -configData::CAMERA_ZOOM_INCREMENT; + } + + Camera *cam = Root::getSingleton()->getCamera(); + cam->setPosition(cam->getPosition() + cam->getDirection() * offset); + } +} diff --git a/source/core/controllers/cameraController.h b/source/core/controllers/cameraController.h new file mode 100644 index 0000000..b4998ac --- /dev/null +++ b/source/core/controllers/cameraController.h @@ -0,0 +1,23 @@ +#ifndef CAMERA_CONTROLLER_H +#define CAMERA_CONTROLLER_H + +#include + +namespace battleship{ + class CameraController{ + public: + static CameraController* getSingleton(); + void updateCameraPosition(); + void orientCamera(vb01::Vector3, double); + void zoomCamera(bool); + inline bool isLookingAround(){return lookingAround;} + inline void setLookingAround(bool l){lookingAround = l;} + private: + CameraController(){} + + bool lookingAround = false; + int numZooms = 0; + }; +} + +#endif diff --git a/source/core/controllers/concreteGuiManager.cpp b/source/core/controllers/concreteGuiManager.cpp new file mode 100644 index 0000000..76c6939 --- /dev/null +++ b/source/core/controllers/concreteGuiManager.cpp @@ -0,0 +1,652 @@ +#include +#include + +#include + +#include "concreteGuiManager.h" +#include "unit.h" +#include "gameManager.h" +#include "singlePlayerButton.h" +#include "mapEditorButton.h" +#include "optionsButton.h" +#include "exitButton.h" +#include "tabButton.h" +#include "okButton.h" +#include "defaultsButton.h" +#include "backButton.h" +#include "newMapButton.h" +#include "loadMapButton.h" +#include "exportButton.h" +#include "mapListbox.h" +#include "skyboxTextureListbox.h" +#include "landTextureListbox.h" +#include "gameObjectListbox.h" +#include "playButton.h" +#include "inGameAppState.h" +#include "mainMenuButton.h" +#include "buildButton.h" +#include "trainButton.h" +#include "statsButton.h" +#include "researchButton.h" +#include "tradeButton.h" +#include "activeStateButton.h" +#include "playerTradeButton.h" +#include "tradingScreenButton.h" +#include "offerButton.h" +#include "resourceAmmountButton.h" +#include "orderButton.h" +#include "stateToggleButton.h" +#include "minimapButton.h" +#include "activeStateBackButton.h" + +namespace battleship{ + using namespace std; + using namespace gameBase; + using namespace vb01; + using namespace vb01Gui; + + static ConcreteGuiManager *concreteGuiManager = nullptr; + + ConcreteGuiManager::ConcreteGuiManager(){ + string assetPath = GameManager::getSingleton()->getPath(); + texBasePath = assetPath + "Textures/"; + fontBasePath = assetPath + "Fonts/"; + } + + ConcreteGuiManager* ConcreteGuiManager::getSingleton(){ + if(!concreteGuiManager) + concreteGuiManager = new ConcreteGuiManager(); + + return concreteGuiManager; + } + + //TODO refactor player difficulty and faction listbox selection + //TODO remove hardcoded font path values + //TODO use configurable map path values + Button* ConcreteGuiManager::parseButton(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + sol::table posTable = guiTable["pos"]; + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + sol::table sizeTable = guiTable["size"]; + Vector2 size = Vector2(sizeTable["x"], sizeTable["y"]); + + //TODO factor out repetetive optional lua key checks + string name = "", nk = "name"; + sol::optional nameOpt = guiTable[nk]; + + if(nameOpt != sol::nullopt) name = guiTable[nk]; + + ButtonType type = (ButtonType)guiTable["buttonType"]; + + string imagePath = "", ipk = "imagePath"; + sol::optional pathOpt = guiTable[ipk]; + bool texturingEnabled = false; + + if(pathOpt != sol::nullopt){ + imagePath = texBasePath; + imagePath += guiTable[ipk]; + bool texturingEnabled = true; + } + + Button *button = nullptr; + string guiScreen = ""; + + switch(type){ + case SINGLE_PLAYER: + button = new SinglePlayerButton(pos, size, name); + break; + case EDITOR: + button = new MapEditorButton(pos, size); + break; + case OPTIONS: + button = new OptionsButton(pos, size, name, true); + break; + case EXIT: + button = new ExitButton(pos, size); + break; + case OK: + button = new OkButton(pos, size, name); + break; + case DEFAULTS: + button = new DefaultsButton(pos, size, name); + break; + case BACK: { + string screen = guiTable["screen"]; + button = new BackButton(pos, size, name, screen); + break; + } + case CONTROLS_TAB: + case MOUSE_TAB: + case VIDEO_TAB: + case AUDIO_TAB: + case MULTIPLAYER_TAB: { + string screens[]{ + "controlsTab.lua", + "mouseTab.lua", + "videoTab.lua", + "audioTab.lua", + "multiplayerTab.lua" + }; + int diff = ((int)type - (int)CONTROLS_TAB); + button = new TabButton(pos, size, name, screens[diff]); + break; + } + case NEW_MAP: + button = new NewMapButton(pos, size); + break; + case NEW_MAP_OK:{ + int numTextboxes = guiTable["numDependencies"]; + vector t; + + for(int i = 0; i < numTextboxes; i++){ + int tid = guiTable["dependencies"][i + 1]["id"]; + t.push_back((Textbox*)guiElements[tid].second); + } + + button = new NewMapButton::OkButton(pos, size, t[0], t[1], t[2]); + break; + } + case LOAD_MAP: + button = new LoadMapButton(pos, size); + break; + case LOAD_MAP_OK:{ + int lid = guiTable["dependencies"][1]["id"]; + button = new LoadMapButton::OkButton(pos, size, (Listbox*)guiElements[lid].second); + break; + } + case EXPORT: + button = new ExportButton(pos, size); + break; + case PLAY:{ + int mid = guiTable["dependencies"][1]["id"]; + Listbox *mapListbox = (MapListbox*)guiElements[mid].second; + button = new PlayButton(mapListbox, pos, size, name, true); + break; + } + case RESUME: + button = new InGameAppState::ResumeButton(pos, size); + break; + case CONSOLE_SCREEN: + button = new InGameAppState::ConsoleButton(pos, size); + break; + case MAIN_MENU: + button = new MainMenuButton(pos, size, name); + break; + case CONSOLE_COMMAND_OK:{ + int lid = guiTable["dependencies"][1]["id"]; + Listbox *listbox = (Listbox*)guiElements[lid].second; + + int tid = guiTable["dependencies"][2]["id"]; + Textbox *textbox = (Textbox*)guiElements[tid].second; + + button = new InGameAppState::ConsoleButton::ConsoleCommandEntryButton(textbox, listbox, pos, size, name); + break; + } + case BUILD: + button = new BuildButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["slotId"]); + break; + case TRAIN: + button = new TrainButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["slotId"]); + break; + case STATISTICS: + button = new StatsButton(pos, size, name, (int)guiTable["trigger"], imagePath); + break; + case RESEARCH: + button = new ResearchButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["techId"]); + break; + case BUY_REFINEDS: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::BUY_REFINEDS); + break; + case SELL_REFINEDS: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::SELL_REFINEDS); + break; + case BUY_RESEARCH: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::BUY_RESEARCH); + break; + case SELL_RESEARCH: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::SELL_RESEARCH); + break; + case ACTIVE_STATE_BUTTON: + button = new ActiveStateButton(pos, size, guiTable["guiScreen"], name, fontBasePath + "batang.ttf", (int)guiTable["trigger"], imagePath); + break; + case ACTIVE_STATE_BACK: + button = new ActiveStateBackButton(pos, size, name); + break; + case PLAYER_TRADE: + guiScreen = guiTable["guiScreen"]; + button = new PlayerTradeButton(pos, size, guiScreen, name, (int)guiTable["trigger"], imagePath); + break; + case TRADING_SCREEN:{ + int lid = guiTable["dependencies"][1]["id"]; + Listbox *listbox = (Listbox*)guiElements[lid].second; + guiScreen = guiTable["guiScreen"]; + + button = new TradingScreenButton(pos, size, listbox, (int)SOL_LUA_STATE["playerId"], guiScreen, name, (int)guiTable["trigger"], imagePath); + break; + } + case TRADE_OFFER: + button = new OfferButton(pos, size, (int)SOL_LUA_STATE["playerId"], name, (int)guiTable["trigger"], imagePath); + break; + case RESOURCE_AMMOUNT: + button = new ResourceAmmountButton(pos, size, name, (int)guiTable["ammount"], (int)guiTable["trigger"], imagePath); + break; + case ORDER: + button = new OrderButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["orderType"]); + break; + case UNIT_STATE: + button = new StateToggleButton(pos, size, name, (int)guiTable["trigger"], imagePath); + break; + case MINIMAP:{ + string minimapPath = GameManager::getSingleton()->getPath() + "Models/Maps/" + Map::getSingleton()->getMapName() + "/minimap.jpg"; + button = new MinimapButton(pos, size, minimapPath); + break; + } + } + + int typeArr[2]{(int)GuiElementType::BUTTON, (int)type}; + guiElements.push_back(make_pair(typeArr, (void*)button)); + + return button; + } + + Listbox* ConcreteGuiManager::parseGameObjectListbox(){ + return nullptr; + } + + Listbox* ConcreteGuiManager::parseListbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + + string sizeTable = "size"; + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + + int numMaxDisplay = guiTable["numMaxDisplay"]; + ListboxType listboxType = (ListboxType)guiTable["listboxType"]; + + int maxDisplay, numLines; + bool closable; + vector lines; + sol::optional linesOpt = guiTable["lines"]; + + if(linesOpt != sol::nullopt){ + sol::table linesTbl = guiTable["lines"]; + + for(int i = 0; i < linesTbl.size(); i++) + lines.push_back(guiTable["lines"][i + 1]); + } + + Listbox *listbox = nullptr; + string fontPath = fontBasePath + "batang.ttf"; + sol::optional nameOpt = guiTable["name"]; + string name = ""; + + if(nameOpt != sol::nullopt) name = guiTable["name"]; + + switch(listboxType){ + case CONTROLS:{ + numLines = 6; + closable = false; + + for(int i = 0; i < numLines; i++) + lines.push_back(to_string(i)); + + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + + break; + } + case RESOLUTION:{ + numLines = guiTable["numLines"]; + + for(int i = 0; i < numLines; i++) + lines.push_back(guiTable["lines"][i + 1]); + + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + + break; + } + case MAPS:{ + lines = readDir(GameManager::getSingleton()->getPath() + "Models/Maps/", true); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + bool addPlayers = guiTable["addPlayerGui"]; + + listbox = new MapListbox(pos, size, lines, maxDisplay, addPlayers, fontPath, closable); + break; + } + case VEHICLES: + case STRUCTURES: + case RESOURCE_DEPOSITS:{ + bool resources = (listboxType == RESOURCE_DEPOSITS); + sol::table gameObjTable = SOL_LUA_STATE[resources ? "resources" : "units"]; + int numGameObjs = gameObjTable.size(); + std::vector gameObjIds; + + for(int i = 0; i < numGameObjs; i++){ + bool canAdd = true; + + if(!resources){ + bool vehicles = (listboxType == VEHICLES); + bool v = gameObjTable[i + 1]["isVehicle"]; + canAdd = (v == vehicles); + } + + if(canAdd){ + lines.push_back(gameObjTable[i + 1]["name"]); + gameObjIds.push_back(i); + } + } + + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new GameObjectListbox(!resources, pos, size, lines, gameObjIds, maxDisplay, fontPath); + break; + } + case SKYBOX_TEXTURES: + lines = readDir(texBasePath + "Skyboxes", true); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new SkyboxTextureListbox(pos, size, lines, maxDisplay, fontPath); + break; + case LAND_TEXTURES: + lines = readDir(texBasePath + "Landmass", false); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new LandTextureListbox(pos, size, lines, maxDisplay, fontPath); + break; + case CPU_DIFFICULTIES: + case FACTIONS: + case COLORS: + case TEAMS: + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath); + break; + case CONSOLE:{ + for(int i = 0; i < numMaxDisplay; i++) + lines.push_back(""); + + closable = false; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + } + break; + case TRADE_OFFERS: + closable = true; + listbox = new Listbox(pos, size, lines, numMaxDisplay, fontPath, closable); + break; + } + + int typeArr[2]{(int)GuiElementType::LISTBOX, (int)listboxType}; + guiElements.push_back(make_pair(typeArr, (void*)listbox)); + + listbox->setName(name); + + return listbox; + } + + Checkbox* ConcreteGuiManager::parseCheckbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Checkbox *checkbox = new Checkbox(pos, fontBasePath + "batang.ttf"); + + int typeArr[2]{(int)GuiElementType::CHECKBOX, -1}; + guiElements.push_back(make_pair(typeArr, (void*)checkbox)); + + return checkbox; + } + + Slider* ConcreteGuiManager::parseSlider(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos", sizeTable = "size"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + + Slider *slider = new Slider(pos, size, guiTable["minValue"], guiTable["maxValue"]); + + int typeArr[2]{(int)GuiElementType::SLIDER, -1}; + guiElements.push_back(make_pair(typeArr, (void*)slider)); + + return slider; + } + + Textbox* ConcreteGuiManager::parseTextbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos", sizeTable = "size"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + Textbox *textbox = new Textbox(pos, size, fontBasePath + "batang.ttf"); + + int typeArr[2]{(int)GuiElementType::TEXTBOX, -1}; + guiElements.push_back(make_pair(typeArr, (void*)textbox)); + + return textbox; + } + + //TODO factor out checking for optional lua values + Node* ConcreteGuiManager::parseGuiRectangle(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->setTransparent(true); + + bool texturingEnabled = false; + string imagePath = "", ipk = "imagePath"; + sol::optional pathOpt = guiTable[ipk]; + + if(pathOpt != sol::nullopt){ + imagePath = guiTable[ipk]; + texturingEnabled = true; + } + + string name = "", nk = "name"; + sol::optional nameOpt = guiTable[nk]; + + if(nameOpt != sol::nullopt) name = guiTable[nk]; + + mat->addBoolUniform("texturingEnabled", texturingEnabled); + + if(texturingEnabled){ + string p[]{texBasePath + imagePath}; + Texture *tex = new Texture(p, 1, false); + mat->addTexUniform("diffuseMap", tex, false); + } + else{ + sol::table colorTable = guiTable["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["w"])); + } + + sol::table sizeTable = guiTable["size"]; + Quad *quad = new Quad(Vector3(sizeTable["x"], sizeTable["y"], 1), false); + quad->setMaterial(mat); + + sol::table posTable = guiTable["pos"]; + Node *guiRectangle = new Node(Vector3(posTable["x"], posTable["y"], posTable["z"]), Quaternion::QUAT_W, Vector3::VEC_IJK, name); + guiRectangle->attachMesh(quad); + root->getGuiNode()->attachChild(guiRectangle); + + int typeArr[2]{(int)GuiElementType::GUI_RECTANGLE, -1}; + guiElements.push_back(make_pair(typeArr, (void*)guiRectangle)); + + return guiRectangle; + } + + //TODO distinguish between floats and vector-like tables for scale + Text* ConcreteGuiManager::parseText(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + sol::table posTable = guiTable["pos"]; + + Root *root = Root::getSingleton(); + + Material *mat = new Material(root->getLibPath() + "text"); + mat->addBoolUniform("texturingEnabled", false); + sol::table colorTable = guiTable["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["w"])); + + string font = guiTable["font"]; + wstring entry = guiTable["text"]; + Text *text = new Text(fontBasePath + font, entry, guiTable["fontFirstChar"], guiTable["fontLastChar"]); + text->setMaterial(mat); + + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + SOL_LUA_STATE.script("tp = type(gui[" + to_string(guiId + 1) + "].scale)"); + string st = SOL_LUA_STATE["tp"]; + Vector3 scale; + + if(st == "table"){ + sol::table scaleTable = guiTable["scale"]; + scale = Vector3(scaleTable["x"], scaleTable["y"], 1); + } + else if(st == "number"){ + float sc = guiTable["scale"]; + scale = Vector3(sc, sc, 1); + } + + Node *node = new Node(pos, Quaternion::QUAT_W, scale, guiTable["name"]); + node->addText(text); + root->getGuiNode()->attachChild(node); + + int typeArr[2]{(int)GuiElementType::TEXT, -1}; + guiElements.push_back(make_pair(typeArr, (void*)text)); + + return text; + } + + void ConcreteGuiManager::parseMusic(){ + sol::state_view SOL_STATE_VIEW = generateView(); + sol::optional musicTblOpt = SOL_STATE_VIEW["music"]; + + if(musicTblOpt == sol::nullopt) return; + + sol::table musicTbl = SOL_STATE_VIEW["music"], tracksTbl = musicTbl["tracks"]; + SoundManager *sm = SoundManager::getSingleton(); + + if(tracksTbl.size() == 0){ + sm->clearPlaylist(); + return; + } + + bool loop = musicTbl["loop"], shuffle = musicTbl["shuffle"]; + int delay = musicTbl["delay"].get_or(0); + int numTracks = tracksTbl.size(); + + vector trackPaths; + + for(int i = 0; i < numTracks; i++){ + string track = tracksTbl[i + 1]; + trackPaths.push_back(GameManager::getSingleton()->getPath() + "Sounds/Music/" + track); + } + + sm->play(trackPaths, 100, delay, loop, shuffle); + } + + void ConcreteGuiManager::readLuaScreenScript( + string script, + vector buttonExceptions, + vector listboxExceptions, + vector checkboxExceptions, + vector sliderExceptions, + vector textboxExceptions, + vector guiRectboxExceptions, + vector textExceptions, + string luaCode + ){ + removeAllGuiElements(buttonExceptions, listboxExceptions, checkboxExceptions, sliderExceptions, textboxExceptions, guiRectboxExceptions, textExceptions); + parseLuaScript(script, luaCode); + } + + void ConcreteGuiManager::readLuaScreenScriptDel( + string script, + vector buttons, + vector listboxs, + vector checkboxs, + vector sliders, + vector textboxs, + vector guiRectboxs, + vector texts + ){ + for(Button *b : buttons) removeButton(b); + for(Listbox *l : listboxs) removeListbox(l); + for(Checkbox *c : checkboxs) removeCheckbox(c); + for(Slider *s : sliders) removeSlider(s); + for(Textbox *t : textboxs) removeTextbox(t); + for(Node *r : guiRectboxs) removeGuiRectangle(r); + for(Text *t : texts) removeText(t); + + parseLuaScript(script); + } + + void ConcreteGuiManager::parseLuaScript(string script, string luaCode){ + guiElements.clear(); + + string basePath = GameManager::getSingleton()->getPath() + "Scripts/Gui/"; + sol::state_view SOL_LUA_VIEW = generateView(); + SOL_LUA_VIEW.script("music = nil"); + SOL_LUA_VIEW.script_file(basePath + script); + + if(luaCode != "") SOL_LUA_VIEW.script(luaCode); + + SOL_LUA_VIEW.script("numGui = #gui"); + int numGuiElements = SOL_LUA_VIEW["numGui"]; + + for(int i = 0; i < numGuiElements; i++){ + int guiTypeId = SOL_LUA_VIEW["gui"][i + 1]["guiType"]; + + switch((GuiElementType)guiTypeId){ + case BUTTON: + addButton(parseButton(i)); + break; + case LISTBOX: + addListbox(parseListbox(i)); + break; + case CHECKBOX: + addCheckbox(parseCheckbox(i)); + break; + case SLIDER: + addSlider(parseSlider(i)); + break; + case TEXTBOX: + addTextbox(parseTextbox(i)); + break; + case GUI_RECTANGLE: + addGuiRectangle(parseGuiRectangle(i)); + break; + case TEXT: + addText(parseText(i)); + break; + } + } + + parseMusic(); + } +} diff --git a/source/core/controllers/concreteGuiManager.h b/source/core/controllers/concreteGuiManager.h new file mode 100644 index 0000000..06ae6a2 --- /dev/null +++ b/source/core/controllers/concreteGuiManager.h @@ -0,0 +1,117 @@ +#ifndef CONCRETE_GUI_MANAGER_H +#define CONCRETE_GUI_MANAGER_H + +#include + +#include +#include +#include + +namespace vb01{ + class Node; + class Text; +} + +namespace battleship{ + enum GuiElementType {BUTTON, LISTBOX, CHECKBOX, SLIDER, TEXTBOX, GUI_RECTANGLE, TEXT, MUSIC}; + enum ButtonType { + SINGLE_PLAYER, + EDITOR, + OPTIONS, + EXIT, + OK, + DEFAULTS, + BACK, + CONTROLS_TAB, + MOUSE_TAB, + VIDEO_TAB, + AUDIO_TAB, + MULTIPLAYER_TAB, + NEW_MAP, + NEW_MAP_OK, + LOAD_MAP, + LOAD_MAP_OK, + EXPORT, + PLAY, + RESUME, + CONSOLE_SCREEN, + MAIN_MENU, + CONSOLE_COMMAND_OK, + BUILD, + TRAIN, + STATISTICS, + RESEARCH, + BUY_REFINEDS, + SELL_REFINEDS, + BUY_RESEARCH, + SELL_RESEARCH, + ACTIVE_STATE_BUTTON, + ACTIVE_STATE_BACK, + PLAYER_TRADE, + TRADING_SCREEN, + TRADE_OFFER, + RESOURCE_AMMOUNT, + UNIT_STATE, + ORDER, + MINIMAP, + }; + enum ListboxType { + CONTROLS, + RESOLUTION, + MAPS, + VEHICLES, + STRUCTURES, + RESOURCE_DEPOSITS, + SKYBOX_TEXTURES, + LAND_TEXTURES, + CPU_DIFFICULTIES, + FACTIONS, + COLORS, + TEAMS, + CONSOLE, + TRADE_OFFERS + }; + + class ConcreteGuiManager : public vb01Gui::AbstractGuiManager{ + public: + static ConcreteGuiManager* getSingleton(); + void readLuaScreenScript( + std::string, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::string = "" + ); + void readLuaScreenScriptDel( + std::string, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{} + ); + void parseLuaScript(std::string, std::string = ""); + private: + ConcreteGuiManager(); + vb01Gui::Button* parseButton(int); + vb01Gui::Listbox* parseGameObjectListbox(); + vb01Gui::Listbox* parseListbox(int); + vb01Gui::Checkbox* parseCheckbox(int); + vb01Gui::Slider* parseSlider(int); + vb01Gui::Textbox* parseTextbox(int); + vb01::Node* parseGuiRectangle(int); + vb01::Text* parseText(int); + void parseMusic(); + + std::vector> guiElements; + std::string texBasePath, fontBasePath; + }; +} + +#endif diff --git a/source/core/controllers/gameObjectFrameController.cpp b/source/core/controllers/gameObjectFrameController.cpp new file mode 100644 index 0000000..58f04cf --- /dev/null +++ b/source/core/controllers/gameObjectFrameController.cpp @@ -0,0 +1,296 @@ +#include + +#include "gameObjectFrameController.h" +#include "activeGameState.h" +#include "resourceDeposit.h" +#include "defConfigs.h" +#include "player.h" +#include "game.h" +#include "unit.h" +#include "util.h" +#include "map.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + static GameObjectFrameController *gameObjectFrameController = nullptr; + + GameObjectFrameController* GameObjectFrameController::getSingleton(){ + if(!gameObjectFrameController) + gameObjectFrameController = new GameObjectFrameController(); + + return gameObjectFrameController; + } + + void GameObjectFrameController::paintSelect(Vector3 rowEnd){ + Vector3 rowDir = rowEnd - paintSelectRowStart; + float lenRow = rowDir.getLength(); + float width = gameObjectFrames[0].getWidth(); + float length = gameObjectFrames[0].getLength(); + float hypothenuse = sqrt(width * width + length * length); + int numStructsInRow = int(lenRow / hypothenuse); + + while(gameObjectFrames.size() > numStructsInRow && gameObjectFrames.size() > 1) + removeGameObjectFrame(gameObjectFrames.size() - 1); + + if(gameObjectFrames.size() < numStructsInRow){ + int structureId = gameObjectFrames[0].getId(); + Vector3 buildDir = rowDir; + + if(gameObjectFrames.size() > 1) + buildDir = gameObjectFrames[1].getModel()->getPosition() - gameObjectFrames[0].getModel()->getPosition(); + + Vector3 pos = paintSelectRowStart + buildDir.norm() * hypothenuse * gameObjectFrames.size(); + addGameObjectFrame(GameObjectFrame(structureId, GameObject::Type::UNIT, gameObjectFrames[0].getPlayer(), nullptr, pos)); + } + } + + void GameObjectFrameController::snapToObj(GameObjectFrame &s, vector snapTargets, float maxDist){ + s.status = GameObjectFrame::BLOCKED_BY_DIFF_TERR; + + for(GameObject *st : snapTargets){ + Vector3 stPos = st->getPos(); + + if(s.getPos().getDistanceFrom(stPos) < maxDist){ + s.status = GameObjectFrame::PLACEABLE; + s.placeAt(stPos); + checkPlacement(s); + break; + } + } + } + + //TODO include checking if frame is outside of line of sight + void GameObjectFrameController::checkPlacement(GameObjectFrame &s){ + s.status = GameObjectFrame::PLACEABLE; + + Map *map = Map::getSingleton(); + Vector3 mapSize = map->getMapSize(); + Vector3 cellSize = map->getCellSize(); + int dirMult[][2]{{1, 1}, {1, -1}, {-1, 1}, {-1, -1}}; + vector &cells = map->getCells(); + + sol::table tbl = generateView()["units"][s.getId() + 1]; + UnitType ut = (UnitType)tbl["unitType"]; + + vector friendlyUnits = (s.getPlayer() ? s.getPlayer()->getFriendlyUnits() : vector{}); + bool withinLos = false; + + for(int i = 0; i < 4; i++){ + Vector3 cornerPos = (s.getPos() + s.getDirVec() * .5 * dirMult[i][0] * s.getLength() + s.getLeftVec() * .5 * dirMult[i][1] * s.getWidth()); + int cellId = map->getCellId(cornerPos, false); + + if(cellId < 0 || !(fabs(cornerPos.x) <= .5 * mapSize.x && fabs(cornerPos.z) <= .5 * mapSize.z)){ + s.status = GameObjectFrame::BLOCKED_BY_MAP_BOUNDS; + return; + } + + Map::Cell cell = cells[cellId]; + bool landUnitOnWater = (ut == UnitType::LAND && cell.type == Map::Cell::Type::WATER); + bool waterUnitOnLand = ((ut == UnitType::SEA_LEVEL || ut == UnitType::UNDERWATER) && cell.type == Map::Cell::Type::LAND); + bool withinCell = (fabs(cell.pos.x - cornerPos.x) < .5 * cellSize.x && fabs(cell.pos.z - cornerPos.z) < .5 * cellSize.z); + + if((landUnitOnWater || waterUnitOnLand) && withinCell){ + s.status = GameObjectFrame::BLOCKED_BY_DIFF_TERR; + return; + } + + if(!withinLos) + for(Unit *fu : friendlyUnits) + if(fu->getPos().getDistanceFrom(s.getPos()) < fu->getLineOfSight()){ + withinLos = true; + break; + } + } + + if(!withinLos){ + s.status = GameObjectFrame::BLOCKED_BY_FOG_OF_WAR; + return; + } + + MeshData meshData = map->getNodeParent()->getChild(0)->getMesh(0)->getMeshBase(); + MeshData::Vertex *verts = meshData.vertices; + int numVerts = 3 * meshData.numTris; + + if(s.getMaxUnevenness() > 0) + for(int i = 0; i < numVerts; i++){ + float diffX = fabs(s.getPos().x - verts[i].pos->x); + float diffY = fabs(s.getPos().y - verts[i].pos->y); + float diffZ = fabs(s.getPos().z - verts[i].pos->z); + + if(diffX < 0.5 * s.getWidth() && diffZ < 0.5 * s.getLength() && diffY > s.getMaxUnevenness()){ + s.status = GameObjectFrame::BLOCKED_BY_BUMPY_TERR; + return; + } + } + + vector units; + + for(Player *player : Game::getSingleton()->getPlayers(true)){ + vector u = player->getUnits(); + units.insert(units.end(), u.begin(), u.end()); + } + + for(Unit *unit : units){ + if(unit == s.getOriginalUnit()) continue; + + Vector3 gm1Pos = s.getPos(); + Vector3 gm1Dir = s.getDirVec(); + gm1Dir = Vector3(gm1Dir.x, 0, gm1Dir.z).norm(); + + Vector3 gm1Left = s.getLeftVec(); + gm1Left = Vector3(gm1Left.x, 0, gm1Left.z).norm(); + + Vector3 gm2Pos = unit->getPos(); + + Vector3 gm2Dir = unit->getDirVec(); + gm2Dir = Vector3(gm2Dir.x, 0, gm2Dir.z).norm(); + + Vector3 gm2Left = unit->getLeftVec(); + gm2Left = Vector3(gm2Left.x, 0, gm2Left.z).norm(); + + bool intersects = rectanglesIntersect( + Vector2(gm1Pos.x, gm1Pos.z), + Vector2(gm1Dir.x, gm1Dir.z), + Vector2(gm1Left.x, gm1Left.z), + Vector2(s.getWidth(), s.getLength()), + Vector2(gm2Pos.x, gm2Pos.z), + Vector2(gm2Dir.x, gm2Dir.z), + Vector2(gm2Left.x, gm2Left.z), + Vector2(unit->getWidth(), unit->getLength()) + ); + + if(intersects){ + s.status = GameObjectFrame::BLOCKED_BY_UNIT; + return; + } + } + } + + //TODO replace the magic number for ray casting height + void GameObjectFrameController::shiftVerticalPlacement(){ + if(!minDepthCalculated){ + Map *map = Map::getSingleton(); + Node *nodeParent = map->getNodeParent(); + Vector3 cellSize = map->getCellSize(), waterBodyPos; + bool inWater = false; + + for(int i = 1; i < nodeParent->getNumChildren(); i++){ + Vector3 wPos = nodeParent->getChild(i)->getPosition(); + Vector3 wSize = ((Quad*)nodeParent->getChild(i)->getMesh(0))->getSize(); + + if(fabs(wPos.x - placementPos.x) < .5 * wSize.x && fabs(wPos.z - placementPos.z) < .5 * wSize.y){ + waterBodyPos = wPos; + inWater = true; + break; + } + } + + if(!inWater) return; + + vector res = map->raycastTerrain( + Vector3(placementPos.x, 100, placementPos.z), + -Vector3::VEC_J, + false + ); + + vector &cells = map->getCells(); + int cid = map->getCellId(placementPos, false); + int numSubmarineCells = cells[cid].underWaterCellIds.size(); + maxDepth = cells[cid].pos.y; + minDepth = (numSubmarineCells > 0 ? cells[cells[cid].underWaterCellIds[numSubmarineCells - 1]].pos.y : maxDepth) - .5 * cellSize.y; + minDepthCalculated = true; + } + else{ + ActiveGameState *activeState = (ActiveGameState*)(GameManager::getSingleton()->getStateManager()->getAppStateByType((int)AppStateType::ACTIVE_STATE)); + float newDepth = minDepth + activeState->getDepth() * (maxDepth - minDepth); + placementPos.y = newDepth; + } + } + + void GameObjectFrameController::update(){ + Map *map = Map::getSingleton(); + Vector3 startPos = Root::getSingleton()->getCamera()->getPosition(); + Vector3 endPos = screenToSpace(getCursorPos()); + vector results = map->raycastTerrain(startPos, (screenToSpace(getCursorPos()) - startPos).norm(), true); + + if(results.empty()) return; + + if(paintSelecting) paintSelect(results[0].pos); + + if(placingVertically) shiftVerticalPlacement(); + else if(placingOnSurface) placementPos = results[0].pos; + + vector deposits; + for(ResourceDeposit *rd : Game::getSingleton()->getCivilianPlayer()->getResourceDeposits()) + deposits.push_back((GameObject*)rd); + + for(int i = 0; i < gameObjectFrames.size(); i++){ + gameObjectFrames[i].update(); + + if(paintSelecting){ + float width = gameObjectFrames[0].getWidth(); + float length = gameObjectFrames[0].getLength(); + float hypothenuse = sqrt(width * width + length * length); + Vector3 dir = (results[0].pos - paintSelectRowStart).norm(); + placementPos = paintSelectRowStart + dir * hypothenuse * i; + } + + if(!rotating && (placingOnSurface || placingVertically)) + gameObjectFrames[i].placeAt(placementPos); + + bool extractor = false; + + if(gameObjectFrames[i].getType() == GameObject::Type::UNIT){ + sol::table tbl = generateView()["units"][gameObjectFrames[i].getId() + 1]; + extractor = ((UnitClass)tbl["unitClass"] == UnitClass::EXTRACTOR); + } + + if(extractor) + snapToObj(gameObjectFrames[i], deposits, 20); + else + checkPlacement(gameObjectFrames[i]); + + bool placeable = (gameObjectFrames[i].status == GameObjectFrame::PLACEABLE); + Vector4 color = (placeable ? Vector4(0, 1, 0, 1) : Vector4(1, 0, 0, 1)); + gameObjectFrames[i].getModel()->getMaterial()->setVec4Uniform("diffuseColor", color); + } + + if(!placingVertically) minDepthCalculated = false; + } + + void GameObjectFrameController::addGameObjectFrame(GameObjectFrame u){ + gameObjectFrames.push_back(u); + vector results = Map::getSingleton()->raycastTerrain(u.getPos(), -Vector3::VEC_J, true); + + if(!results.empty()) placementPos = results[0].pos; + } + + void GameObjectFrameController::removeGameObjectFrame(int i){ + gameObjectFrames[i].destroy(); + gameObjectFrames.erase(gameObjectFrames.begin() + i); + } + + void GameObjectFrameController::removeGameObjectFrames(){ + while(gameObjectFrames.size() > 0) + removeGameObjectFrame(0); + } + + void GameObjectFrameController::rotateGameObjectFrames(float angle){ + for(GameObjectFrame &s : gameObjectFrames) + s.orientAt(Quaternion(angle, Vector3::VEC_J) * s.getRot()); + } +} diff --git a/source/core/controllers/gameObjectFrameController.h b/source/core/controllers/gameObjectFrameController.h new file mode 100644 index 0000000..5a6c217 --- /dev/null +++ b/source/core/controllers/gameObjectFrameController.h @@ -0,0 +1,55 @@ +#ifndef GAME_OBJECT_FRAME_CONTROLLER_H +#define GAME_OBJECT_FRAME_CONTROLLER_H + +#include +#include + +#include +#include + +#include "gameObjectFrame.h" + +namespace vb01{ + class Model; +} + +namespace battleship{ + class GameObjectFrameController{ + public: + static GameObjectFrameController* getSingleton(); + void update(); + void removeGameObjectFrame(int); + void removeGameObjectFrames(); + void rotateGameObjectFrames(float); + void checkPlacement(GameObjectFrame&); + void addGameObjectFrame(GameObjectFrame); + inline int getNumGameObjectFrames(){return gameObjectFrames.size();} + inline GameObjectFrame& getGameObjectFrame(int i){return gameObjectFrames[i];} + inline bool isPaintSelecting(){return paintSelecting;} + inline void setPaintSelecting(bool ps){paintSelecting = ps;} + inline bool isRotating(){return rotating;} + inline void setRotating(bool rs){rotating = rs;} + inline bool isPlacingOnSurface(){return placingOnSurface;} + inline void setPlacingOnSurface(bool ps){placingOnSurface = ps;} + inline bool isPlacingVertically(){return placingVertically;} + inline void setPlacingVertically(bool v){this->placingVertically = v;} + inline void setPaintSelectRowStart(vb01::Vector3 st){paintSelectRowStart = st;} + inline void toggleFrameTransformations(bool rot, bool pos, bool pv){ + setRotating(rot); + setPlacingOnSurface(pos); + setPlacingVertically(pv); + } + private: + GameObjectFrameController(){} + void paintSelect(vb01::Vector3); + void snapToObj(GameObjectFrame&, std::vector, float); + void shiftVerticalPlacement(); + + std::vector gameObjectFrames; + vb01::Vector3 paintSelectRowStart, placementPos; + float minDepth, maxDepth; + bool paintSelecting = false, rotating = false, placingOnSurface = false, placingVertically = false, minDepthCalculated = false; + }; +} + +#endif diff --git a/source/core/defConfigs.cpp b/source/core/defConfigs.cpp new file mode 100644 index 0000000..ec8fdff --- /dev/null +++ b/source/core/defConfigs.cpp @@ -0,0 +1,33 @@ +#include "defConfigs.h" + +namespace battleship{ + namespace configData{ + int calcSumStaticBinds(int id, bool calcPrev){ + int numBinds = 0; + + if(calcPrev) + for(int i = 0; i < id; i++) + numBinds += numStaticBinds[i]; + else + numBinds = numStaticBinds[id]; + + return numBinds; + } + + int calcSumConfBinds(int id, bool calcPrev){ + int numBinds = 0; + + if(calcPrev) + for(int i = 0; i < id; i++) + numBinds += numConfBinds[i]; + else + numBinds = numConfBinds[id]; + + return numBinds; + } + + int calcSumBinds(int id, bool calcPrev){ + return calcSumStaticBinds(id, calcPrev) + calcSumConfBinds(id, calcPrev); + } + } +} diff --git a/source/core/defConfigs.h b/source/core/defConfigs.h new file mode 100644 index 0000000..63906a5 --- /dev/null +++ b/source/core/defConfigs.h @@ -0,0 +1,214 @@ +#ifndef DEF_CONFIGS_H +#define DEF_CONFIGS_H +#define SOL_ALL_SAFETIES_ON 1 + +#include +#include + +#include + +#include + +#include +#include + +#include "binds.h" +#include "gameManager.h" + +namespace battleship{ + namespace configData{ + using namespace gameBase; + + const std::string DEFAULT_TEXTURE = "Textures/defaultTexture.jpg"; + const double camPanSpeed = .5, CAMERA_DISTANCE = 100, CAMERA_ZOOM_INCREMENT = 1, cellLength = 7, cellWidth = 7, cellDepth = 7, DIST_FROM_RAY = 15; + const int maxNumGroups = 10, NUM_MAX_ZOOMS = 300, NUM_SUBDIVS = 100; + const vb01::u32 IMPASS_NODE_VAL = 65535; + + const static int numAppStates = 5; + const static int numStaticBinds[numAppStates]{6, 0, 5, 19, 0}; + const static int numConfBinds[numAppStates]{0, 1, 27, 0, 0}; + const static int maxStaticBinds = 19; + const static int maxConfBinds = 23; + const static int numScripts = 5; + + const static std::string scriptPathBase = "Scripts/"; + enum ScriptFiles{ + CORE_MAIN, + GUI_MAIN, + OPTIONS, + RESOURCE_DATA, + PROJECTILE_DATA, + UNIT_DATA, + AI_AGENT, + PLAYER + }; + const static std::vector scripts = std::vector{ + "Scripts/Core/main.lua", + "Scripts/Gui/main.lua", + "Scripts/Core/options.lua", + "Scripts/GameObjects/resourceData.lua", + "Scripts/GameObjects/Projectiles/projectileData.lua", + "Scripts/GameObjects/Units/unitData.lua", + "Scripts/aiAgent.lua", + "Scripts/Core/player.lua", + "Scripts/Technologies/technologyData.lua", + "Scripts/Abilities/abilityData.lua", + "Scripts/Trading/traderData.lua" + }; + + const static Bind staticBinds[numAppStates][maxStaticBinds]{ + { + Bind::LEFT_CLICK, + Bind::SCROLLING_UP, + Bind::SCROLLING_DOWN, + Bind::LEFT, + Bind::RIGHT, + Bind::DELETE_CHAR, + }, + {}, + { + Bind::LOOK_UP, + Bind::LOOK_DOWN, + Bind::LOOK_LEFT, + Bind::LOOK_RIGHT, + Bind::LOOK_AROUND, + }, + { + Bind::LOOK_UP, + Bind::LOOK_DOWN, + Bind::LOOK_LEFT, + Bind::LOOK_RIGHT, + Bind::LOOK_AROUND, + }, + {} + }; + const static Bind confBinds[numAppStates][maxConfBinds]{ + {}, + { + Bind::TOGGLE_MAIN_MENU + }, + { + Bind::DRAG_BOX, + Bind::MOVE_CAMERA, + Bind::ROTATE_OBJ_FRAME, + Bind::HALT, + Bind::ZOOM_IN, + Bind::ZOOM_OUT, + Bind::LEFT_CONTROL, + Bind::LEFT_SHIFT, + Bind::SELECT_PATROL_POINTS, + Bind::LAUNCH, + Bind::SHIFT_SUB_DEPTH, + Bind::GROUP_0, + Bind::GROUP_1, + Bind::GROUP_2, + Bind::GROUP_3, + Bind::GROUP_4, + Bind::GROUP_5, + Bind::GROUP_6, + Bind::GROUP_7, + Bind::GROUP_8, + Bind::GROUP_9, + Bind::SELECT_STRUCTURE, + Bind::DESELECT_STRUCTURE + }, + {}, + {} + }; + + const static int staticTriggers[numAppStates][maxStaticBinds]{ + { + 0, + GLFW_KEY_W, + GLFW_KEY_S, + GLFW_KEY_LEFT, + GLFW_KEY_RIGHT, + GLFW_KEY_BACKSPACE + }, + {}, + { + Mapping::MOUSE_AXIS_UP, + Mapping::MOUSE_AXIS_DOWN, + Mapping::MOUSE_AXIS_LEFT, + Mapping::MOUSE_AXIS_RIGHT, + 0 + }, + { + Mapping::MOUSE_AXIS_UP, + Mapping::MOUSE_AXIS_DOWN, + Mapping::MOUSE_AXIS_LEFT, + Mapping::MOUSE_AXIS_RIGHT, + 0 + }, + {} + }; + const static int confTriggers[numAppStates][maxConfBinds]{ + {}, + { + GLFW_KEY_ESCAPE + }, + { + GLFW_KEY_LEFT, + GLFW_KEY_RIGHT, + GLFW_KEY_H, + 3, + 4, + GLFW_KEY_LEFT_CONTROL, + GLFW_KEY_LEFT_SHIFT, + GLFW_KEY_P, + GLFW_KEY_C, + GLFW_KEY_S, + GLFW_KEY_0, + GLFW_KEY_1, + GLFW_KEY_2, + GLFW_KEY_3, + GLFW_KEY_4, + GLFW_KEY_5, + GLFW_KEY_6, + GLFW_KEY_7, + GLFW_KEY_8, + GLFW_KEY_9, + GLFW_KEY_B, + GLFW_KEY_ESCAPE + }, + {}, + {} + }; + + const static bool isStaticKey[numAppStates][maxStaticBinds]{ + {0, 1, 1, 1, 1, 1}, + {}, + {0, 0, 0, 0, 1}, + {0, 0, 0, 0, 1}, + {} + }; + const static bool isConfKey[numAppStates][maxConfBinds]{ + {}, + {1}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {}, + {} + }; + + const static bool isStaticAction[numAppStates][maxStaticBinds]{ + {1, 1, 1, 1, 1, 1}, + {}, + {0, 0, 0, 0, 1}, + {0, 0, 0, 0, 1}, + {} + }; + const static bool isConfAction[numAppStates][maxConfBinds]{ + {}, + {1}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {}, + {} + }; + + int calcSumStaticBinds(int, bool); + int calcSumConfBinds(int, bool); + int calcSumBinds(int, bool); + } +} + +#endif diff --git a/source/core/game/game.cpp b/source/core/game/game.cpp new file mode 100644 index 0000000..25183bc --- /dev/null +++ b/source/core/game/game.cpp @@ -0,0 +1,247 @@ +#include "game.h" +#include "player.h" +#include "projectile.h" +#include "gameManager.h" +#include "activeGameState.h" +#include "inGameAppState.h" +#include "concreteGuiManager.h" +#include "fxManager.h" +#include "factory.h" +#include "defConfigs.h" + +#include + +#include +#include + +#include + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + static Game *game = nullptr; + + Game* Game::getSingleton(){ + if(!game) + game = new Game(); + + return game; + } + + void Game::endGame(bool victory){ + ended = true; + + StateManager *sm = GameManager::getSingleton()->getStateManager(); + sm->dettachAppState(sm->getAppStateByType(AppStateType::ACTIVE_STATE)); + + ConcreteGuiManager::getSingleton()->parseLuaScript(victory ? "victory.lua" : "defeat.lua"); + } + + void Game::update(){ + sol::state_view SOL_LUA_VIEW = generateView(); + + for(int i = 0; i < getCpuPlayers().size(); i++) { + string plStr = "game.cpuPlayers[" + to_string(i + 1) + "]"; + SOL_LUA_VIEW.script("executeBtNode(" + plStr + ", " + plStr + ".behaviour)"); + SOL_LUA_VIEW.collect_garbage(); + } + + int numPlayersWithUnits = 0; + + for(int i = 0; i < players.size(); i++){ + players[i]->update(); + + if(players[i]->getNumUnits() > 0) + numPlayersWithUnits++; + } + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + if(mainPlayer && !ended){ + int numMainPlayerUnits = mainPlayer->getNumUnits(); + + if(numMainPlayerUnits > 0 && numPlayersWithUnits == 1) + endGame(true); + else if(numMainPlayerUnits == 0) + endGame(false); + } + + FxManager::getSingleton()->update(); + } + + void Game::initLuaPlayers(){ + sol::state_view SOL_LUA_VIEW = generateView(); + vector cpuPlayers = getCpuPlayers(); + + for(int i = 0; i < cpuPlayers.size(); i++) + SOL_LUA_VIEW["game"]["cpuPlayers"][i + 1] = cpuPlayers[i]; + + SOL_LUA_VIEW.script_file(GameManager::getSingleton()->getPath() + "Scripts/Core/playerInit.lua"); + } + + void Game::removeAllElements(){ + generateView().script("game.cpuPlayers = {}"); + + while(!players.empty()){ + delete players[0]; + players.erase(players.begin()); + } + + ended = false; + paused = false; + } + + void Game::togglePause(){ + GameManager *gm = GameManager::getSingleton(); + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + ActiveGameState *activeState = ((InGameAppState*)gm->getStateManager()->getAppStateByType(AppStateType::IN_GAME_STATE))->getActiveState(); + + if (!paused) { + gm->getStateManager()->dettachAppState(activeState); + guiManager->parseLuaScript("gamePaused.lua"); + } + else { + guiManager->readLuaScreenScript("inGame.lua", activeState->getGuiButtons()); + + if(debug){ + sol::state_view SOL_LUA_VIEW = generateView(); + + for(string f : configData::scripts) + SOL_LUA_VIEW.script_file(gm->getPath() + f); + + initLuaPlayers(); + + vector gameObjs; + + for(Player *pl : Game::getSingleton()->getPlayers()){ + for(Unit *u : pl->getUnits()) + gameObjs.push_back((GameObject*)u); + + for(Projectile *proj : pl->getProjectiles()) + gameObjs.push_back((GameObject*)proj); + } + + for(ResourceDeposit *dep : civilianPlayer->getResourceDeposits()) + gameObjs.push_back((GameObject*)dep); + + string gop = SOL_LUA_VIEW["gameObjPrefix"], vfxp = SOL_LUA_VIEW["vfxPrefix"]; + AssetManager::getSingleton()->load(gm->getPath() + gop, true); + AssetManager::getSingleton()->load(gm->getPath() + vfxp, true); + + for(GameObject *obj : gameObjs) + obj->reinit(); + } + + gm->getStateManager()->attachAppState(activeState); + } + + paused = !paused; + } + + vector Game::parseTechTable(int tid, string key, string numVarKey, string varKey){ + sol::state_view SOL_LUA_VIEW = generateView(); + SOL_LUA_VIEW.script(numVarKey + " = #" + key + "[" + to_string(tid + 1) + "]." + varKey); + int numVar = SOL_LUA_VIEW[numVarKey]; + sol::table techTable = SOL_LUA_VIEW[key][tid + 1]; + + vector varVec; + + for(int i = 0; i < numVar; i++) + varVec.push_back(techTable[varKey][i + 1]); + + return varVec; + } + + //TODO clean this method up + void Game::initTechnologies(){ + technologies.clear(); + + sol::state_view SOL_LUA_VIEW = generateView(); + string techKey = "technologies"; + SOL_LUA_VIEW.script("numTechs = #" + techKey); + int numTechs = SOL_LUA_VIEW["numTechs"]; + + for(int i = 0; i < numTechs; i++){ + sol::table techTable = SOL_LUA_VIEW[techKey][i + 1]; + + Technology t; + t.cost = techTable["cost"]; + t.name = techTable["name"]; + t.icon = techTable["icon"]; + t.description = techTable["description"]; + t.parents = parseTechTable(i, techKey, "numParents", "parents"); + t.abilities = parseTechTable(i, techKey, "numAbilities", "abilities"); + + technologies.push_back(t); + } + + techKey = "abilities"; + SOL_LUA_VIEW.script("numAbilities = #" + techKey); + int numAbilities = SOL_LUA_VIEW["numAbilities"]; + + for(int i = 0; i < numAbilities; i++){ + sol::table techTable = SOL_LUA_VIEW[techKey][i + 1]; + + Ability ability; + ability.type = techTable["type"]; + ability.ammount = techTable["ammount"].get_or(0.0); + ability.gameObjType = techTable["gameObjType"]; + ability.gameObjIds = parseTechTable(i, techKey, "numGameObjIds", "gameObjIds"); + abilities.push_back(ability); + } + } + + float Game::calcAbilFromTech(Ability::Type type, vector techResearch, int gameObjType, int unitId){ + float ammount = 0; + + for(int techId : techResearch) + for(int abilId : technologies[techId].abilities) + if( + abilities[abilId].type == type && + abilities[abilId].gameObjType == gameObjType && + find(abilities[abilId].gameObjIds.begin(), abilities[abilId].gameObjIds.end(), unitId) != abilities[abilId].gameObjIds.end() + ){ + ammount += abilities[abilId].ammount; + } + + return ammount; + } + + bool Game::isUnitUnlocked(vector techResearch, int unitId){ + for(int techId : techResearch){ + for(int abilId : technologies[techId].abilities){ + vector ids = abilities[abilId].gameObjIds; + + if(find(ids.begin(), ids.end(), unitId) != ids.end()) + return true; + } + } + + return false; + } + + vector Game::getPlayers(bool civPl){ + vector playersVec = players; + + if(civPl) playersVec.push_back(civilianPlayer); + + return playersVec; + } + + vector Game::getCpuPlayers(){ + vector playersVec; + + for(Player *pl : players) + if(pl->isCpuPlayer()) + playersVec.push_back(pl); + + return playersVec; + } +} diff --git a/source/core/game/game.h b/source/core/game/game.h new file mode 100644 index 0000000..1661f3f --- /dev/null +++ b/source/core/game/game.h @@ -0,0 +1,53 @@ +#ifndef GAME_H +#define GAME_H + +#include "technology.h" +#include "ability.h" +#include "tradeOffer.h" + +#include + +namespace sf{ + class Sound; +} + +namespace battleship{ + class Unit; + class Player; + class Projectile; + + class Game{ + public: + static Game* getSingleton(); + void update(); + void initLuaPlayers(); + void togglePause(); + void removeAllElements(); + void initTechnologies(); + float calcAbilFromTech(Ability::Type, std::vector, int, int); + bool isUnitUnlocked(std::vector, int); + std::vector getPlayers(bool = false); + std::vector getCpuPlayers(); + inline void setDebug(bool d){this->debug = d;} + inline void addPlayer(Player *pl){players.push_back(pl);} + inline bool isDebug(){return debug;} + inline Player* getPlayer(int id){return players[id];} + inline void setCivilianPlayer(Player *pl){this->civilianPlayer = pl;} + inline Player* getCivilianPlayer(){return civilianPlayer;} + inline int getNumPlayers(){return players.size();} + inline Technology getTechnology(int id){return technologies[id];} + inline Ability getAbility(int id){return abilities[id];} + private: + Game(){} + void endGame(bool); + std::vector parseTechTable(int, std::string, std::string, std::string); + + bool paused = false, ended = false, debug = false; + std::vector technologies; + std::vector abilities; + std::vector players; + Player *civilianPlayer = nullptr; + }; +} + +#endif diff --git a/source/core/game/gameManager.cpp b/source/core/game/gameManager.cpp new file mode 100644 index 0000000..913ea76 --- /dev/null +++ b/source/core/game/gameManager.cpp @@ -0,0 +1,279 @@ +#include + +#include +#include +#include + +#include +#include + +#include "gameManager.h" +#include "gameObjectFrameController.h" +#include "gameObjectFactory.h" +#include "map.h" +#include "game.h" +#include "guiAppState.h" +#include "defConfigs.h" +#include "player.h" +#include "extractor.h" +#include "factory.h" +#include "vehicle.h" +#include "engineer.h" +#include "destructable.h" +#include "pointDefense.h" +#include "pathfinder.h" +#include "resourceDeposit.h" + +using namespace std; +using namespace vb01; +using namespace gameBase; + +namespace battleship{ + static GameManager *gameManager = nullptr; + + GameManager* GameManager::getSingleton(){ + if(!gameManager) + gameManager = new GameManager(); + + return gameManager; + } + + void GameManager::registerMembers(){ + sol::state_view SOL_LUA_STATE = generateView(); + + SOL_LUA_STATE.new_usertype( + "Order", sol::constructors, Vector3, int)>(), + "type", &Order::type, + "direction", &Order::direction, + "targets", &Order::targets + ); + + SOL_LUA_STATE.new_usertype( + "Target", sol::constructors(), + "unit", &Order::Target::unit, + "pos", &Order::Target::pos + ); + + SOL_LUA_STATE.new_usertype( + "GameObjectFactory", + "createUnit", &GameObjectFactory::createUnit, + "createResourceDeposit", &GameObjectFactory::createResourceDeposit + ); + + SOL_LUA_STATE.new_usertype( + "GarrisonSlot", + "vehicle", &Unit::GarrisonSlot::vehicle, + "category", &Unit::GarrisonSlot::category + ); + + SOL_LUA_STATE.new_usertype( + "Unit", sol::constructors(), + "setState", &Unit::setState, + "getDestructable", &Unit::getDestructable, + "getGarrisonSlots", &Unit::getGarrisonSlots, + "getNumFreeGarrisonSlots", &Unit::getNumFreeGarrisonSlots, + "getNumGarrisonSlots", &Unit::getNumGarrisonSlots, + "isGarrisonEmpty", &Unit::isGarrisonEmpty, + "getOrder", &Unit::getOrder, + "getNumOrders", &Unit::getNumOrders, + "getDirVec", &GameObject::getDirVec, + "getLeftVec", &GameObject::getLeftVec, + "getPos", &GameObject::getPos, + "getUnitClass", &Unit::getUnitClass, + "getLineOfSight", &Unit::getLineOfSight, + "getBuildableUnit", &Unit::getBuildableUnit, + "getBuildableUnits", &Unit::getBuildableUnits, + "getNumBuildableUnits", &Unit::getNumBuildableUnits, + "toGameObject", [](Unit *u){return (GameObject*)u;}, + "toVehicle", [](Unit *u){return (Vehicle*)u;}, + "toEngineer", [](Unit *u){return (Engineer*)u;}, + "toStructure", [](Unit *u){return (Structure*)u;}, + "toFactory", [](Unit *u){return (Factory*)u;}, + "toExtractor", [](Unit *u){return (Extractor*)u;}, + "toPointDefense", [](Unit *u){return (PointDefense*)u;} + ); + + SOL_LUA_STATE.new_usertype( + "Engineer", sol::constructors(), + "getGarrisonable", &Vehicle::getGarrisonable + ); + + SOL_LUA_STATE.new_usertype( + "Engineer", sol::constructors(), + "getBuildableUnit", &Unit::getBuildableUnit, + "getBuildableUnits", &Unit::getBuildableUnits, + "getNumBuildableUnits", &Unit::getNumBuildableUnits + ); + + SOL_LUA_STATE.new_usertype( + "Structure", sol::constructors(), + "getPos", &GameObject::getPos, + "isComplete", &Structure::isComplete, + "getBuildStatus", &Structure::getBuildStatus + ); + + SOL_LUA_STATE.new_usertype( + "BuildableUnit", sol::constructors(), + "id", &BuildableUnit::id, + "buildable", &BuildableUnit::buildable + ); + + SOL_LUA_STATE.new_usertype( + "Factory", sol::constructors(), + "getBuildableUnit", &Unit::getBuildableUnit, + "getBuildableUnits", &Unit::getBuildableUnits, + "getNumBuildableUnits", &Unit::getNumBuildableUnits, + "getDirVec", &GameObject::getDirVec, + "getLeftVec", &GameObject::getLeftVec, + "getPos", &GameObject::getPos, + "appendToQueue", &Factory::appendToQueue, + "getBuildStatus", &Structure::getBuildStatus, + "getNumQueueUnitsById", &Factory::getNumQueueUnitsById, + "getRallyPoint", &Factory::getRallyPoint, + "setRallyPoint", &Factory::setRallyPoint, + "getQueue", &Factory::getQueue + ); + + SOL_LUA_STATE.new_usertype( + "Player", sol::constructors(), + "getSelectedUnits", &Player::getSelectedUnits, + "haltUnits", &Player::haltUnits, + "addUnit", &Player::addUnit, + "getUnit", &Player::getUnit, + "getNumUnits", &Player::getNumUnits, + "getResourceDeposits", &Player::getResourceDeposits, + "issueOrder", &Player::issueOrder, + "selectUnits", &Player::selectUnits, + "deselectUnits", &Player::deselectUnits, + "getSpawnPointId", &Player::getSpawnPointId, + "getFaction", &Player::getFaction, + "getTeam", &Player::getTeam, + "getUnits", &Player::getUnits, + "getHostileUnits", &Player::getHostileUnits, + "getFriendlyUnits", &Player::getFriendlyUnits, + "getUnitsById", &Player::getUnitsById, + "getUnitsByClass", &Player::getUnitsByClass, + "isObjectVisible", &Player::isObjectVisible + ); + + SOL_LUA_STATE.new_usertype( + "Extractor", sol::constructors(), + "getDeposit", &Extractor::getDeposit + ); + + SOL_LUA_STATE.new_usertype( + "ResourceDeposit", sol::constructors(), + "getExtractor", &ResourceDeposit::getExtractor, + "getAmmount", &ResourceDeposit::getAmmount, + "getPos", &GameObject::getPos, + "toGameObject", [](ResourceDeposit *rd){return (GameObject*)rd;} + ); + + SOL_LUA_STATE.new_usertype( + "Game", + "getSingleton", &Game::getSingleton, + "getPlayers", &Game::getPlayers + ); + + SOL_LUA_STATE.new_usertype( + "Vector3", sol::constructors(), + "x", &Vector3::x, + "y", &Vector3::y, + "z", &Vector3::z, + "norm", &Vector3::norm, + "getAngleBetween", &Vector3::getAngleBetween, + "getDistanceFrom", &Vector3::getDistanceFrom, + "neg", [](Vector3 v){return -v;}, + "add", [](Vector3 v1, Vector3 v2){return v1 + v2;}, + "subtr", [](Vector3 v1, Vector3 v2){return v1 - v2;}, + "mult", [](Vector3 v1, float s){return v1 * s;}, + "div", [](Vector3 v1, float s){return v1 / s;} + ); + + SOL_LUA_STATE.new_usertype( + "Quaternion", sol::constructors(), + "w", &Quaternion::w, + "x", &Quaternion::x, + "y", &Quaternion::y, + "z", &Quaternion::z, + "getAngle", &Quaternion::getAngle, + "getAxis", &Quaternion::getAxis, + "multVec", [](Quaternion q, Vector3 v){return q * v;} + ); + + SOL_LUA_STATE.new_usertype( + "Cell", + "pos", &Map::Cell::pos, + "type", &Map::Cell::type + ); + + SOL_LUA_STATE.new_usertype( + "Map", + "getSingleton", &Map::getSingleton, + "getCell", &Map::getCell, + "getCells", &Map::getCells, + "getCellId", &Map::getCellId, + "getMapSize", &Map::getMapSize, + "getSpawnPoint", &Map::getSpawnPoint, + "getNumSpawnPoints", &Map::getNumSpawnPoints + ); + + SOL_LUA_STATE.new_usertype( + "Pathfinder", + "getSingleton", &Pathfinder::getSingleton, + "calcHeuristics", &Pathfinder::calcHeuristics, + "findPath", &Pathfinder::findPath + ); + + SOL_LUA_STATE.new_usertype( + "GameObjectFrame", sol::constructors(), + "getOriginalUnit", &GameObjectFrame::getOriginalUnit, + "destroy", &GameObjectFrame::destroy, + "placeAt", &GameObjectFrame::placeAt, + "getDirVec", &GameObjectFrame::getDirVec, + "getLeftVec", &GameObjectFrame::getLeftVec, + "getLength", &GameObjectFrame::getLength, + "getWidth", &GameObjectFrame::getWidth, + "status", &GameObjectFrame::status + ); + + SOL_LUA_STATE.new_usertype( + "GameObjectFrameController", + "getSingleton", &GameObjectFrameController::getSingleton, + "checkPlacement", &GameObjectFrameController::checkPlacement + ); + } + + void GameManager::initLua(string gameDir){ + sol::state_view SOL_LUA_STATE = generateView(); + + path = gameDir + "Assets/"; + SOL_LUA_STATE.script("PATH = \"" + path + "\";"); + + for(string f : configData::scripts) + SOL_LUA_STATE.script_file(path + f); + + sol::table resTable = SOL_LUA_STATE["graphics"]["resolution"]; + width = resTable["x"]; + height = resTable["y"]; + } + + void GameManager::start(string gameDir) { + running = true; + registerMembers(); + initLua(gameDir); + + Root *root = Root::getSingleton(); + root->start(width, height, path + "../external/vb01/", "Battleship"); + + stateManager = new StateManager(); + inputManager = new InputManager(stateManager, root->getWindow()); + } + + void GameManager::update() { + Root::getSingleton()->update(); + SoundManager::getSingleton()->update(); + inputManager->update(); + stateManager->update(); + } +} diff --git a/source/core/game/gameManager.h b/source/core/game/gameManager.h new file mode 100644 index 0000000..471abca --- /dev/null +++ b/source/core/game/gameManager.h @@ -0,0 +1,46 @@ +#pragma once +#ifndef GAME_MANAGER_H +#define GAME_MANAGER_H + +#include "util.h" + +#include + +#include +#include + +namespace gameBase{ + class InputManager; + class StateManager; +} + +namespace battleship{ + class GameManager { + public: + static GameManager* getSingleton(); + void start(std::string); + void update(); + inline int getWidth(){return width;} + inline int getHeight(){return height;} + inline std::string getPath(){return path;} + inline gameBase::InputManager* getInputManager(){return inputManager;} + inline bool isServerSide(){return serverSide;} + inline bool isRunning(){return running;} + inline void setRunning(bool r){this->running = r;} + inline gameBase::StateManager* getStateManager(){return stateManager;} + private: + GameManager(){} + ~GameManager(){} + void registerMembers(); + void initLua(std::string); + + gameBase::StateManager *stateManager = nullptr; + gameBase::InputManager *inputManager = nullptr; + int width, height; + std::string path = ""; + bool serverSide, running = false; + }; + +} + +#endif diff --git a/source/core/main.cpp b/source/core/main.cpp new file mode 100644 index 0000000..0a01ef0 --- /dev/null +++ b/source/core/main.cpp @@ -0,0 +1,38 @@ +#include "util.h" +#include "gameManager.h" +#include "guiAppState.h" +#include "concreteGuiManager.h" + +#include + +#include +#include + +#include + +using namespace battleship; +using namespace vb01; +using namespace std; + +int main(int argc, char **argv) { + string gamePath = string(argv[0]); + + for(int i = 0; i < gamePath.length(); i++) + if(gamePath[i] == '\\') + gamePath[i] = '/'; + + gamePath = gamePath.substr(0, gamePath.find_last_of("/") + 1) + "../"; + + GameManager *gm = GameManager::getSingleton(); + gm->start(gamePath); + gm->getStateManager()->attachAppState(new GuiAppState()); + AssetManager::getSingleton()->load(gm->getPath() + "Fonts/batang.ttf"); + + ConcreteGuiManager::getSingleton()->readLuaScreenScript("mainMenu.lua"); + + while(gm->isRunning()){ + gm->update(); + } + + return 0; +} diff --git a/source/gameplay/environment/environment.cpp b/source/gameplay/environment/environment.cpp new file mode 100644 index 0000000..28480cb --- /dev/null +++ b/source/gameplay/environment/environment.cpp @@ -0,0 +1,50 @@ +#include "environment.h" +#include "fxManager.h" +#include "player.h" +#include "unit.h" +#include "game.h" +#include "destructable.h" + +#include +#include +#include +#include +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + + static Environment *environment = nullptr; + + Environment* Environment::getSingleton(){ + if(!environment) + environment = new Environment(); + + return environment; + } + + //TODO use the pos argument from the fx + void Environment::explode(FxManager::Fx *fx, Detonation det, Vector3 pos, int damage, float radius){ + FxManager::getSingleton()->addFx(fx); + + if(radius == 0) return; + + for(Player *pl : Game::getSingleton()->getPlayers()){ + for(Unit *un : pl->getUnits()){ + float distance = un->getPos().getDistanceFrom(pos); + + if(distance < radius){ + switch(det){ + case Detonation::EXPLOSION: + un->getDestructable()->takeDamage(int(damage * (1.f - distance / radius))); + break; + case Detonation::EMP: + un->setCondition(Unit::Condition::EM_JAMMED); + break; + } + } + } + } + } +} diff --git a/source/gameplay/environment/environment.h b/source/gameplay/environment/environment.h new file mode 100644 index 0000000..8e3bcaf --- /dev/null +++ b/source/gameplay/environment/environment.h @@ -0,0 +1,20 @@ +#ifndef ENVIRONMENT_H +#define ENVIRONMENT_H + +#include + +#include "fxManager.h" + +namespace battleship{ + class Environment{ + public: + enum class Detonation{EXPLOSION, EMP, CRYO}; + + static Environment* getSingleton(); + static void explode(FxManager::Fx*, Detonation, vb01::Vector3, int = 0, float = 0); + private: + Environment(){} + }; +} + +#endif diff --git a/source/gameplay/environment/explosion.cpp b/source/gameplay/environment/explosion.cpp new file mode 100644 index 0000000..53647be --- /dev/null +++ b/source/gameplay/environment/explosion.cpp @@ -0,0 +1,50 @@ +#include + +#include "explosion.h" +#include "util.h" +#include "inGameAppState.h" +#include "game.h" + +using namespace std; +using namespace vb01; + +namespace battleship{ + void detonate(string p){ + sf::SoundBuffer *sfxBuffer = new sf::SoundBuffer(); + sf::Sound *sfx = nullptr; + string p = GameManager::getSingleton()->getPath() + "Sounds/Explosions/explosion0" + to_string(rand() % 4) + ".ogg"; + + if(sfxBuffer->loadFromFile(p.c_str())){ + sfx = new sf::Sound(*sfxBuffer); + sfx->play(); + } + + Game::getSingleton()->addFx(Fx(sfx, 2500)); + } + + void detonateDepthCharge(){ + sf::SoundBuffer *sfxBuffer = new sf::SoundBuffer(); + sf::Sound *sfx = nullptr; + string p = GameManager::getSingleton()->getPath() + "Sounds/Destroyers/depthCharge.ogg"; + + if(sfxBuffer->loadFromFile(p.c_str())){ + sfx = new sf::Sound(*sfxBuffer); + sfx->play(); + } + + Game::getSingleton()->addFx(Fx(sfx, 2000)); + } + + void detonateTorpedo(){ + sf::SoundBuffer *sfxBuffer = new sf::SoundBuffer(); + sf::Sound *sfx = nullptr; + string p = GameManager::getSingleton()->getPath() + "Sounds/Submarines/torpedo.ogg"; + + if(sfxBuffer->loadFromFile(p.c_str())){ + sfx = new sf::Sound(*sfxBuffer); + sfx->play(); + } + + Game::getSingleton()->addFx(Fx(sfx, 250)); + } +} diff --git a/source/gameplay/environment/explosion.h b/source/gameplay/environment/explosion.h new file mode 100644 index 0000000..7dd9684 --- /dev/null +++ b/source/gameplay/environment/explosion.h @@ -0,0 +1,10 @@ +#ifndef EXPLOSION_H +#define EXPLOSION_H + +#include + +namespace battleship{ + void detonate(std::string); +} + +#endif diff --git a/source/gameplay/environment/fxManager.cpp b/source/gameplay/environment/fxManager.cpp new file mode 100644 index 0000000..859d297 --- /dev/null +++ b/source/gameplay/environment/fxManager.cpp @@ -0,0 +1,232 @@ +#include "fxManager.h" +#include "gameObject.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + + FxManager::Fx::Component::Component(void *c, bool v, s64 dur, vb01::Vector3 p, s64 ot) : comp(c), vfx(v), duration(dur), pos(p), offsetTime(ot) { + if(v) ((vb01::Node*)c)->setVisible(false); + } + + FxManager::Fx::Fx(std::vector comps, bool re) : components(comps), reuse(re){ + toggleComponents(!re); + } + + FxManager::Fx* FxManager::initFx(sol::table fxTbl, Node *baseNode, bool attached, Vector3 cp){ + int numComponents = fxTbl.size(); + + if(numComponents == 0) return nullptr; + + vector fxComponents; + + for(int i = 0; i < numComponents; i++){ + sol::table compTbl = fxTbl[i + 1]; + + bool vfx = compTbl["vfx"]; + s64 duration = compTbl["duration"], offset = compTbl["offset"].get_or(0); + + if(vfx){ + sol::table meshTbl = compTbl["mesh"]; + + string meshPath = ""; + Material *mat = nullptr; + Node *flashNode = nullptr; + + sol::optional posOpt = compTbl["pos"], rotOpt = compTbl["rot"]; + Vector3 compPos = cp; + Quaternion compRot = Quaternion::QUAT_W; + float sc = compTbl["scale"].get_or(1); + + if(posOpt != sol::nullopt){ + sol::table posTable = compTbl["pos"]; + compPos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + } + + if(rotOpt != sol::nullopt){ + sol::table rotTable = compTbl["rot"]; + compRot = Quaternion(rotTable["w"], rotTable["x"], rotTable["y"], rotTable["z"]); + } + + sol::optional pathOpt = meshTbl["path"]; + sol::optional numPartOpt = meshTbl["numParticles"]; + + if(pathOpt != sol::nullopt){ + mat = new Material(Root::getSingleton()->getLibPath() + "texture"); + + meshPath = meshTbl["path"]; + flashNode = new Model(meshPath); + ((Model*)flashNode)->setMaterial(mat); + flashNode->setPosition(compPos); + flashNode->setOrientation(compRot); + flashNode->setScale(Vector3(sc, sc, sc)); + } + else if(numPartOpt != sol::nullopt){ + mat = new Material(Root::getSingleton()->getLibPath() + "particle"); + + int numParticles = meshTbl["numParticles"]; + ParticleEmitter *pe = new ParticleEmitter(numParticles); + pe->setMaterial(mat); + pe->setLowLife(meshTbl["lowLife"]); + pe->setHighLife(meshTbl["highLife"]); + pe->setSpeed(0); + + sol::table sizeTbl = meshTbl["size"]; + pe->setSize(Vector2(sizeTbl["x"], sizeTbl["y"])); + + flashNode = new Node(compPos + Vector3(0, 2, 0), compRot, Vector3(sc, sc, sc)); + flashNode->attachParticleEmitter(pe); + flashNode->lookAt(Vector3::VEC_J, Vector3::VEC_K); + } + else{ + mat = new Material(Root::getSingleton()->getLibPath() + "texture"); + + sol::table sizeTbl = meshTbl["size"]; + Box *box = new Box(Vector3(sizeTbl["x"], sizeTbl["y"], 0)); + box->setMaterial(mat); + + flashNode = new Node(compPos, compRot, Vector3(sc, sc, sc), "laser"); + flashNode->attachMesh(box); + } + + sol::optional texOpt = meshTbl["texture"]; + + if(texOpt != sol::nullopt){ + string p[]{meshTbl["texture"]}; + Texture *tex = new Texture(p, 1, false); + mat->addBoolUniform("texturingEnabled", true); + mat->addTexUniform("diffuseMap[0]", tex, false); + } + else{ + sol::table colorTable = meshTbl["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["a"])); + mat->addBoolUniform("texturingEnabled", false); + } + + flashNode->setVisible(false); + Node *parNode = (attached ? baseNode : Root::getSingleton()->getRootNode()); + sol::optional parNameOpt = compTbl["parent"]; + + if(parNameOpt != sol::nullopt){ + string parName = compTbl["parent"]; + parNode = baseNode->findDescendant(parName, true); + } + + parNode->attachChild(flashNode); + fxComponents.push_back(FxManager::Fx::Component((void*)flashNode, vfx, duration, compPos, offset)); + } + else{ + sf::SoundBuffer *sfxBuffer = new sf::SoundBuffer(); + sf::Sound *sfx = GameObject::prepareSfx(sfxBuffer, compTbl["path"]); + fxComponents.push_back(FxManager::Fx::Component((void*)sfx, vfx, duration, Vector3::VEC_ZERO, offset)); + } + } + + return new FxManager::Fx(fxComponents, attached); + } + + void FxManager::Fx::toggleComponents(bool active){ + for(Component &component : components){ + component.active = active; + component.initTime = getTime(); + } + } + + static FxManager *fxManager = nullptr; + + FxManager* FxManager::getSingleton(){ + if(!fxManager) + fxManager = new FxManager(); + + return fxManager; + } + + void FxManager::update(){ + for(int i = 0; i < fxs.size(); i++){ + s64 currTime = getTime(); + Fx *fx = fxs[i]; + + for(int j = 0; j < fx->components.size(); j++){ + Fx::Component &comp = fx->components[j]; + s64 currTime = getTime(); + + if(comp.comp && currTime - comp.initTime > comp.offsetTime){ + if(comp.active){ + if(comp.vfx) + ((Node*)comp.comp)->setVisible(true); + else + ((sf::Sound*)comp.comp)->play(); + + comp.active = false; + comp.initTime = getTime(); + } + else if(!comp.active && currTime - comp.initTime > comp.duration){ + if(fx->reuse){ + if(comp.vfx) + ((Node*)comp.comp)->setVisible(false); + else + ((sf::Sound*)comp.comp)->stop(); + } + else{ + destroyFxComponent(i, j); + fx->components.erase(fx->components.begin() + j); + j--; + } + } + } + + } + + if(fx->components.empty()){ + delete fx; + fxs.erase(fxs.begin() + i); + i--; + } + } + } + + void FxManager::destroyFxComponent(int fid, int cid){ + Fx *fx = fxs[fid]; + + if(fx->components[cid].vfx){ + Node *vfxNode = (Node*)fx->components[cid].comp, *parNode = vfxNode->getParent(); + parNode->dettachChild(vfxNode); + delete vfxNode; + } + else{ + sf::Sound *sfx = (sf::Sound*)fx->components[cid].comp; + const sf::SoundBuffer *buffer = &sfx->getBuffer(); + sfx->stop(); + + delete sfx; + delete buffer; + } + + fx->components[cid].comp = nullptr; + } + + void FxManager::removeFx(Fx *fx){ + int id = -1; + + for(int i = 0; i < fxs.size(); i++) + if(fxs[i] == fx){ + id = i; + break; + } + + for(int i = 0; i < fx->components.size(); i++) + destroyFxComponent(id, i); + + fxs.erase(fxs.begin() + id); + delete fx; + } +} diff --git a/source/gameplay/environment/fxManager.h b/source/gameplay/environment/fxManager.h new file mode 100644 index 0000000..64090b6 --- /dev/null +++ b/source/gameplay/environment/fxManager.h @@ -0,0 +1,44 @@ +#ifndef FX_MANAGER_H +#define FX_MANAGER_H + +#include + +#include + +namespace vb01{ + class Node; +} + +namespace battleship{ + class FxManager{ + public: + struct Fx { + struct Component{ + vb01::s64 duration, initTime = 0, offsetTime = 0; + vb01::Vector3 pos = vb01::Vector3::VEC_ZERO; + bool vfx, active = false; + void *comp = nullptr; + + Component(void*, bool, vb01::s64, vb01::Vector3 = vb01::Vector3::VEC_ZERO, vb01::s64 = 0); + }; + + bool reuse; + std::vector components; + + Fx(std::vector, bool = false); + void toggleComponents(bool); + }; + + static FxManager* getSingleton(); + FxManager::Fx* initFx(sol::table, vb01::Node*, bool, vb01::Vector3 = vb01::Vector3::VEC_ZERO); + void update(); + void removeFx(Fx*); + inline void addFx(Fx *fx){fxs.push_back(fx);} + private: + FxManager(){} + void destroyFxComponent(int, int); + std::vector fxs; + }; +} + +#endif diff --git a/source/gameplay/environment/map.cpp b/source/gameplay/environment/map.cpp new file mode 100644 index 0000000..414ca15 --- /dev/null +++ b/source/gameplay/environment/map.cpp @@ -0,0 +1,754 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "map.h" +#include "util.h" +#include "vehicle.h" +#include "game.h" +#include "player.h" +#include "gameObject.h" +#include "pathfinder.h" +#include "gameManager.h" +#include "defConfigs.h" +#include "resourceDeposit.h" +#include "activeGameState.h" +#include "concreteGuiManager.h" +#include "gameObjectFactory.h" + + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + using namespace configData; + + static Map *map = nullptr; + static Map::Minimap *minimap = nullptr; + + Map::Minimap* Map::Minimap::getSingleton(){ + if(!minimap) minimap = new Map::Minimap(); + + return minimap; + } + + Map::Minimap::Minimap(){ + sol::state_view SOL_STATE_VIEW = generateView(); + SOL_STATE_VIEW.script_file(GameManager::getSingleton()->getPath() + "Scripts/Gui/activeGameState.lua"); + + string refIconFile = SOL_STATE_VIEW["refIcon"]; + string basePath = GameManager::getSingleton()->getPath() + "Textures/Icons/Minimap/"; + + for(ResourceDeposit *rd : Game::getSingleton()->getCivilianPlayer()->getResourceDeposits()) + depositIcons.push_back(initIcon(rd->getPos(), basePath + refIconFile)); + + string camIconFile = SOL_STATE_VIEW["eyeIcon"]; + camIcon = initIcon(Root::getSingleton()->getCamera()->getPosition(), basePath + camIconFile); + } + + Map::Minimap::~Minimap(){ + Node *guiNode = Root::getSingleton()->getGuiNode(); + + for(Node *node : depositIcons){ + guiNode->dettachChild(node); + delete node; + } + + depositIcons.clear(); + + guiNode->dettachChild(camIcon); + delete camIcon; + } + + Node* Map::Minimap::initIcon(Vector3 posOnMap, string iconPath){ + string p[]{iconPath}; + ImageAsset *asset = (ImageAsset*)AssetManager::getSingleton()->getAsset(iconPath); + Vector3 iconSize = Vector3(asset->width, asset->height, 0); + Texture *tex = new Texture(p, 1, false); + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->addBoolUniform("texturingEnabled", true); + mat->addTexUniform("diffuseMap", tex, false); + + sol::state_view SOL_LUA_VIEW = generateView(); + sol::table posTbl = SOL_LUA_VIEW["minimapPos"], sizeTbl = SOL_LUA_VIEW["minimapSize"]; + Vector2 minimapSize = Vector2(sizeTbl["x"], sizeTbl["y"]); + Vector3 minimapPos = Vector3(posTbl["x"], posTbl["y"], posTbl["z"]); + + Vector3 mapSize = Map::getSingleton()->getMapSize(); + Vector2 iconPos = Vector2( + minimapSize.x * (posOnMap.x + .5 * mapSize.x) / mapSize.x, + minimapSize.y * (posOnMap.z + .5 * mapSize.z) / mapSize.z + ); + + Quad *quad = new Quad(iconSize, false); + quad->setMaterial(mat); + + Node *node = new Node(minimapPos + Vector3(iconPos.x, iconPos.y, .1) - .5 * iconSize); + node->attachMesh(quad); + node->setVisible(false); + root->getGuiNode()->attachChild(node); + + return node; + } + + //TODO add a flag to Player::getUnits* whether to include garrisoned units + void Map::Minimap::updateImage(){ + GameManager *gm = GameManager::getSingleton(); + Map *map = Map::getSingleton(); + + string imagePath = gm->getPath() + "Models/Maps/" + map->getMapName() + "/minimap.jpg"; + ImageAsset *asset = (ImageAsset*)AssetManager::getSingleton()->getAsset(imagePath); + int width = asset->width, height = asset->height; + + Vector3 mapSize = map->getMapSize(); + vector> unitMinimapPos; + + ActiveGameState *activeState = (ActiveGameState*)gm->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + + for(Player *pl : Game::getSingleton()->getPlayers(true)) + if(pl->getTeam() == activeState->getPlayer()->getTeam()){ + vector units = pl->getUnits(); + + for(Unit *u : units){ + if(u->isVehicle() && ((Vehicle*)u)->getGarrisonable()) continue; + + Vector2 coords = Vector2( + int(u->getPos().x / mapSize.x * width), + int(-u->getPos().z / mapSize.z * height) + ); + unitMinimapPos.push_back(make_pair(u, coords)); + } + } + + int pxId = 0, numChannels = asset->numChannels, size = width * height * numChannels; + float losFactor = .6; + + for(u8 *p = asset->image; p != asset->image + size; p += numChannels, pxId += numChannels){ + *(p + 0) = losFactor * oldImageData[pxId + 0]; + *(p + 1) = losFactor * oldImageData[pxId + 1]; + *(p + 2) = losFactor * oldImageData[pxId + 2]; + } + + int unitPxRadius = 1; + + for(pair unitPair : unitMinimapPos){ + Unit *losUnit = unitPair.first; + Vector2 losUnitCoords = unitPair.second; + float minimapLos = losUnit->getLineOfSight() / mapSize.x * width; + + for(int x = max(-.5f * width, losUnitCoords.x - minimapLos); x < min(.5f * width, losUnitCoords.x + minimapLos); x++){ + for(int y = max(-.5f * height, losUnitCoords.y - minimapLos); y < min(.5f * height, losUnitCoords.y + minimapLos); y++){ + int pxId = width * (y + .5 * height) + (x + .5 * width); + Vector2 coords = Vector2(x, y); + + if(coords.getDistanceFrom(losUnitCoords) < minimapLos){ + asset->image[numChannels * pxId + 0] = oldImageData[numChannels * pxId + 0]; + asset->image[numChannels * pxId + 1] = oldImageData[numChannels * pxId + 1]; + asset->image[numChannels * pxId + 2] = oldImageData[numChannels * pxId + 2]; + + for(pair addUnitPair : unitMinimapPos) + if(fabs(addUnitPair.second.x - coords.x) < unitPxRadius && fabs(addUnitPair.second.y - coords.y) < unitPxRadius){ + Vector3 unitCol = addUnitPair.first->getPlayer()->getColor(); + asset->image[numChannels * pxId + 0] = unitCol.x * 255; + asset->image[numChannels * pxId + 1] = unitCol.y * 255; + asset->image[numChannels * pxId + 2] = unitCol.z * 255; + + break; + } + } + } + } + } + + Node *rectNode = ConcreteGuiManager::getSingleton()->getButton("minimap")->getRectNode(); + Material *mat = rectNode->getMesh(0)->getMaterial(); + Texture *tex = ((Material::TextureUniform*)mat->getUniform("diffuseMap"))->value; + tex->loadImageData(asset, false); + } + + void Map::Minimap::updateCamFrame(Button *minimapButton){ + Vector3 camPos = Root::getSingleton()->getCamera()->getPosition(); + Vector3 mapSize = Map::getSingleton()->getMapSize(); + Vector2 minimapSize = minimapButton->getSize(); + Vector2 iconPos = Vector2( + minimapSize.x * (camPos.x + .5 * mapSize.x) / mapSize.x, + minimapSize.y * (camPos.z + .5 * mapSize.z) / mapSize.z + ); + + camIcon->setPosition(minimapButton->getPos() + Vector3(iconPos.x, iconPos.y, .1)); + } + + void Map::Minimap::update(){ + if(!GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE)) return; + + Button *mb = ConcreteGuiManager::getSingleton()->getButton("minimap"); + updateCamFrame(mb); + } + + void Map::Minimap::load(){ + for(Node *node : depositIcons) + node->setVisible(true); + + AssetManager *am = AssetManager::getSingleton(); + string minimapPath = GameManager::getSingleton()->getPath() + "Models/Maps/" + Map::getSingleton()->getMapName() + "/minimap.jpg"; + + am->load(minimapPath); + ImageAsset *asset = (ImageAsset*)am->getAsset(minimapPath); + int imgSize = asset->width * asset->height * asset->numChannels; + oldImageData = new u8[imgSize]; + + for(int i = 0; i < imgSize; i++) + oldImageData[i] = asset->image[i]; + } + + void Map::Minimap::unload(){ + for(Node *node : depositIcons) + node->setVisible(false); + + delete[] oldImageData; + } + + Map* Map::getSingleton(){ + if(!map) map = new Map; + + return map; + } + + vector Map::generateAdjacentNodeEdges(int numVertCells, int i, int numHorCells, int j, int weight){ + vector edges; + bool checkUp = (i > 0), checkRight = (j < numHorCells - 1), checkDown = (i < numVertCells - 1), checkLeft = (j > 0); + + if(checkLeft) + edges.push_back(Map::Edge(weight, numHorCells * i + j, numHorCells * i + j - 1)); + + if(checkRight) + edges.push_back(Map::Edge(weight, numHorCells * i + j, numHorCells * i + j + 1)); + + if(checkUp) + edges.push_back(Map::Edge(weight, numHorCells * i + j, numHorCells * (i - 1) + j)); + + if(checkDown) + edges.push_back(Map::Edge(weight, numHorCells * i + j, numHorCells * (i + 1) + j)); + + if(checkUp && checkLeft) + edges.push_back(Map::Edge(int(sqrt(2 * weight * weight)), numHorCells * i + j, numHorCells * (i - 1) + j - 1)); + + if(checkUp && checkRight) + edges.push_back(Map::Edge(int(sqrt(2 * weight * weight)), numHorCells * i + j, numHorCells * (i - 1) + j + 1)); + + if(checkDown && checkLeft) + edges.push_back(Map::Edge(int(sqrt(2 * weight * weight)), numHorCells * i + j, numHorCells * (i + 1) + j - 1)); + + if(checkDown && checkRight) + edges.push_back(Map::Edge(int(sqrt(2 * weight * weight)), numHorCells * i + j, numHorCells * (i + 1) + j + 1)); + + return edges; + } + + void Map::update(){ + Minimap::getSingleton()->update(); + } + + void Map::loadSkybox(){ + sol::state_view SOL_LUA_STATE = generateView(); + string skyboxName = SOL_LUA_STATE[mapTable]["skybox"]; + string basePath = GameManager::getSingleton()->getPath() + "Textures/Skyboxes/" + skyboxName; + + const int numPaths = 6; + string path[numPaths] = { + basePath + "/left.jpg", + basePath + "/right.jpg", + basePath + "/up.jpg", + basePath + "/down.jpg", + basePath + "/front.jpg", + basePath + "/back.jpg" + }; + + for(int i = 0; i < numPaths; i++) + AssetManager::getSingleton()->load(path[i]); + + Root::getSingleton()->createSkybox(path); + } + + void Map::loadLights(){ + sol::state_view SOL_LUA_VIEW = generateView(); + sol::table lightsTbl = SOL_LUA_VIEW[mapTable]["lights"]; + int numLights = lightsTbl.size(); + + for(int i = 0; i < numLights; i++){ + int id = i + 1; + Light::Type type = (Light::Type)lightsTbl[id]["type"]; + sol::table colTbl = lightsTbl[id]["color"]; + + Light *light = new Light(type); + light->setColor(Vector3(colTbl["x"], colTbl["y"], colTbl["z"])); + + Node *lightNode = new Node(); + lights.push_back(lightNode); + lightNode->addLight(light); + + if(type == Light::Type::DIRECTIONAL){ + sol::table dirTbl = lightsTbl[id]["dir"]; + Vector3 dir = Vector3(dirTbl["x"], dirTbl["y"], dirTbl["z"]).norm(); + lightNode->lookAt(dir); + } + + Root::getSingleton()->getRootNode()->attachChild(lightNode); + } + } + + void Map::loadTerrainObject(int id){ + string texPath = "", albedoPath = ""; + Quad *quad = nullptr; + Node *node = nullptr; + sol::state_view SOL_LUA_STATE = generateView(); + + if(id == -1){ + string basePath = GameManager::getSingleton()->getPath() + "Models/Maps/" + mapName + "/"; + + albedoPath = SOL_LUA_STATE[mapTable]["terrain"]["albedo"]; + texPath = basePath + albedoPath; + + string modelPath = SOL_LUA_STATE[mapTable]["terrain"]["model"]; + string terrainFile = basePath + modelPath; + + AssetManager::getSingleton()->load(terrainFile); + node = (Model*)((new Model(terrainFile))->getChild(0)); + + Node *par = node->getParent(); + par->dettachChild(node); + delete par; + } + else{ + sol::table waterBodyTable = SOL_LUA_STATE[mapTable]["waterbodies"][id + 1], posTable = waterBodyTable["pos"]; + albedoPath = waterBodyTable["albedo"]; + texPath = GameManager::getSingleton()->getPath() + "Textures/Water/" + albedoPath; + + quad = new Quad(Vector3(waterBodyTable["size"]["x"], waterBodyTable["size"]["y"], 1), true); + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + node = new Node(pos); + node->attachMesh(quad); + } + + terrainNode->attachChild(node); + + Material *mat = new Material(Root::getSingleton()->getLibPath() + "texture"); + mat->addBoolUniform("texturingEnabled", true); + mat->addBoolUniform("lightingEnabled", true); + mat->addBoolUniform("constLightingEnabled", true); + mat->addBoolUniform("normalMapEnabled", false); + mat->addBoolUniform("specularMapEnabled", false); + mat->addBoolUniform("castShadow", false); + + string fr[]{texPath}; + AssetManager::getSingleton()->load(fr[0]); + Texture *t = new Texture(fr, 1, false); + + mat->addTexUniform("textures[0]", t, true); + + if(id == -1) + ((Model*)node)->setMaterial(mat); + else{ + mat->setTransparent(true); + quad->setMaterial(mat); + } + } + + int Map::getNumMapSpawnPoints(string name){ + if(name != "") + generateView().script_file(GameManager::getSingleton()->getPath() + "Models/Maps/" + name + "/" + name + ".lua"); + + sol::table spawnPointsTbl = generateView()["metadata"]["spawnPoints"]; + return spawnPointsTbl.size(); + } + + //TODO improve for underwater cells + vector Map::getSurroundingCells(Vector3 p, int numRings){ + int numHorCells = int(mapSize.x / CELL_SIZE.x); + int numVertCells = int(mapSize.z / CELL_SIZE.z); + int horId = int((.5 * mapSize.x + p.x) / CELL_SIZE.x); + int vertId = int((.5 * mapSize.z + p.z) / CELL_SIZE.z); + + vector cellIds; + + for(int i = max(vertId - numRings, 0); i <= min(vertId + numRings, numVertCells - 1); i++) + for(int j = max(horId - numRings, 0); j <= min(horId + numRings, numHorCells - 1); j++) + cellIds.push_back(numHorCells * i + j); + + return cellIds; + } + + void Map::blockCells(Unit *unit){ + vector surroundingCellIds = getSurroundingCells(unit->getPos(), 0); + + for(int scid : surroundingCellIds){ + if(cells[scid].blockedBy && cells[scid].blockedBy != unit) continue; + + bool within = unit->pointWithinObj(cells[scid].pos); + cells[scid].blockedBy = (within ? unit : nullptr); + } + } + + void Map::unblockCells(Unit *unit){ + vector surroundingCellIds = getSurroundingCells(unit->getPos(), 0); + + for(int scid : surroundingCellIds) + if(cells[scid].blockedBy == unit) + cells[scid].blockedBy = nullptr; + } + + void Map::loadSpawnPoints(){ + sol::state_view SOL_LUA_VIEW = generateView(); + int numSpawnPoints = getNumMapSpawnPoints(); + + for(int i = 0; i < numSpawnPoints; i++){ + sol::table posTable = SOL_LUA_VIEW[mapTable]["spawnPoints"][i + 1]; + spawnPoints.push_back(Vector3(posTable["x"], posTable["y"], posTable["z"])); + } + } + + void Map::loadPlayerGameObjects(Player *player, sol::table playerTbl){ + sol::state_view SOL_LUA_VIEW = generateView(); + + string resDepInd = "resourceDeposits"; + sol::optional resDepTblOpt = playerTbl[resDepInd]; + + if(resDepTblOpt != sol::nullopt){ + sol::table resDepTbl = playerTbl[resDepInd]; + int numNpcObjs = resDepTbl.size(); + + for(int j = 0; j < numNpcObjs; j++){ + sol::table npcObjTable = resDepTbl[j + 1]; + int id = npcObjTable["id"]; + + sol::table posTable = npcObjTable["pos"]; + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + sol::table rotTable = npcObjTable["rot"]; + Quaternion rot = Quaternion(rotTable["w"], rotTable["x"], rotTable["y"], rotTable["z"]); + int initAmmount = resDepTbl[j + 1]["initAmmount"]; + + player->addResourceDeposit(GameObjectFactory::createResourceDeposit(player, id, pos, rot, initAmmount)); + } + } + + string unitInd = "units"; + sol::optional unitsTblOpt = playerTbl[unitInd]; + + if(unitsTblOpt != sol::nullopt){ + sol::table unitsTbl = playerTbl[unitInd]; + int numUnits = unitsTbl.size(); + + for(int j = 0; j < numUnits; j++){ + sol::table unitTable = unitsTbl[j + 1]; + + string posInd = "pos"; + Vector3 pos = Vector3(unitTable[posInd]["x"], unitTable[posInd]["y"], unitTable[posInd]["z"]); + + string rotInd = "rot"; + Quaternion rot = Quaternion(unitTable[rotInd]["w"], unitTable[rotInd]["x"], unitTable[rotInd]["y"], unitTable[rotInd]["z"]); + + int id = unitTable["id"]; + int buildStatus = unitTable["buildStatus"].get_or(0); + player->addUnit(GameObjectFactory::createUnit(player, id, pos, rot, buildStatus)); + } + } + } + + //TODO move minimap loading elsewhere + void Map::loadPlayersGameObjects(){ + Game *game = Game::getSingleton(); + vector choosablePlayers = game->getPlayers(false); + sol::state_view SOL_LUA_VIEW = generateView(); + + for(int i = 0; i < choosablePlayers.size(); i++){ + sol::table playerTbl = SOL_LUA_VIEW[mapTable]["choosablePlayers"][i + 1]; + loadPlayerGameObjects(choosablePlayers[i], playerTbl); + } + + sol::table civPlayerTbl = SOL_LUA_VIEW[mapTable]["civilianPlayer"]; + loadPlayerGameObjects(game->getCivilianPlayer(), civPlayerTbl); + + Minimap::getSingleton()->load(); + } + + void Map::preprareScene(bool empty){ + Game::getSingleton()->setCivilianPlayer(new Player(0, 0, 0, .5 * Vector3::VEC_IJK)); + + Root *root = Root::getSingleton(); + Node *rootNode = root->getRootNode(); + string libPath = root->getLibPath(); + terrainNode = new Node(); + rootNode->attachChild(terrainNode); + + cellNode = new Node(); + cellNode->setVisible(false); + rootNode->attachChild(cellNode); + landCellMat = new Material(libPath + "texture"); + landCellMat->addBoolUniform("lightingEnabled", false); + landCellMat->addBoolUniform("texturingEnabled", false); + landCellMat->addVec4Uniform("diffuseColor", Vector4(0, 1, 0, 1)); + waterCellMat = new Material(libPath + "texture"); + waterCellMat->addBoolUniform("lightingEnabled", false); + waterCellMat->addBoolUniform("texturingEnabled", false); + waterCellMat->addVec4Uniform("diffuseColor", Vector4(0, 0, 1, 1)); + + Camera *cam = root->getCamera(); + cam->setFarPlane(600); + cam->setPosition(Vector3(1, 1, 1) * configData::CAMERA_DISTANCE); + cam->lookAt(Vector3(-1, -1, -1).norm(), Vector3(-1, 1, -1).norm()); + + if(empty){ + Light *light = new Light(Light::Type::AMBIENT); + light->setColor(Vector3::VEC_IJK * .9); + + Node *node = new Node(); + node->addLight(light); + Root::getSingleton()->getRootNode()->attachChild(node); + lights.push_back(node); + } + } + + //TODO implement toggleable cell rendering + void Map::loadCells(){ + sol::state_view SOL_LUA_VIEW = generateView(); + sol::table cellsTable = SOL_LUA_VIEW["cells"]; + int numCells = cellsTable.size(); + + for(int i = 0; i < numCells; i++){ + sol::table cellTable = cellsTable[i + 1], posTable = cellTable["pos"]; + int numEdges = cellTable["numEdges"]; + vector edges; + + for(int j = 0; j < numEdges; j++){ + sol::table edgeTable = cellTable["edges"][j + 1]; + edges.push_back(Edge(edgeTable["weight"], edgeTable["srcCellId"], edgeTable["destCellId"])); + } + + int numUnderWaterCells = cellTable["numUnderWaterCells"]; + vector underWaterCellIds; + + for(int j = 0; j < numUnderWaterCells; j++) + underWaterCellIds.push_back((int)cellTable["underWaterCellId"][j + 1]); + + Vector3 cellPos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + Cell::Type cellType = (Cell::Type)cellTable["type"]; + + /* + Quad *quad = new Quad(Vector3(CELL_SIZE.x, CELL_SIZE.z, 0)); + quad->setWireframe(true); + quad->setMaterial(cellType == Cell::Type::LAND ? landCellMat : waterCellMat); + + Node *node = new Node(cellPos + Vector3::VEC_J * .1); + node->attachMesh(quad); + */ + + cells.push_back(Cell(cellPos, cellType, edges, underWaterCellIds)); + + } + } + + void Map::load(string mapName){ + this->mapName = mapName; + + Pathfinder::getSingleton()->setImpassibleNodeVal(u32(0 - 1)); + string path = GameManager::getSingleton()->getPath(); + AssetManager::getSingleton()->load(path + "Textures/", true); + + sol::state_view SOL_LUA_STATE = generateView(); + SOL_LUA_STATE.script_file(path + "Models/Maps/" + mapName + "/" + mapName + ".lua"); + SOL_LUA_STATE.script_file(path + "Models/Maps/" + mapName + "/cells.lua"); + + preprareScene(false); + loadSpawnPoints(); + loadSkybox(); + loadCells(); + loadTerrainObject(-1); + + sol::optional lightsOpt = SOL_LUA_STATE[mapTable]["lights"]; + + if(lightsOpt != sol::nullopt) + loadLights(); + + sol::table sizeTable = SOL_LUA_STATE[mapTable]["size"]; + mapSize = Vector3(sizeTable["x"], sizeTable["y"], sizeTable["z"]); + sol::table wbTbl = SOL_LUA_STATE[mapTable]["waterbodies"]; + int numWaterbodies = wbTbl.size(); + + for(int i = 0; i < numWaterbodies; i++) + loadTerrainObject(i); + } + + void Map::create(string mapName, Vector3 mapSize){ + this->mapName = mapName; + this->mapSize = mapSize; + + //addSpawnPoint(Vector3::VEC_ZERO); + preprareScene(true); + } + + //TODO unload skybox assets + void Map::unloadSkybox(){ + Root::getSingleton()->removeSkybox(); + } + + void Map::unloadLights(){ + while(!lights.empty()){ + Root::getSingleton()->getRootNode()->dettachChild(lights[0]); + delete lights[0]; + lights.erase(lights.begin()); + } + } + + void Map::unloadCells(){ + while(cellNode->getNumChildren() > 0){ + Node *node = cellNode->getChild(0); + node->getMesh(0)->setMaterial(nullptr); + cellNode->dettachChild(node); + delete node; + } + + delete landCellMat; + delete waterCellMat; + + cells.clear(); + } + + //TODO unload terrain assets + void Map::unloadTerrainObjects(){ + while(terrainNode->getNumChildren() > 0){ + Node *node = terrainNode->getChild(0); + terrainNode->dettachChild(node); + delete node; + } + } + + void Map::unloadPlayerObjects(){ + for(Player *pl : Game::getSingleton()->getPlayers(true)){ + while(pl->getNumUnits() > 0){ + pl->removeUnit(0); + } + + while(pl->getNumResourceDeposits() > 0){ + pl->removeResourceDeposit(0); + } + } + } + + void Map::destroyScene(){ + Node *rootNode = Root::getSingleton()->getRootNode(); + rootNode->dettachChild(terrainNode); + rootNode->dettachChild(cellNode); + + delete terrainNode; + delete cellNode; + } + + void Map::unload(){ + Minimap::getSingleton()->unload(); + + unloadPlayerObjects(); + unloadCells(); + unloadTerrainObjects(); + destroyScene(); + unloadLights(); + unloadSkybox(); + spawnPoints.clear(); + } + + vector Map::raycastTerrain(Vector3 rayPos, Vector3 rayDir, bool bothTerrTypes){ + vector allResults = RayCaster::cast(rayPos, rayDir, terrainNode->getChild(0), 0, configData::DIST_FROM_RAY); + + if(bothTerrTypes){ + vector waterNodes = terrainNode->getChildren(); + waterNodes.erase(waterNodes.begin()); + + vector waterResults = RayCaster::cast(rayPos, rayDir, waterNodes); + allResults.insert(allResults.end(), waterResults.begin(), waterResults.end()); + + RayCaster::sortResults(allResults); + } + + return allResults; + } + + template int Map::bsearch(vector haystack, T needle, float eps){ + T haystackMidVal; + bool sizeHaystackEven = (haystack.size() % 2 == 0); + int midValId = haystack.size() / 2; + + if(sizeHaystackEven) + haystackMidVal = (haystack[midValId - 1] + haystack[midValId]) / 2; + else + haystackMidVal = haystack[midValId]; + + int beginId, endId; + + if(fabs(haystackMidVal - needle) > eps){ + if(fabs(haystackMidVal - needle) < eps){ + beginId = 0; + endId = (midValId - 1); + } + else{ + endId = haystack.size() - 1; + endId = (midValId + 1); + } + + return bsearch(vector(haystack.begin() + beginId, haystack.begin() + endId), needle); + } + else{ + if(sizeHaystackEven) + return midValId - (needle > haystackMidVal ? 0 : 1); + else + return midValId; + } + } + + //TODO replace search with binary search + //TODO optimize horizontal and vertical id calculcation + int Map::getCellId(Vector3 pos, bool checkUnderwaterCells){ + int numHorCells = int(mapSize.x / CELL_SIZE.x), horId = -1; + + for(int i = 0; i < numHorCells; i++) + if(fabs(cells[i].pos.x - pos.x) <= .5 * CELL_SIZE.x){ + horId = i; + break; + } + + int numVertCells = int(mapSize.z / CELL_SIZE.z), vertId = -1; + + for(int i = 0; i < numVertCells; i++) + if(fabs(cells[i * numHorCells].pos.z - pos.z) <= .5 * CELL_SIZE.z){ + vertId = i; + break; + } + + int surfaceCellId = vertId * numHorCells + horId; + + if(checkUnderwaterCells && cells[surfaceCellId].type == Cell::Type::WATER && !cells[surfaceCellId].underWaterCellIds.empty()){ + int cellId = surfaceCellId; + + for(int i = 0; i <= cells[surfaceCellId].underWaterCellIds.size(); i++){ + cellId = (i == 0 ? surfaceCellId : cells[surfaceCellId].underWaterCellIds[i - 1]); + + if(fabs(cells[cellId].pos.y - pos.y) < .5 * CELL_SIZE.y) + return cellId; + } + + return surfaceCellId; + } + else return surfaceCellId; + } +} diff --git a/source/gameplay/environment/map.h b/source/gameplay/environment/map.h new file mode 100644 index 0000000..63d7f31 --- /dev/null +++ b/source/gameplay/environment/map.h @@ -0,0 +1,128 @@ +#ifndef MAP_H +#define MAP_H + +#include +#include + +#include +#include +#include + +#include + +namespace vb01{ + class Model; + class Material; + class Node; +} + +namespace vb01Gui{ + class Button; +} + +namespace battleship{ + class Player; + class Unit; + class ResourceDeposit; + struct Cell; + + class Map { + public: + struct Cell; + + struct Edge{ + Edge(vb01::s64 w, vb01::s64 src, vb01::s64 dest) : weight(w), srcCellId(src), destCellId(dest){} + vb01::s64 weight, srcCellId, destCellId; + }; + + struct Cell{ + enum Type{LAND, WATER}; + + Type type; + vb01::Vector3 pos; + std::vector edges; + Unit *blockedBy = nullptr; + std::vector underWaterCellIds; + + Cell(){} + Cell(vb01::Vector3 p, Type t, std::vector e = std::vector{}, std::vector uc = std::vector{}): pos(p), type(t), edges(e), underWaterCellIds(uc){} + }; + + class Minimap{ + public: + static Minimap* getSingleton(); + ~Minimap(); + void update(); + void updateImage(); + void load(); + void unload(); + inline vb01::u8* getOldMinimapImage(){return oldImageData;} + private: + Minimap(); + vb01::Node* initIcon(vb01::Vector3, std::string); + void updateCamFrame(vb01Gui::Button*); + + vb01::u8 *oldImageData = nullptr; + vb01::Node *camFrame = nullptr; + std::vector depositIcons; + vb01::Node* camIcon = nullptr; + }; + + static Map* getSingleton(); + ~Map(){} + static std::vector generateAdjacentNodeEdges(int, int, int, int, int); + void update(); + void load(std::string); + void create(std::string, vb01::Vector3); + void unload(); + std::vector raycastTerrain(vb01::Vector3, vb01::Vector3, bool); + int getCellId(vb01::Vector3, bool = true); + bool isPointWithinTerrainObject(vb01::Vector3, int); + void loadPlayersGameObjects(); + int getNumMapSpawnPoints(std::string = ""); + std::vector getSurroundingCells(vb01::Vector3, int); + void blockCells(Unit*); + void unblockCells(Unit*); + inline Map::Cell getCell(int i){return cells[i];} + inline std::string getMapName(){return mapName;} + inline vb01::Node* getNodeParent(){return terrainNode;} + inline vb01::Vector3 getCellSize(){return CELL_SIZE;} + inline int getNumSpawnPoints(){return spawnPoints.size();} + inline vb01::Vector3 getSpawnPoint(int i){return spawnPoints[i];} + inline void setMapSize(vb01::Vector3 s){this->mapSize = s;} + inline vb01::Vector3 getMapSize(){return mapSize;} + inline void addSpawnPoint(vb01::Vector3 sp){spawnPoints.push_back(sp);} + inline std::vector& getCells(){return cells;} + inline vb01::Node* getLight(int i){return lights[i];} + inline std::vector getLights(){return lights;} + inline float getBaseHeight(){return baseHeight;} + inline void setBaseHeight(float bh){this->baseHeight = bh;} + private: + std::string mapTable = "metadata"; + vb01::Node *terrainNode = nullptr, *cellNode = nullptr; + vb01::Material *landCellMat = nullptr, *waterCellMat = nullptr; + std::string mapName; + vb01::Vector3 CELL_SIZE = vb01::Vector3(7, 7, 7), mapSize; + std::vector spawnPoints; + std::vector cells; + float baseHeight; + std::vector lights; + + Map(){} + void preprareScene(bool); + void loadSpawnPoints(); + void loadLights(); + void loadSkybox(); + void loadCells(); + void loadPlayerGameObjects(Player*, sol::table); + void loadTerrainObject(int); + void unloadTerrainObjects(); + void unloadCells(); + void unloadLights(); + void unloadSkybox(); + void unloadPlayerObjects(); + void destroyScene(); + template int bsearch(std::vector, T, float); + }; +} +#endif diff --git a/source/gameplay/gameObjects/destructable.cpp b/source/gameplay/gameObjects/destructable.cpp new file mode 100644 index 0000000..cf22dbb --- /dev/null +++ b/source/gameplay/gameObjects/destructable.cpp @@ -0,0 +1,107 @@ +#include "destructable.h" +#include "environment.h" +#include "player.h" +#include "game.h" + +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace vb01; +using namespace gameBase; + +namespace battleship{ + Destructable::Destructable(GameObject *obj) : gameObject(obj){ + initProperties(); + } + + void Destructable::initProperties(){ + int id = gameObject->getId(); + sol::state_view SOL_LUA_VIEW = generateView(); + string objType = gameObject->getGameObjTableName(); + sol::table objTable = SOL_LUA_VIEW[objType][gameObject->getId() + 1]; + + vector currTechs = gameObject->getPlayer()->getTechnologies(); + health += Game::getSingleton()->calcAbilFromTech(Ability::Type::HEALTH, currTechs, (int)gameObject->getType(), id); + maxHealth = objTable["health"]; + + if(health == 0) health = maxHealth; + + string tblName = "armor"; + sol::optional at = objTable[tblName]; + + if(at != sol::nullopt){ + string varName = "numArmorTypes"; + SOL_LUA_VIEW.script(varName + " = #" + objType + "[" + to_string(id + 1) + "]." + tblName); + int numArmorTypes = SOL_LUA_VIEW[varName]; + + for(int i = 0; i < numArmorTypes; i++){ + Armor arm = (Armor)objTable[tblName][i + 1]; + armorTypes.push_back(arm); + } + } + } + + void Destructable::update(){ + if (health <= DEATH_HP){ + gameObject->setRemove(true); + + sol::optional tblOpt = generateView()[gameObject->getGameObjTableName()][gameObject->getId() + 1]["deathFx"]; + + if(tblOpt == sol::nullopt) return; + + Vector3 pos = gameObject->getPos(); + sol::table tbl = generateView()[gameObject->getGameObjTableName()][gameObject->getId() + 1]["deathFx"]; + FxManager::Fx *fx = FxManager::getSingleton()->initFx(tbl, gameObject->getModel(), false, pos); + Environment::explode(fx, Environment::Detonation::EXPLOSION, pos); + } + } + + Node* Destructable::createBar(Vector2 pos, Vector2 size, Vector4 color){ + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->addBoolUniform("texturingEnabled", false); + mat->addVec4Uniform("diffuseColor", color); + + Quad *quad = new Quad(Vector3(size.x, size.y, 0), false); + quad->setMaterial(mat); + + Node *node = new Node(Vector3(pos.x, pos.y, 0)); + node->attachMesh(quad); + node->setVisible(false); + root->getGuiNode()->attachChild(node); + + return node; + } + + void Destructable::displayStats(Node *foreground, Node *background, int currVal, int maxVal, bool render, Vector2 offset) { + foreground->setVisible(render); + background->setVisible(render); + + if(render){ + Vector3 offset3d = Vector3(offset.x, offset.y, 0); + Vector2 screenPos = gameObject->getScreenPos(); + + Quad *bgQuad = (Quad*)background->getMesh(0); + Vector3 size = bgQuad->getSize(); + float shiftedX = screenPos.x - 0.5 * size.x; + background->setPosition(Vector3(shiftedX, screenPos.y, 0) + offset3d); + + Quad *fgQuad = (Quad*)foreground->getMesh(0); + fgQuad->setSize(Vector3((float)currVal / maxVal * size.x, size.y, 0)); + fgQuad->updateVerts(fgQuad->getMeshBase()); + foreground->setPosition(Vector3(shiftedX, screenPos.y, .01) + offset3d); + } + } + + void Destructable::removeBar(Node *node){ + Root::getSingleton()->getGuiNode()->dettachChild(node); + delete node; + } +} diff --git a/source/gameplay/gameObjects/destructable.h b/source/gameplay/gameObjects/destructable.h new file mode 100644 index 0000000..083b56e --- /dev/null +++ b/source/gameplay/gameObjects/destructable.h @@ -0,0 +1,43 @@ +#ifndef DESTRUCTABLE_H +#define DESTRUCTABLE_H + +#include + +#include +#include + +namespace vb01{ + class Node; +} + +namespace battleship{ + enum class Armor {CAST, COMBINED, MECHANIC, SHELL, STEEL}; + + class GameObject; + + class Destructable{ + public: + Destructable(GameObject*); + void update(); + void removeBar(vb01::Node*); + vb01::Node* createBar(vb01::Vector2, vb01::Vector2, vb01::Vector4); + void displayStats(vb01::Node*, vb01::Node*, int, int, bool, vb01::Vector2 offset = vb01::Vector2::VEC_ZERO); + inline int getHealth(){return health;} + inline int getMaxHealth(){return maxHealth;} + inline int getDeathHp(){return DEATH_HP;} + inline void takeDamage(int damage) {health -= damage * (1 + (freezeDmgFactor - 1) * freezeStatus * .01f);} + inline GameObject* getGameObject(){return gameObject;} + inline std::vector getArmorTypes(){return armorTypes;} + inline void setFreezeStatus(int fs){this->freezeStatus = std::clamp(fs, 0, 100);} + inline int getFreezeStatus(){return freezeStatus;} + private: + std::vector armorTypes; + const int DEATH_HP = 0; + int health = 0, maxHealth, freezeStatus = 0, freezeDmgFactor = 10; + GameObject *gameObject = nullptr; + + void initProperties(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/gameObject.cpp b/source/gameplay/gameObjects/gameObject.cpp new file mode 100644 index 0000000..db193f1 --- /dev/null +++ b/source/gameplay/gameObjects/gameObject.cpp @@ -0,0 +1,226 @@ +#include "gameObject.h" +#include "gameManager.h" +#include "destructable.h" +#include "defConfigs.h" +#include "player.h" +#include "game.h" +#include "unit.h" + +#include + +#include +#include + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + using namespace configData; + + GameObject::GameObject(Type t, int i, Player *pl, vb01::Vector3 vec, vb01::Quaternion quat) : type(t), id(i), player(pl), pos(vec), rot(quat){} + + void GameObject::reinit(){ + placeAt(pos); + orientAt(rot); + + if(hitbox){ + Box *hbMesh = (Box*)hitbox->getMesh(0); + hbMesh->setSize(Vector3(width, height, length)); + hbMesh->updateVerts(hbMesh->getMeshBase()); + hitbox->setVisible(Game::getSingleton()->isDebug()); + } + } + + void GameObject::update(){ + leftVec = model->getGlobalAxis(0); + upVec = model->getGlobalAxis(1); + dirVec = model->getGlobalAxis(2); + screenPos = spaceToScreen(pos); + } + + void GameObject::placeAt(Vector3 p) { + model->setPosition(p); + pos = p; + } + + void GameObject::orientAt(Quaternion rotQuat){ + rot = rotQuat; + model->setOrientation(rotQuat); + leftVec = model->getGlobalAxis(0); + upVec = model->getGlobalAxis(1); + dirVec = model->getGlobalAxis(2); + } + + //TODO remove neccessity to create a material for an invisible mesh + void GameObject::initHitbox(){ + Material *mat = new Material(Root::getSingleton()->getLibPath() + "texture"); + mat->addBoolUniform("texturingEnabled", false); + mat->addBoolUniform("lightingEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4(1, 1, 1, 1)); + + Box *box = new Box(Vector3(width, height, length)); + box->setWireframe(true); + box->setMaterial(mat); + + sol::table gameObjTable = generateView()[GameObject::getGameObjTableName()][id + 1]; + sol::table offsetPosTable = gameObjTable["hitboxOffset"]; + + hitbox = new Node(Vector3(offsetPosTable["x"], offsetPosTable["y"], offsetPosTable["z"])); + hitbox->attachMesh(box); + hitbox->setVisible(Game::getSingleton()->isDebug()); + model->attachChild(hitbox); + } + + void GameObject::destroyHitbox(){ + model->dettachChild(hitbox); + delete hitbox; + hitbox = nullptr; + } + + //TODO use a vector for the color nodes + void GameObject::useColor(Material *colorMat){ + if(hitbox) model->dettachChild(hitbox); + model->setMaterial(model->getMaterial()); + if(hitbox) model->attachChild(hitbox); + + sol::table gameObjTable = generateView()[GameObject::getGameObjTableName()][id + 1]; + sol::table colNodeTbl = gameObjTable["colorNodes"]; + int numColorNodes = colNodeTbl.size(); + + for(int i = 0; i < numColorNodes; i++){ + string name = colNodeTbl[i + 1]; + Node *node = model->findDescendant(name, true); + + if(!node) continue; + + vector meshes = node->getMeshes(); + + for(Mesh *mesh : meshes) + mesh->setMaterial(colorMat); + } + } + + void GameObject::initProperties(){ + sol::table objTable = generateView()[GameObject::getGameObjTableName()][id + 1]; + sol::optional maxUnevOpt = objTable["maxUnevenness"]; + + if(maxUnevOpt != sol::nullopt) maxUnevenness = objTable["maxUnevenness"]; + + sol::table sizeTable = objTable["size"]; + width = sizeTable["x"]; + height = sizeTable["y"]; + length = sizeTable["z"]; + } + + void GameObject::destroyModel(){ + if(model->getMaterial()) + delete model->getMaterial(); + + Root::getSingleton()->getRootNode()->dettachChild(model); + delete model; + } + + //TODO improve DEFAULT_TEXTURE handling + void GameObject::initModel(bool textured){ + sol::table gameObjTable = generateView()[GameObject::getGameObjTableName()][id + 1]; + string basePath = gameObjTable["basePath"], meshPath = gameObjTable["meshPath"]; + model = new Model(basePath + meshPath); + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "texture"); + + if(textured){ + string albedoPath = gameObjTable["albedoPath"].get_or(configData::DEFAULT_TEXTURE); + string f[]{albedoPath == configData::DEFAULT_TEXTURE ? GameManager::getSingleton()->getPath() + albedoPath : basePath + albedoPath}; + Texture *diffuseTexture = new Texture(f, 1, false); + + mat->addBoolUniform("texturingEnabled", true); + mat->addBoolUniform("lightingEnabled", true); + mat->addBoolUniform("constLightingEnabled", false); + mat->addTexUniform("textures[0]", diffuseTexture, true); + } + else{ + mat->addBoolUniform("texturingEnabled", false); + mat->addBoolUniform("lightingEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4::VEC_ZERO); + model->setWireframe(true); + } + + model->setMaterial(mat); + sol::optional colNodeOpt = gameObjTable["colorNodes"]; + + if(!(model->isWireframe() || colNodeOpt == sol::nullopt)) + useColor(player->getColorMaterial()); + + root->getRootNode()->attachChild(model); + } + + //TODO implement death SFX destruction + void GameObject::destroySound(){ + } + + sf::Sound* GameObject::prepareSfx(sf::SoundBuffer *buffer, string sfxPath){ + sf::Sound *sfx = nullptr; + + if(buffer->loadFromFile(sfxPath.c_str())){ + sfx = new sf::Sound(*buffer); + sfx->setBuffer(*buffer); + } + + return sfx; + } + + bool GameObject::pointWithinObj(Vector3 point, float deltaLength, float deltaWidth, bool useHeight, float deltaHeight){ + Vector3 pointDir = point - pos; + bool within = true; + + float forwAngle = std::min(dirVec.getAngleBetween(pointDir.norm()), (-dirVec).getAngleBetween(pointDir.norm())); + float forwDist = pointDir.getLength() * cos(forwAngle); + within &= (forwDist < .5 * (length + deltaLength)); + + float leftAngle = std::min(leftVec.getAngleBetween(pointDir.norm()), (-leftVec).getAngleBetween(pointDir.norm())); + float leftDist = pointDir.getLength() * cos(leftAngle); + within &= (leftDist < .5 * (width + deltaWidth)); + + if(useHeight){ + float upAngle = std::min(upVec.getAngleBetween(pointDir.norm()), (-upVec).getAngleBetween(pointDir.norm())); + float upDist = pointDir.getLength() * cos(upAngle); + within &= (upDist < .5 * (height + deltaHeight)); + } + + return within; + } + + //TODO improve for other game objs, too + void GameObject::changePlayer(Player *newPlayer){ + vector &oldPlayerUnits = player->getUnits(); + int oldId = -1; + + for(int i = 0; i < oldPlayerUnits.size(); i++) + if(oldPlayerUnits[i] == (Unit*)this){ + oldId = i; + break; + } + + oldPlayerUnits.erase(oldPlayerUnits.begin() + oldId); + newPlayer->addUnit((Unit*)this); + setPlayer(newPlayer); + useColor(newPlayer->getColorMaterial()); + ((Unit*)this)->halt(); + } + + string GameObject::getGameObjTableName(){ + switch(type){ + case GameObject::Type::UNIT: + return "units"; + case GameObject::Type::PROJECTILE: + return "projectiles"; + case GameObject::Type::RESOURCE_DEPOSIT: + return "resources"; + } + } +} diff --git a/source/gameplay/gameObjects/gameObject.h b/source/gameplay/gameObjects/gameObject.h new file mode 100644 index 0000000..e095f7d --- /dev/null +++ b/source/gameplay/gameObjects/gameObject.h @@ -0,0 +1,80 @@ +#ifndef GAME_OBJECT_H +#define GAME_OBJECT_H + +#include +#include +#include + +namespace sf{ + class SoundBuffer; + class Sound; +} + +namespace battleship{ + class Player; + class Unit; + class Destructable; + + class GameObject{ + public: + enum class Type{UNIT, PROJECTILE, RESOURCE_DEPOSIT}; + + GameObject(Type, int, Player*, vb01::Vector3, vb01::Quaternion); + ~GameObject(){} + virtual void reinit(); + virtual void update(); + virtual void select(){} + virtual void placeAt(vb01::Vector3); + void orientAt(vb01::Quaternion); + std::string getGameObjTableName(); + static sf::Sound* prepareSfx(sf::SoundBuffer*, std::string); + bool pointWithinObj(vb01::Vector3, float = 0, float = 0, bool = false, float = 0); + void changePlayer(Player *newPlayer); + inline float getMaxUnevenness(){return maxUnevenness;} + inline vb01::Vector2 getScreenPos(){return screenPos;} + inline vb01::Vector3 getCorner(int i){return corners[i];} + inline bool isSelectable(){return selectable;} + inline bool isDebuggable(){return debugging;} + inline vb01::Vector3 getPos() {return pos;} + inline vb01::Quaternion getRot(){return rot;} + inline float getWidth() {return width;} + inline float getHeight() {return height;} + inline float getLength() {return length;} + inline vb01::Model* getModel() {return model;} + inline void setPlayer(Player *pl){player = pl;} + inline Player* getPlayer(){return player;} + inline void toggleDebugging(bool d){this->debugging=d;} + inline vb01::Vector3 getDirVec() {return dirVec;} + inline vb01::Vector3 getLeftVec() {return leftVec;} + inline vb01::Vector3 getUpVec() {return upVec;} + inline int getId() {return id;} + inline Type getType(){return type;} + inline vb01::Node* getHitbox(){return hitbox;} + inline void setRemove(bool rm){this->remove = rm;} + inline bool isRemove(){return remove;} + inline Destructable* getDestructable(){return destructable;} + protected: + virtual void useColor(vb01::Material*); + virtual void initProperties(); + virtual void destroyModel(); + virtual void initModel(bool = true); + virtual void initHitbox(); + virtual void destroyHitbox(); + virtual void destroySound(); + virtual void initSound(){} + + Type type; + int id; + Player *player; + Destructable *destructable = nullptr; + vb01::Model *model = nullptr; + vb01::Node *hitbox = nullptr; + vb01::Vector3 pos = vb01::Vector3(0, 0, 0), upVec = vb01::Vector3(0, 1, 0), dirVec = vb01::Vector3(0, 0, 1), leftVec = vb01::Vector3(1, 0, 0), corners[8]; + vb01::Vector2 screenPos; + vb01::Quaternion rot; + bool selectable = false, debugging = false, remove = false; + float width, height, length, maxUnevenness; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/gameObjectFactory.cpp b/source/gameplay/gameObjects/gameObjectFactory.cpp new file mode 100644 index 0000000..9fded44 --- /dev/null +++ b/source/gameplay/gameObjects/gameObjectFactory.cpp @@ -0,0 +1,94 @@ +#include "gameObjectFactory.h" +#include "player.h" +#include "factory.h" +#include "engineer.h" +#include "submarine.h" +#include "resourceRover.h" +#include "projectile.h" +#include "resourceDeposit.h" +#include "pointDefense.h" +#include "extractor.h" +#include "freezer.h" +#include "researchStruct.h" +#include "missile.h" +#include "cruiseMissile.h" +#include "iceSheet.h" +#include "shell.h" +#include "torpedo.h" +#include "depthCharge.h" +#include "defConfigs.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + Unit* GameObjectFactory::createUnit(Player *player, int id, Vector3 pos, Quaternion rot, int buildStatus){ + sol::state_view SOL_LUA_VIEW = generateView(); + int unitClass = SOL_LUA_VIEW["units"][id + 1]["unitClass"]; + bool vehicle = SOL_LUA_VIEW["units"][id + 1]["isVehicle"]; + + if(vehicle) + switch((UnitClass)unitClass){ + case UnitClass::ROBO_ENGINEER: + case UnitClass::CYBORG_ENGINEER: + return new Engineer(player, id, pos, rot, Unit::State::STAND_GROUND); + case UnitClass::RESOURCE_ROVER: + return new ResourceRover(player, id, pos, rot, Unit::State::STAND_GROUND); + case UnitClass::SUBMARINE: + case UnitClass::MISSILE_SUBMARINE: + return new Submarine(player, id, pos, rot, Unit::State::STAND_GROUND); + case UnitClass::FREEZER: + return new Freezer(player, id, pos, rot, Unit::State::STAND_GROUND); + default: + return new Vehicle(player, id, pos, rot, Unit::State::STAND_GROUND); + } + else + switch((UnitClass)unitClass){ + case UnitClass::LAND_FACTORY: + case UnitClass::NAVAL_FACTORY: + return new Factory(player, id, pos, rot, buildStatus, Unit::State::STAND_GROUND); + case UnitClass::POINT_DEFENSE: + return new PointDefense(player, id, pos, rot, buildStatus, Unit::State::STAND_GROUND); + case UnitClass::EXTRACTOR: + return new Extractor(player, id, pos, rot, buildStatus); + case UnitClass::LAB: + return new ResearchStruct(player, id, pos, rot, buildStatus); + case UnitClass::ICE_SHEET: + return new IceSheet(player, id, pos, rot, buildStatus); + default: + return new Structure(player, id, pos, rot, buildStatus, Unit::State::STAND_GROUND); + } + } + + Projectile* GameObjectFactory::createProjectile(Unit *unit, int id, Vector3 pos, Quaternion rot){ + sol::state_view SOL_LUA_VIEW = generateView(); + int projectileClass = SOL_LUA_VIEW["projectiles"][id + 1]["projectileClass"]; + + Order::Target target = unit->getOrder(0).targets[0]; + Vector3 targetPos = (target.unit ? target.unit->getPos() : target.pos); + + switch((ProjectileClass)projectileClass){ + case ProjectileClass::SHELL: + return new Shell(unit, id, pos, rot); + case ProjectileClass::CRUISE_MISSILE: + return new CruiseMissile(unit, id, targetPos, pos, rot); + case ProjectileClass::MISSILE: + return new Missile(unit, id, targetPos, pos, rot); + case ProjectileClass::TORPEDO: + return new Torpedo(unit, id, pos, rot); + case ProjectileClass::DEPTH_CHARGE: + return new DepthCharge(unit, id, pos, rot); + default: + return new Projectile(unit, id, pos, rot); + } + } + + ResourceDeposit* GameObjectFactory::createResourceDeposit(Player *player, int id, Vector3 pos, Quaternion rot, int initAmmount){ + return new ResourceDeposit(player, id, pos, rot, initAmmount); + } +} diff --git a/source/gameplay/gameObjects/gameObjectFactory.h b/source/gameplay/gameObjects/gameObjectFactory.h new file mode 100644 index 0000000..87221a0 --- /dev/null +++ b/source/gameplay/gameObjects/gameObjectFactory.h @@ -0,0 +1,23 @@ +#ifndef GAME_OBJECT_FACTORY_H +#define GAME_OBJECT_FACTORY_H + +#include +#include + +namespace battleship{ + class Unit; + class Projectile; + class ResourceDeposit; + class Player; + + class GameObjectFactory{ + public: + enum GameObjectType{UNIT, PROJECTILE, RESOURCE_DEPOSIT}; + static Unit* createUnit(Player*, int, vb01::Vector3, vb01::Quaternion, int = 0); + static Projectile* createProjectile(Unit*, int, vb01::Vector3, vb01::Quaternion); + static ResourceDeposit* createResourceDeposit(Player*, int, vb01::Vector3, vb01::Quaternion, int = 0); + private: + }; +} + +#endif diff --git a/source/gameplay/gameObjects/gameObjectFrame.cpp b/source/gameplay/gameObjects/gameObjectFrame.cpp new file mode 100644 index 0000000..72f13a1 --- /dev/null +++ b/source/gameplay/gameObjects/gameObjectFrame.cpp @@ -0,0 +1,4 @@ +#include "gameObjectFrame.h" + +namespace battleship{ +} diff --git a/source/gameplay/gameObjects/gameObjectFrame.h b/source/gameplay/gameObjects/gameObjectFrame.h new file mode 100644 index 0000000..f051308 --- /dev/null +++ b/source/gameplay/gameObjects/gameObjectFrame.h @@ -0,0 +1,38 @@ +#ifndef GAME_OBJECT_FRAME_H +#define GAME_OBJECT_FRAME_H + +#include "gameObject.h" + +#include +#include + +namespace battleship{ + struct GameObjectFrame : public GameObject{ + enum Status{ + PLACEABLE, + BLOCKED_BY_FOG_OF_WAR, + BLOCKED_BY_UNIT, + BLOCKED_BY_MAP_BOUNDS, + BLOCKED_BY_DIFF_TERR, + BLOCKED_BY_BUMPY_TERR + }; + + GameObjectFrame(int i, GameObject::Type t, Player *pl, Unit *ou = nullptr, vb01::Vector3 pos = vb01::Vector3::VEC_ZERO, vb01::Quaternion rot = vb01::Quaternion::QUAT_W) : + originalUnit(ou), + GameObject(t, i, pl, pos, rot) + { + initModel(false); + initProperties(); + placeAt(pos); + orientAt(rot); + } + ~GameObjectFrame(){} + void destroy(){destroyModel();} + inline Unit* getOriginalUnit(){return originalUnit;} + + Status status; + Unit *originalUnit = nullptr; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/pathfinder.cpp b/source/gameplay/gameObjects/pathfinder.cpp new file mode 100644 index 0000000..7054230 --- /dev/null +++ b/source/gameplay/gameObjects/pathfinder.cpp @@ -0,0 +1,145 @@ +#include + +#include "pathfinder.h" +#include "vehicle.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + + static Pathfinder *pathfinder = nullptr; + + Pathfinder* Pathfinder::getSingleton(){ + if(!pathfinder) + pathfinder = new Pathfinder(); + + return pathfinder; + } + + vector Pathfinder::calcHeuristics(vector &cells, int dest){ + vector heuristics; + + for(Map::Cell &cell : cells) + heuristics.push_back(145 * (cells[dest].pos.getDistanceFrom(cell.pos))); + + return heuristics; + } + + vector Pathfinder::findPath(vector &cells, vector &heuristics, int source, int dest, int vehicleType){ + if(heuristics.empty() || (heuristics.size() == 1 && heuristics[0] == 0.0)) + heuristics = calcHeuristics(cells, dest); + + bool useHeur = !heuristics.empty(); + + const int size = cells.size(); + u32 *distances = new u32[size]; + vector *paths = new vector[size]; + vector> cellsByCheck; + vector posMinCellChecked; + vector possibleMinCells = vector{source}; + + for(int i = 0; i < size; i++){ + cellsByCheck.push_back(pair(i, false)); + distances[i] = impassibleNodeVal; + posMinCellChecked.push_back(false); + } + + paths[source].push_back(source); + distances[source] = 0; + posMinCellChecked[source] = true; + + int lastVertStrich = -1; + + while(!cellsByCheck[dest].second){ + int posMinCellId = 0, vertStrich = possibleMinCells[posMinCellId]; + + for(int i = 0; i < possibleMinCells.size(); i++){ + float sum1 = distances[possibleMinCells[i]] + (useHeur ? heuristics[possibleMinCells[i]] : 0); + float sum2 = distances[possibleMinCells[posMinCellId]] + (useHeur ? heuristics[possibleMinCells[posMinCellId]] : 0); + + if(sum1 < sum2 || (useHeur && sum1 == sum2 && heuristics[i] < heuristics[vertStrich])){ + posMinCellId = i; + vertStrich = possibleMinCells[i]; + } + } + + if(distances[vertStrich] == impassibleNodeVal){ + vector path = paths[lastVertStrich]; + + delete[] paths; + delete[] distances; + + return path; + } + + posMinCellChecked[possibleMinCells[posMinCellId]] = false; + possibleMinCells.erase(possibleMinCells.begin() + posMinCellId); + + cellsByCheck[vertStrich].second = true; + + int numEdges = cells[vertStrich].edges.size(); + + for(int i = 0; i < numEdges; i++){ + int edgeNode = cells[vertStrich].edges[i].destCellId; + + if(cellsByCheck[edgeNode].second) continue; + + if(!posMinCellChecked[edgeNode]){ + posMinCellChecked[edgeNode] = true; + possibleMinCells.push_back(edgeNode); + } + + UnitType ut = (UnitType)vehicleType; + Map::Cell::Type ct = cells[edgeNode].type; + bool ship = (ut == UnitType::UNDERWATER || ut == UnitType::SEA_LEVEL); + int weightMult = 1; + + if((ut == UnitType::LAND && ct == Map::Cell::WATER) || (ship && ct == Map::Cell::LAND)) + weightMult = 10; + /* + if(vehicleType != -1){ + UnitType unitType = vehicle->getType(); + bool ship = (unitType == UnitType::UNDERWATER || unitType == UnitType::SEA_LEVEL); + Unit *blockingUnit = cells[vertStrich].blockedBy; + bool diffBlockingUnit = (blockingUnit && blockingUnit != vehicle); + bool throughBlockedCell = (source != vertStrich); + + if( + (unitType == UnitType::LAND && ((diffBlockingUnit && throughBlockedCell) || cells[vertStrich].type != Map::Cell::LAND)) || + ( + ship && + ( + cells[vertStrich].type != Map::Cell::WATER || + ( + diffBlockingUnit && + throughBlockedCell && + blockingUnit->getUnitClass() == UnitClass::ICE_SHEET && + vehicle->getUnitClass() != UnitClass::ICEBREAKER + ) + ) + ) + ) + { + continue; + } + } + */ + + if(distances[vertStrich] + weightMult * cells[vertStrich].edges[i].weight < distances[edgeNode]){ + distances[edgeNode] = distances[vertStrich] + weightMult * cells[vertStrich].edges[i].weight; + paths[edgeNode] = paths[vertStrich]; + paths[edgeNode].push_back(edgeNode); + } + } + + lastVertStrich = vertStrich; + } + + vector path = paths[dest]; + + delete[] paths; + delete[] distances; + + return path; + } +} diff --git a/source/gameplay/gameObjects/pathfinder.h b/source/gameplay/gameObjects/pathfinder.h new file mode 100644 index 0000000..66a9000 --- /dev/null +++ b/source/gameplay/gameObjects/pathfinder.h @@ -0,0 +1,27 @@ +#ifndef PATHFINDER_H +#define PATHFINDER_H + +#include "map.h" + +#include + +#include + +namespace battleship{ + class Vehicle; + + class Pathfinder{ + public: + static Pathfinder* getSingleton(); + std::vector calcHeuristics(std::vector&, int); + std::vector findPath(std::vector&, std::vector&, int, int, int = -1); + inline vb01::u32 getImpassibleNodeVal(){return impassibleNodeVal;} + inline void setImpassibleNodeVal(vb01::u32 val){this->impassibleNodeVal = val;} + private: + Pathfinder(){} + + vb01::u32 impassibleNodeVal; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/projectiles/cruiseMissile.cpp b/source/gameplay/gameObjects/projectiles/cruiseMissile.cpp new file mode 100644 index 0000000..c43998e --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/cruiseMissile.cpp @@ -0,0 +1,81 @@ +#include "cruiseMissile.h" +#include "destructable.h" +#include "player.h" +#include "unit.h" +#include "map.h" +#include "game.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + + CruiseMissile::CruiseMissile(Unit *unit, int id, Vector3 tp, Vector3 pos, Quaternion rot) : + Projectile(unit, id, pos, rot), + targetPoint(tp), + flightStage(FlightStage::ASCENT) + { + destructable = new Destructable(this); + + Vector3 unitDir = unit->getDirVec(); + Vector3 leftDir = unit->getLeftVec(); + Vector3 targDir = (Vector3(targetPoint.x, pos.y, targetPoint.z) - pos).norm(); + + bool left = (leftDir.getAngleBetween(targDir) < PI / 2); + float angle = unitDir.getAngleBetween(targDir) * (left ? 1 : -1); + orientAt(Quaternion(angle, Vector3::VEC_J) * rot); + } + + CruiseMissile::~CruiseMissile(){delete destructable;} + + void CruiseMissile::pitch(float rotAngle, Vector3 compVec){ + float minHeight = 20; + float angleToCompVec = Projectile::dirVec.getAngleBetween(compVec); + float angle = (angleToCompVec > rotAngle ? rotAngle : angleToCompVec); + + if(flightStage == FlightStage::ASCENT && Projectile::pos.y - initPos.y > minHeight){ + Projectile::orientAt(Quaternion(angle, Projectile::leftVec) * Projectile::rot); + if(Projectile::dirVec.y <= .001) flightStage = FlightStage::CRUISE; + } + else if(flightStage == FlightStage::DESCENT){ + Vector3 targDir = (Vector3(targetPoint.x, initPos.y, targetPoint.z) - initPos).norm(); + + if(Projectile::dirVec.getAngleBetween(targDir) < PI / 2) + Projectile::orientAt(Quaternion(angle, Projectile::leftVec) * Projectile::rot); + } + } + + void CruiseMissile::cruise(){ + float minDist = 6; + float initDist = Vector3(targetPoint.x, initPos.y, targetPoint.z).getDistanceFrom(initPos); + + if(Projectile::pos.getDistanceFrom(Vector3(targetPoint.x, Projectile::pos.y, targetPoint.z)) < minDist) + flightStage = FlightStage::DESCENT; + } + + void CruiseMissile::update(){ + Projectile::update(); + destructable->update(); + + switch(flightStage){ + case FlightStage::ASCENT: + pitch(rotAngle, Vector3(Projectile::dirVec.x, 0, Projectile::dirVec.z).norm()); + break; + case FlightStage::DESCENT: + pitch(rotAngle, -Vector3::VEC_J); + break; + case FlightStage::CRUISE: + cruise(); + break; + } + + if(flightStage == FlightStage::DESCENT && !remove){ + checkSurfaceCollision(false); + if(remove) return; + checkUnitCollision(); + } + } +} diff --git a/source/gameplay/gameObjects/projectiles/cruiseMissile.h b/source/gameplay/gameObjects/projectiles/cruiseMissile.h new file mode 100644 index 0000000..ff76db4 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/cruiseMissile.h @@ -0,0 +1,22 @@ +#ifndef CRUISE_MISSILE_H +#define CRUISE_MISSILE_H + +#include "projectile.h" + +namespace battleship{ + class CruiseMissile : public Projectile{ + public: + CruiseMissile(Unit*, int, vb01::Vector3, vb01::Vector3, vb01::Quaternion); + ~CruiseMissile(); + void update(); + private: + enum class FlightStage{ASCENT, CRUISE, DESCENT}; + FlightStage flightStage; + vb01::Vector3 targetPoint; + + void pitch(float, vb01::Vector3); + void cruise(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/projectiles/depthCharge.cpp b/source/gameplay/gameObjects/projectiles/depthCharge.cpp new file mode 100644 index 0000000..083e7be --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/depthCharge.cpp @@ -0,0 +1,17 @@ +#include "depthCharge.h" + +namespace battleship{ + using namespace vb01; + + DepthCharge::DepthCharge(Unit *un, int id, Vector3 pos, Quaternion rot) : Projectile(un, id, pos, rot){} + + void DepthCharge::update(){ + Projectile::update(); + + if(!remove){ + checkSurfaceCollision(true); + if(remove) return; + checkUnitCollision(); + } + } +} diff --git a/source/gameplay/gameObjects/projectiles/depthCharge.h b/source/gameplay/gameObjects/projectiles/depthCharge.h new file mode 100644 index 0000000..bf14216 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/depthCharge.h @@ -0,0 +1,15 @@ +#ifndef DEPTH_CHARGE_H +#define DEPTH_CHARGE_H + +#include "projectile.h" + +namespace battleship{ + class DepthCharge : public Projectile{ + public: + DepthCharge(Unit*, int, vb01::Vector3, vb01::Quaternion); + void update(); + private: + }; +} + +#endif diff --git a/source/gameplay/gameObjects/projectiles/missile.cpp b/source/gameplay/gameObjects/projectiles/missile.cpp new file mode 100644 index 0000000..5fd6e49 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/missile.cpp @@ -0,0 +1,32 @@ +#include "missile.h" +#include "unit.h" +#include "destructable.h" + +namespace battleship{ + using namespace vb01; + + Missile::Missile(Unit *un, int id, Vector3 tp, Vector3 pos, Quaternion rot) : + Projectile(un, id, pos, rot), + targetPos(tp) + { + destructable = new Destructable(this); + } + + Missile::~Missile(){delete destructable;} + + void Missile::update(){ + Vector3 targDir = (targetPos - Projectile::pos).norm(); + float angle = targDir.getAngleBetween(Projectile::dirVec); + float ra = (rotAngle < angle ? rotAngle : angle); + orientAt(Quaternion(ra, Projectile::dirVec.cross(targDir)) * Projectile::rot); + + Projectile::update(); + destructable->update(); + + if(!remove){ + checkSurfaceCollision(false); + if(remove) return; + checkUnitCollision(); + } + } +} diff --git a/source/gameplay/gameObjects/projectiles/missile.h b/source/gameplay/gameObjects/projectiles/missile.h new file mode 100644 index 0000000..1ef06de --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/missile.h @@ -0,0 +1,17 @@ +#ifndef MISSILE_H +#define MISSILE_H + +#include "projectile.h" + +namespace battleship{ + class Missile : public Projectile{ + public: + Missile(Unit*, int, vb01::Vector3, vb01::Vector3, vb01::Quaternion); + ~Missile(); + void update(); + private: + vb01::Vector3 targetPos; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/projectiles/projectile.cpp b/source/gameplay/gameObjects/projectiles/projectile.cpp new file mode 100644 index 0000000..ca321a0 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/projectile.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +#include + +#include "game.h" +#include "unit.h" +#include "util.h" +#include "player.h" +#include "environment.h" +#include "projectile.h" +#include "defConfigs.h" +#include "destructable.h" +#include "resourceDeposit.h" +#include "inGameAppState.h" + +using namespace std; +using namespace vb01; + +namespace battleship{ + using namespace vb01; + using namespace configData; + using namespace gameBase; + + Projectile::Projectile(Unit *un, int id, Vector3 pos, Quaternion rot) : GameObject(GameObject::Type::PROJECTILE, id, un->getPlayer(), pos, rot), unit(un), initPos(pos){ + initProperties(); + initModel(); + initSound(); + placeAt(pos); + orientAt(rot); + } + + Projectile::~Projectile(){ + destroySound(); + destroyModel(); + } + + void Projectile::reinit(){ + destroySound(); + destroyModel(); + + initProperties(); + + initModel(); + + GameObject::reinit(); + } + + void Projectile::initProperties(){ + GameObject::initProperties(); + Game *game = Game::getSingleton(); + vector currTechs = player->getTechnologies(); + + sol::table projTable = generateView()[GameObject::getGameObjTableName()][id + 1]; + projClass = (ProjectileClass)projTable["projectileClass"]; + + rayLength = projTable["rayLength"]; + directHitDamage = projTable["directHitDamage"]; directHitDamage += game->calcAbilFromTech(Ability::Type::DIRECT_HIT_DAMAGE, currTechs, (int)GameObject::type, id); + + string explKey = "explosion"; + explosionDamage = projTable[explKey]["damage"]; explosionDamage += game->calcAbilFromTech(Ability::Type::EXPLOSION_DAMAGE, currTechs, (int)GameObject::type, id); + explosionRadius = projTable[explKey]["radius"]; explosionRadius += game->calcAbilFromTech(Ability::Type::EXPLOSION_RADIUS, currTechs, (int)GameObject::type, id); + speed = projTable["speed"]; + rotAngle = projTable["rotAngle"].get_or(0.0); + } + + void Projectile::update() { + GameObject::update(); + placeAt(pos + speed * dirVec); + } + + void Projectile::detonate(Unit *target){ + if(target) target->getDestructable()->takeDamage(directHitDamage); + + sol::table tbl = generateView()[getGameObjTableName()][id + 1]["explosion"]; + FxManager::Fx *fx = FxManager::getSingleton()->initFx(tbl["fx"], model, false, pos); + Environment::explode(fx, (Environment::Detonation)tbl["detonation"], pos, tbl["damage"], tbl["radius"]); + + remove = true; + } + + void Projectile::checkUnitCollision(){ + vector players = Game::getSingleton()->getPlayers(true); + vector targetUnits; + vector targetNodes; + + for(Player *pl : players){ + vector units = pl->getUnits(); + + for(Unit *u : units) + if(unit && unit != u){ + targetUnits.push_back(u); + targetNodes.push_back(u->getHitbox()); + } + } + + vector results = RayCaster::cast(pos, dirVec, targetNodes, rayLength); + + if(!results.empty()) + for(int i = 0; i < targetNodes.size(); i++) + if(targetNodes[i]->getMesh(0) == results[0].mesh) + detonate(targetUnits[i]); + } + + //TODO fix map boundary checks + void Projectile::checkSurfaceCollision(bool useBottomCell){ + Map *map = Map::getSingleton(); + vector &cells = map->getCells(); + int cellId = map->getCellId(pos, false); + Vector3 mapSize = map->getMapSize(); + + if(cellId < 0 || fabs(pos.x) > .5 * mapSize.x || fabs(pos.z) > .5 * mapSize.z){ + remove = true; + return; + } + + if(useBottomCell && !cells[cellId].underWaterCellIds.empty()){ + int numUnderwaterCells = cells[cellId].underWaterCellIds.size(); + cellId = cells[cellId].underWaterCellIds[numUnderwaterCells - 1]; + } + + if((pos + dirVec * rayLength).y <= cells[cellId].pos.y + .5 * map->getCellSize().y) + detonate(); + } + + void Projectile::debug(){ + } +} diff --git a/source/gameplay/gameObjects/projectiles/projectile.h b/source/gameplay/gameObjects/projectiles/projectile.h new file mode 100644 index 0000000..96cc0f8 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/projectile.h @@ -0,0 +1,44 @@ +#ifndef PROJECTILE_H +#define PROJECTILE_H + +#include "gameObject.h" +#include "fxManager.h" + +#include + +namespace vb01{ + class Node; +} + +namespace battleship { + class Unit; + + enum class ProjectileClass{SHELL, CRUISE_MISSILE, MISSILE, TORPEDO, DEPTH_CHARGE}; + + class Projectile : public GameObject{ + public: + Projectile(Unit*, int, vb01::Vector3, vb01::Quaternion); + virtual ~Projectile(); + virtual void update(); + virtual void debug(); + inline ProjectileClass getProjectileClass(){return projClass;} + private: + void initProperties(); + void detonate(Unit *u = nullptr); + protected: + virtual void reinit(); + virtual void checkUnitCollision(); + virtual void checkSurfaceCollision(bool); + + vb01::Vector3 initPos; + ProjectileClass projClass; + Unit *unit = nullptr; + FxManager::Fx *explosionFx = nullptr; + float speed, rayLength, explosionRadius, rotAngle; + int directHitDamage, explosionDamage; + sf::SoundBuffer *shotSfxBuffer, *explosionSfxBuffer; + sf::Sound *shotSfx = nullptr, *explosionSfx = nullptr; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/projectiles/shell.cpp b/source/gameplay/gameObjects/projectiles/shell.cpp new file mode 100644 index 0000000..72e3157 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/shell.cpp @@ -0,0 +1,42 @@ +#include "shell.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + Shell::Shell(Unit *un, int id, Vector3 pos, Quaternion rot) : + Projectile(un, id, pos, rot), + initTime(getTime()), + initDir(dirVec) {} + + void Shell::update(){ + float currTime = float(getTime() - initTime) / 1000; + + if(currTime == 0) return; + + const float G = generateView()["G"]; + float angle = initDir.getAngleBetween(Vector3(initDir.x, 0, initDir.z).norm()); + + dirVec = ( + pos + + speed * currTime * cos(angle) * Vector3(initDir.x, 0, initDir.z).norm() + + (currTime * speed * sin(angle) - G * currTime * currTime) * Vector3::VEC_J - + pos + ).norm(); + upVec = Quaternion(-PI / 2, leftVec) * dirVec; + model->lookAt(dirVec, upVec); + + Projectile::update(); + + if(!remove){ + checkSurfaceCollision(false); + if(remove) return; + checkUnitCollision(); + } + } +} diff --git a/source/gameplay/gameObjects/projectiles/shell.h b/source/gameplay/gameObjects/projectiles/shell.h new file mode 100644 index 0000000..3b73877 --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/shell.h @@ -0,0 +1,19 @@ +#ifndef SHELL_H +#define SHELL_H + +#include "projectile.h" + +#include + +namespace battleship{ + class Shell : public Projectile{ + public: + Shell(Unit*, int, vb01::Vector3, vb01::Quaternion); + void update(); + private: + vb01::s64 initTime; + vb01::Vector3 initDir; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/projectiles/torpedo.cpp b/source/gameplay/gameObjects/projectiles/torpedo.cpp new file mode 100644 index 0000000..61aa8cf --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/torpedo.cpp @@ -0,0 +1,17 @@ +#include "torpedo.h" + +namespace battleship{ + using namespace vb01; + + Torpedo::Torpedo(Unit *un, int id, Vector3 pos, Quaternion rot) : Projectile(un, id, pos, rot){} + + void Torpedo::update(){ + Projectile::update(); + + if(!remove){ + checkSurfaceCollision(true); + if(remove) return; + checkUnitCollision(); + } + } +} diff --git a/source/gameplay/gameObjects/projectiles/torpedo.h b/source/gameplay/gameObjects/projectiles/torpedo.h new file mode 100644 index 0000000..2e8dffe --- /dev/null +++ b/source/gameplay/gameObjects/projectiles/torpedo.h @@ -0,0 +1,15 @@ +#ifndef TORPEDO_H +#define TORPEDO_H + +#include "projectile.h" + +namespace battleship{ + class Torpedo : public Projectile{ + public: + Torpedo(Unit*, int, vb01::Vector3, vb01::Quaternion); + void update(); + private: + }; +} + +#endif diff --git a/source/gameplay/gameObjects/resources/resourceDeposit.cpp b/source/gameplay/gameObjects/resources/resourceDeposit.cpp new file mode 100644 index 0000000..76ea15c --- /dev/null +++ b/source/gameplay/gameObjects/resources/resourceDeposit.cpp @@ -0,0 +1,34 @@ +#include "resourceDeposit.h" + +namespace battleship{ + using namespace vb01; + + ResourceDeposit::ResourceDeposit(Player *player, int id, Vector3 pos, Quaternion rot, int ia) : + GameObject(GameObject::Type::RESOURCE_DEPOSIT, id, player, pos, rot), + initAmmount(ia), + ammount(ia) + { + initProperties(); + initModel(); + initHitbox(); + placeAt(pos); + orientAt(rot); + } + + ResourceDeposit::~ResourceDeposit(){ + destroyHitbox(); + destroyModel(); + } + + void ResourceDeposit::reinit(){ + destroyHitbox(); + destroyModel(); + + initProperties(); + + initModel(); + initHitbox(); + + GameObject::reinit(); + } +} diff --git a/source/gameplay/gameObjects/resources/resourceDeposit.h b/source/gameplay/gameObjects/resources/resourceDeposit.h new file mode 100644 index 0000000..2d8527e --- /dev/null +++ b/source/gameplay/gameObjects/resources/resourceDeposit.h @@ -0,0 +1,26 @@ +#ifndef RESOURCE_DEPOSIT_H +#define RESOURCE_DEPOSIT_H + +#include "gameObject.h" + +namespace battleship{ + class Extractor; + + class ResourceDeposit : public GameObject{ + public: + ResourceDeposit(Player*, int, vb01::Vector3, vb01::Quaternion, int); + ~ResourceDeposit(); + inline int getAmmount(){return ammount;} + inline int getInitAmmount(){return initAmmount;} + inline void decreaseAmmount(int amnt){ammount -= amnt;} + inline Extractor* getExtractor(){return extractor;} + inline void setExtractor(Extractor *e){this->extractor = e;} + private: + int ammount, initAmmount; + Extractor *extractor = nullptr; + + void reinit(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/ability.h b/source/gameplay/gameObjects/units/ability.h new file mode 100644 index 0000000..c45f7dc --- /dev/null +++ b/source/gameplay/gameObjects/units/ability.h @@ -0,0 +1,32 @@ +#ifndef ABILITY_H +#define ABILITY_H + +#include + +namespace battleship{ + struct Ability{ + enum class Type{ + SPEED, + HEALTH, + LINE_OF_SIGHT, + MAX_TURN_ANGLE, + DIRECT_HIT_DAMAGE, + EXPLOSION_RADIUS, + EXPLOSION_DAMAGE, + CAPACITY, + LOAD_RATE, + LOAD_SPEED, + DRAW_RATE, + DRAW_SPEED, + HACK_RANGE, + UNIT_UNLOCK + }; + + Type type; + float ammount; + int gameObjType; + std::vector gameObjIds; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/buildableUnit.h b/source/gameplay/gameObjects/units/buildableUnit.h new file mode 100644 index 0000000..772a3b6 --- /dev/null +++ b/source/gameplay/gameObjects/units/buildableUnit.h @@ -0,0 +1,13 @@ +#ifndef BUILDABLE_UNIT_H +#define BUILDABLE_UNIT_H + +namespace battleship{ + struct BuildableUnit{ + int id; + bool buildable; + + BuildableUnit(int i, bool build) : id(i), buildable(build){} + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/garrisonable.cpp b/source/gameplay/gameObjects/units/garrisonable.cpp new file mode 100644 index 0000000..7857696 --- /dev/null +++ b/source/gameplay/gameObjects/units/garrisonable.cpp @@ -0,0 +1,23 @@ +#include "garrisonable.h" +#include "unit.h" + +#include + +namespace battleship{ + using namespace vb01; + + void Garrisonable::update(){ + } + + void Garrisonable::ejectVehicle(int id){ + } + + void Garrisonable::prepareGarrisonSlots(){ + for(int i = 0; i < capacity; i++){ + Vector2 size = 10 * Vector2::VEC_IJ; + Vector2 pos = Vector2(0, 10 * (i)); + garrisonSlotBackgrounds.push_back(Unit::createBar(pos, size, Vector4(0, 0, 0, 1))); + garrisonSlotForegrounds.push_back(Unit::createBar(pos, size, Vector4(0, 1, 0, 1))); + } + } +} diff --git a/source/gameplay/gameObjects/units/garrisonable.h b/source/gameplay/gameObjects/units/garrisonable.h new file mode 100644 index 0000000..df69025 --- /dev/null +++ b/source/gameplay/gameObjects/units/garrisonable.h @@ -0,0 +1,30 @@ +#ifndef GARRISONABLE_H +#define GARRISONABLE_H + +#include + +#include "unit.h" + +namespace vb01{ + class Node; +} + +namespace battleship{ + class Vehicle; + + class Garrisonable{ + public: + Garrisonable(){} + ~Garrisonable(){} + void update(); + void ejectVehicle(int); + protected: + int capacity; + std::vector garrisonedVehicles; + std::vector garrisonSlotForegrounds, garrisonSlotBackgrounds; + + void prepareGarrisonSlots(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/structures/extractor.cpp b/source/gameplay/gameObjects/units/structures/extractor.cpp new file mode 100644 index 0000000..ede6007 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/extractor.cpp @@ -0,0 +1,70 @@ +#include "extractor.h" +#include "player.h" +#include "game.h" +#include "destructable.h" +#include "activeGameState.h" +#include "resourceDeposit.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + Extractor::Extractor(Player *player, int id, Vector3 pos, Quaternion rot, int buildStatus, ResourceDeposit *rd, Unit::State state) : Structure(player, id, pos, rot, buildStatus, state){ + Vector2 size = Vector2(lenHpBar, 10); + ammountBackground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 0, 1)); + ammountForeground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 1, 1)); + + if(rd) return; + + initProperties(); + + for(ResourceDeposit *dep : Game::getSingleton()->getCivilianPlayer()->getResourceDeposits()) + if(dep->getPos().getDistanceFrom(pos) < .001){ + deposit = dep; + deposit->setExtractor(this); + break; + } + } + + Extractor::~Extractor(){ + if(deposit) deposit->setExtractor(nullptr); + + destructable->removeBar(ammountForeground); + destructable->removeBar(ammountBackground); + } + + void Extractor::initProperties(){ + Game *game = Game::getSingleton(); + vector currTechs = player->getTechnologies(); + + sol::table unitTable = generateView()["units"][id + 1]; + drawRate = unitTable["drawRate"]; drawRate += game->calcAbilFromTech(Ability::Type::DRAW_RATE, currTechs, (int)GameObject::type, id); + drawSpeed = unitTable["drawSpeed"]; drawSpeed += game->calcAbilFromTech(Ability::Type::DRAW_SPEED, currTechs, (int)GameObject::type, id); + } + + void Extractor::update(){ + Structure::update(); + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + int ammount = 0, initAmmount = 0; + + if(deposit){ + ammount = deposit->getAmmount(); + initAmmount = deposit->getInitAmmount(); + } + + destructable->displayStats(ammountForeground, ammountBackground, ammount, initAmmount, mainPlayer == player && mainPlayerSelecting, Vector2(0, -10)); + } + + void Extractor::draw(){ + deposit->decreaseAmmount(drawSpeed); + lastDrawTime = getTime(); + } +} diff --git a/source/gameplay/gameObjects/units/structures/extractor.h b/source/gameplay/gameObjects/units/structures/extractor.h new file mode 100644 index 0000000..0002eb4 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/extractor.h @@ -0,0 +1,30 @@ +#ifndef EXTRACTOR_H +#define EXTRACTOR_H + +#include "structure.h" + +#include + +namespace battleship{ + class ResourceDeposit; + + class Extractor : public Structure{ + public: + Extractor(Player*, int, vb01::Vector3, vb01::Quaternion, int, ResourceDeposit *rd = nullptr, Unit::State = Unit::State::STAND_GROUND); + ~Extractor(); + void update(); + void draw(); + bool canDraw(){return vb01::getTime() - lastDrawTime > drawRate;} + inline ResourceDeposit* getDeposit(){return deposit;} + private: + void initProperties(); + + int drawSpeed, drawRate; + vb01::s64 lastDrawTime = 0; + vb01::Node *ammountBackground = nullptr, *ammountForeground = nullptr; + ResourceDeposit *deposit = nullptr; + + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/structures/factory.cpp b/source/gameplay/gameObjects/units/structures/factory.cpp new file mode 100644 index 0000000..9390bdc --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/factory.cpp @@ -0,0 +1,82 @@ +#include "factory.h" +#include "player.h" +#include "game.h" +#include "destructable.h" +#include "gameObjectFactory.h" +#include "activeGameState.h" + +#include +#include + +#include + +namespace battleship{ + using namespace vb01; + using namespace std; + using namespace gameBase; + + Factory::Factory(Player *player, int id, Vector3 pos, Quaternion rot, int buildStatus, Unit::State state) : Structure(player, id, pos, rot, buildStatus, state), rallyPoint(pos + 30 * dirVec){} + + void Factory::update(){ + Structure::update(); + + if(!isComplete()) return; + + if(!unitQueue.empty()) train(); + } + + //TODO replace repetetive string literals + void Factory::initProperties(){ + Structure::initProperties(); + } + + int Factory::getNumQueueUnitsById(int unitId){ + int numUnits = 0; + + for(int qu : unitQueue) + if(qu == unitId) + numUnits++; + + return numUnits; + } + + void Factory::appendToQueue(int buId){ + if(buId < buildableUnits.size() && buildableUnits[buId].buildable) + unitQueue.push_back(buildableUnits[buId].id); + } + + void Factory::train(){ + bool training = !unitQueue.empty(); + buildStatusForeground->setVisible(training); + buildStatusBackground->setVisible(training); + + if(training){ + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + + destructable->displayStats(buildStatusForeground, buildStatusBackground, trainingStatus, 100, mainPlayer == player && mainPlayerSelecting, Vector2(0, -10)); + sol::table targTable = generateView()["units"][unitQueue[0] + 1]; + int costRate = (int)targTable["cost"] / 100, trainRate = (int)targTable["buildTime"] / 100; + + if(player->getResource(ResourceType::REFINEDS) >= costRate && getTime() - lastTrainTime > trainRate){ + trainingStatus++; + player->updateResource(ResourceType::REFINEDS, -costRate, true); + lastTrainTime = getTime(); + } + + if(trainingStatus >= 100){ + Unit *unit = GameObjectFactory::createUnit(player, unitQueue[0], pos, rot); + player->addUnit(unit); + unit->receiveOrder(Order(Order::TYPE::MOVE, vector{Order::Target(nullptr, rallyPoint)}, Vector3::VEC_ZERO), false); + + unitQueue.erase(unitQueue.begin()); + trainingStatus = 0; + + player->incVehiclesBuilt(); + } + } + } +} diff --git a/source/gameplay/gameObjects/units/structures/factory.h b/source/gameplay/gameObjects/units/structures/factory.h new file mode 100644 index 0000000..d9a2128 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/factory.h @@ -0,0 +1,37 @@ +#ifndef FACTORY_H +#define FACTORY_H + +#include "structure.h" + +#include +#include + +namespace vb01{ + class Node; +} + +namespace battleship{ + class Player; + + class Factory : public Structure{ + public: + Factory(Player*, int, vb01::Vector3, vb01::Quaternion, int = 0, Unit::State = Unit::State::STAND_GROUND); + ~Factory(){} + void update(); + int getNumQueueUnitsById(int); + void appendToQueue(int); + inline void setRallyPoint(vb01::Vector3 rp){this->rallyPoint = rp;} + inline vb01::Vector3 getRallyPoint(){return rallyPoint;} + inline std::vector getQueue(){return unitQueue;} + private: + std::vector unitQueue; + int maxLenQueue, trainingStatus = 0; + vb01::s64 lastTrainTime = 0; + vb01::Vector3 rallyPoint; + + void initProperties(); + void train(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/structures/iceSheet.cpp b/source/gameplay/gameObjects/units/structures/iceSheet.cpp new file mode 100644 index 0000000..fea125f --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/iceSheet.cpp @@ -0,0 +1,27 @@ +#include "iceSheet.h" +#include "player.h" +#include "game.h" + +namespace battleship{ + using namespace vb01; + using namespace std; + + IceSheet::IceSheet(Player *player, int id, Vector3 pos, Quaternion rot, int bldSt) : Structure(player, id, pos, rot, bldSt, Unit::State::STAND_GROUND){} + + void IceSheet::update(){ + Structure::update(); + + if(buildStatus < 100) + model->getChild(0)->setScale(.01 * buildStatus * Vector3::VEC_IJK); + + for(Player *pl : Game::getSingleton()->getPlayers(true)){ + vector icebreakers = pl->getUnitsByClass(UnitClass::ICEBREAKER); + + for(Unit *icebreaker : icebreakers) + if(icebreaker->pointWithinObj(pos)){ + player->removeUnit(this); + return; + } + } + } +} diff --git a/source/gameplay/gameObjects/units/structures/iceSheet.h b/source/gameplay/gameObjects/units/structures/iceSheet.h new file mode 100644 index 0000000..83ecf1f --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/iceSheet.h @@ -0,0 +1,15 @@ +#ifndef ICE_SHEET_H +#define ICE_SHEET_H + +#include "structure.h" + +namespace battleship{ + class IceSheet : public Structure{ + public: + IceSheet(Player*, int, vb01::Vector3, vb01::Quaternion, int); + void update(); + private: + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/structures/pointDefense.cpp b/source/gameplay/gameObjects/units/structures/pointDefense.cpp new file mode 100644 index 0000000..47faeab --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/pointDefense.cpp @@ -0,0 +1,17 @@ +#include "pointDefense.h" + +#include + +namespace battleship{ + using namespace vb01; + using namespace std; + using namespace gameBase; + + PointDefense::PointDefense(Player *player, int id, Vector3 pos, Quaternion rot, int buildStatus, Unit::State state) : Structure(player, id, pos, rot, buildStatus, state){} + + void PointDefense::attack(Order order){ + if(!isComplete()) return; + + Unit::attack(order); + } +} diff --git a/source/gameplay/gameObjects/units/structures/pointDefense.h b/source/gameplay/gameObjects/units/structures/pointDefense.h new file mode 100644 index 0000000..b5f6746 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/pointDefense.h @@ -0,0 +1,15 @@ +#ifndef POINT_DEFENSE_H +#define POINT_DEFENSE_H + +#include "structure.h" + +namespace battleship{ + class PointDefense : public Structure{ + public: + PointDefense(Player*, int, vb01::Vector3, vb01::Quaternion, int, Unit::State); + private: + void attack(Order); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/structures/researchStruct.cpp b/source/gameplay/gameObjects/units/structures/researchStruct.cpp new file mode 100644 index 0000000..398b307 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/researchStruct.cpp @@ -0,0 +1,68 @@ +#include "researchStruct.h" +#include "activeGameState.h" +#include "destructable.h" +#include "player.h" +#include "game.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + ResearchStruct::ResearchStruct(Player *player, int id, Vector3 pos, Quaternion rot, int buildStatus, Unit::State state) : Structure(player, id, pos, rot, buildStatus, state){ + sol::table unitTable = generateView()["units"][id + 1]; + generationRate = unitTable["generationRate"]; + generationSpeed = unitTable["generationSpeed"]; + researchCost = unitTable["researchCost"]; + + Vector2 size = Vector2(lenHpBar, 10); + researchStatusBackground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 0, 1)); + researchStatusForeground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(1, 0, 1, 1)); + } + + ResearchStruct::~ResearchStruct(){ + destructable->removeBar(researchStatusBackground); + destructable->removeBar(researchStatusForeground); + } + + void ResearchStruct::update(){ + Structure::update(); + + if(!isComplete()) return; + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = Unit::getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + + if(researchQueue.empty()){ + if(destructable->getHealth() > .3 * destructable->getMaxHealth() && player->getResource(ResourceType::REFINEDS) >= researchCost && canUpdateResearch()){ + player->updateResource(ResourceType::RESEARCH, generationSpeed, true); + player->updateResource(ResourceType::REFINEDS, -researchCost, true); + lastUpdateTime = getTime(); + } + + researchStatusBackground->setVisible(false); + researchStatusForeground->setVisible(false); + } + else if(!researchQueue.empty()){ + destructable->displayStats(researchStatusForeground, researchStatusBackground, researchStatus, 100, mainPlayer == player && mainPlayerSelecting, Vector2(0, -10)); + + int techCost = Game::getSingleton()->getTechnology(researchQueue[0]).cost; + int playerResearch = player->getResource(ResourceType::RESEARCH); + + if(techCost > playerResearch && canUpdateResearch()){ + researchStatus += int(100 * ((float)playerResearch / techCost)); + lastUpdateTime = getTime(); + } + else if(researchStatus >= 100 || techCost <= playerResearch){ + player->addTechnology(researchQueue[0]); + researchQueue.erase(researchQueue.begin()); + researchStatus = 0; + } + } + } +} diff --git a/source/gameplay/gameObjects/units/structures/researchStruct.h b/source/gameplay/gameObjects/units/structures/researchStruct.h new file mode 100644 index 0000000..5cdc969 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/researchStruct.h @@ -0,0 +1,29 @@ +#ifndef RESEARCH_STRUCT_H +#define RESEARCH_STRUCT_H + +#include "structure.h" + +namespace vb01{ + class Node; +} + +namespace battleship{ + class ResearchStruct : public Structure{ + public: + ResearchStruct(Player*, int, vb01::Vector3, vb01::Quaternion, int = 0, Unit::State = Unit::State::STAND_GROUND); + ~ResearchStruct(); + void update(); + inline void appendToQueue(int tid){researchQueue.push_back(tid);} + inline std::vector getQueue(){return researchQueue;} + private: + vb01::s64 lastUpdateTime = 0; + int researchCost, generationRate, generationSpeed, researchStatus = 0; + std::vector researchQueue; + vb01::Node *researchStatusForeground = nullptr, *researchStatusBackground = nullptr; + + bool canUpdateResearch(){return vb01::getTime() - lastUpdateTime > generationRate;} + void generateResearch(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/structures/structure.cpp b/source/gameplay/gameObjects/units/structures/structure.cpp new file mode 100644 index 0000000..a054347 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/structure.cpp @@ -0,0 +1,41 @@ +#include "structure.h" +#include "activeGameState.h" +#include "destructable.h" + +#include + +#include + +using namespace gameBase; +using namespace vb01; +using namespace std; + +namespace battleship{ + Structure::Structure(Player *player, int id, Vector3 pos, Quaternion rot, int bldSt, Unit::State state) : Unit(player, id, pos, rot, state), buildStatus(bldSt){ + Vector2 size = Vector2(lenHpBar, 10); + buildStatusBackground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 0, 1)); + buildStatusForeground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 1, 1)); + } + + Structure::~Structure(){ + destructable->removeBar(buildStatusForeground); + destructable->removeBar(buildStatusBackground); + } + + void Structure::update(){ + Unit::update(); + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + + if(!isComplete()) + destructable->displayStats(buildStatusForeground, buildStatusBackground, buildStatus, 100, mainPlayer == player && mainPlayerSelecting, Vector2(0, -10)); + else{ + buildStatusBackground->setVisible(false); + buildStatusForeground->setVisible(false); + } + } +} diff --git a/source/gameplay/gameObjects/units/structures/structure.h b/source/gameplay/gameObjects/units/structures/structure.h new file mode 100644 index 0000000..37d4fa3 --- /dev/null +++ b/source/gameplay/gameObjects/units/structures/structure.h @@ -0,0 +1,26 @@ +#ifndef STRUCTURE_H +#define STRUCTURE_H + +#include "unit.h" + +namespace vb01{ + class Node; +} + +namespace battleship{ + class Structure : public Unit{ + public: + Structure(Player*, int, vb01::Vector3, vb01::Quaternion, int = 0, Unit::State = Unit::State::STAND_GROUND); + ~Structure(); + virtual void update(); + inline bool isComplete(){return buildStatus == 100;} + inline int getBuildStatus(){return buildStatus;} + inline void incrementBuildStatus(){buildStatus++;} + private: + protected: + vb01::Node *buildStatusBackground = nullptr, *buildStatusForeground = nullptr; + int buildStatus = 0; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/technology.h b/source/gameplay/gameObjects/units/technology.h new file mode 100644 index 0000000..88d863e --- /dev/null +++ b/source/gameplay/gameObjects/units/technology.h @@ -0,0 +1,18 @@ +#ifndef TECHNOLOGY_H +#define TECHNOLOGY_H + +#include +#include + +namespace battleship{ + struct Technology{ + int cost; + std::string name; + std::string icon; + std::string description; + std::vector parents; + std::vector abilities; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/unit.cpp b/source/gameplay/gameObjects/units/unit.cpp new file mode 100644 index 0000000..98f337a --- /dev/null +++ b/source/gameplay/gameObjects/units/unit.cpp @@ -0,0 +1,582 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "unit.h" +#include "weapon.h" +#include "util.h" +#include "game.h" +#include "map.h" +#include "vehicle.h" +#include "destructable.h" +#include "gameObjectFactory.h" +#include "activeGameState.h" +#include "gameManager.h" +#include "projectile.h" +#include "defConfigs.h" +#include "pathfinder.h" +#include "ability.h" + +using namespace glm; +using namespace vb01; +using namespace gameBase; +using namespace std; + +namespace battleship{ + //TODO move restartTime to unitData.lua + Unit::Unit(Player *player, int id, Vector3 pos, Quaternion rot, State st) : + GameObject(GameObject::Type::UNIT, id, player, pos, rot), + state(st), + restartTime(2000) + { + selectable = true; + + destructable = new Destructable(this); + + Unit::initProperties(); + initModel(); + initHitbox(); + initSound(); + initWeapons(); + placeAt(pos); + orientAt(rot); + + Vector2 size = Vector2(lenHpBar, 10); + hpBackgroundNode = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 0, 1)); + hpForegroundNode = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 1, 0, 1)); + } + + Unit::~Unit() { + Map::getSingleton()->unblockCells(this); + destructable->removeBar(hpBackgroundNode); + destructable->removeBar(hpForegroundNode); + destroyWeapons(); + destroySound(); + destroyHitbox(); + destroyModel(); + } + + void Unit::initProperties(){ + GameObject::initProperties(); + + sol::state_view SOL_LUA_VIEW = generateView(); + string objType = GameObject::getGameObjTableName(); + sol::table unitTable = SOL_LUA_VIEW[objType][id + 1]; + + Game *game = Game::getSingleton(); + vector currTechs = player->getTechnologies(); + + string name = unitTable["name"]; + alignToSurface = unitTable["alignToSurface"].get_or(false); + vehicle = unitTable["isVehicle"]; + + lineOfSight = unitTable["lineOfSight"]; lineOfSight += game->calcAbilFromTech(Ability::Type::LINE_OF_SIGHT, currTechs, (int)GameObject::type, id); + unitClass = (UnitClass)unitTable["unitClass"]; + type = (UnitType)unitTable["unitType"]; + + guiScreen = ""; + string gsk = "guiScreen"; + sol::optional nameOpt = unitTable[gsk]; + + if(nameOpt != sol::nullopt) guiScreen = unitTable[gsk]; + + string tblName = "garrisonCapacity"; + sol::optional gc = unitTable[tblName]; + + if(gc != sol::nullopt){ + string varName = "numGarrisonSlots"; + SOL_LUA_VIEW.script(varName + " = #" + objType + "[" + to_string(id + 1) + "]." + tblName); + int numGarrisonSlots = SOL_LUA_VIEW[varName]; + + for(int i = 0; i < numGarrisonSlots; i++){ + Vector2 size = 10 * Vector2::VEC_IJ; + Vector2 pos = Vector2(1.5 * size.x * i, 20); + Node *bg = destructable->createBar(pos, size, Vector4(0, 0, 0, 1)); + Node *fg = destructable->createBar(pos, size, Vector4(0, 1, 0, 1)); + int category = unitTable[tblName][i + 1]; + garrisonSlots.push_back(GarrisonSlot(bg, fg, pos, category)); + } + } + + tblName = "buildableUnits"; + sol::optional bu = unitTable[tblName]; + + if(bu != sol::nullopt){ + SOL_LUA_VIEW.script("numBuildableUnits = #units[" + to_string(id + 1) + "]." + tblName); + int numBuildableUnits = SOL_LUA_VIEW["numBuildableUnits"]; + + for(int i = 0; i < numBuildableUnits; i++){ + sol::table buTable = unitTable[tblName][i + 1]; + buildableUnits.push_back(BuildableUnit(buTable["id"], game->isUnitUnlocked(currTechs, id) | (bool)buTable["buildable"])); + } + } + } + + void Unit::initWeapons(){ + sol::state_view SOL_STATE_VIEW = generateView(); + string objType = GameObject::getGameObjTableName(); + sol::table unitTable = SOL_STATE_VIEW[objType][id + 1]; + + string tblName = "weapons"; + sol::optional weaponsTblOpt = unitTable[tblName]; + + if(weaponsTblOpt != sol::nullopt){ + string varName = "numWeapons"; + SOL_STATE_VIEW.script(varName + " = #" + objType + "[" + to_string(id + 1) + "]." + tblName); + int numWeapons = SOL_STATE_VIEW[varName]; + + for(int i = 0; i < numWeapons; i++){ + int wt = 0; + sol::optional wtOpt = unitTable[tblName][i + 1]["type"]; + + if(wtOpt != sol::nullopt) wt = unitTable[tblName][i + 1]["type"]; + + weapons.push_back(new Weapon(this, unitTable, i)); + } + } + } + + void Unit::destroyWeapons(){ + for(Weapon *weapon : weapons) + delete weapon; + + weapons.clear(); + } + + void Unit::initLosLight(){ + Light *light = new Light(Light::Type::POINT); + light->setAttenuation(Light::Attenuation::NONE); + light->setRadius(lineOfSight); + light->setColor(Vector3::VEC_IJK * .3); + light->setAdditiveLighting(false); + light->setUseAngle(false); + + losLightNode = new Node(Vector3::VEC_J * 5); + //losLightNode->addLight(light); + model->attachChild(losLightNode); + } + + void Unit::destroyLosLight(){ + model->dettachChild(losLightNode); + delete losLightNode; + losLightNode = nullptr; + } + + int Unit::getNumFreeGarrisonSlots(){ + int numFreeSlots = 0; + + for(GarrisonSlot &slot : garrisonSlots) + if(!slot.vehicle) numFreeSlots++; + + return numFreeSlots; + } + + void Unit::destroySound(){ + selectionSfx->stop(); + delete selectionSfx; + delete selectionSfxBuffer; + } + + //TODO factor out initSound() + void Unit::initSound(){ + GameObject::initSound(); + selectionSfxBuffer = new sf::SoundBuffer(); + string sfxPath = generateView()[getGameObjTableName()][id + 1]["selectionSfx"]; + selectionSfx = GameObject::prepareSfx(selectionSfxBuffer, sfxPath); + } + + void Unit::reinit(){ + if(losLightNode) + destroyLosLight(); + + destroyWeapons(); + destroyHitbox(); + destroyModel(); + destroySound(); + + Unit::initProperties(); + + initModel(); + initHitbox(); + initSound(); + initWeapons(); + + GameObject::reinit(); + } + + bool Unit::canGarrison(Vehicle *vehicle){ + for(int i = 0; i < garrisonSlots.size(); i++) + if(!garrisonSlots[i].vehicle && garrisonSlots[i].category >= vehicle->getGarrisonCategory()) + return true; + + return false; + } + + void Unit::launch(Order order){ + getWeaponsByOrder(Order::TYPE::LAUNCH)[0]->fire(order); + removeOrder(0); + } + + float Unit::calculateRotation(Vector3 dir, float angle, float maxTurnAngle){ + float rotSpeed = (maxTurnAngle > angle ? angle : maxTurnAngle); + + if(isTargetToTheRight(dir, leftVec)) + rotSpeed *= -1; + + return rotSpeed; + } + + void Unit::renderOrderLine(bool mainPlayerSelecting){ + if (!orders.empty() && orders[0].lineId != -1 && mainPlayerSelecting){ + bool display = canDisplayOrderLine(); + LineRenderer *lineRenderer = LineRenderer::getSingleton(); + lineRenderer->toggleVisibility(orders[0].lineId, display); + + if(display){ + lineRenderer->changeLineField(orders[0].lineId, pos, LineRenderer::START); + Vector3 targPos = (orders[0].targets[0].unit ? orders[0].targets[0].unit->getPos() : orders[0].targets[0].pos); + lineRenderer->changeLineField(orders[0].lineId, targPos, LineRenderer::END); + } + } + } + + //TODO clean up method code + void Unit::autoAttackTargets(){ + if(!orders.empty()) return; + + vector players = Game::getSingleton()->getPlayers(); + vector targets; + + for(Player *pl : players) + if(!(pl == player || pl->getTeam() == player->getTeam())){ + vector targs = pl->getDestructables(); + targets.insert(targets.end(), targs.begin(), targs.end()); + } + + for(GameObject *targ : targets) + if(targ->getPos().getDistanceFrom(pos) < lineOfSight){ + receiveOrder(Order(Order::TYPE::ATTACK, vector{Order::Target(targ)}, Vector3::VEC_ZERO, -1, false), false); + break; + } + } + + void Unit::update() { + GameObject::update(); + destructable->update(); + + if(condition == Condition::EM_JAMMED && getTime() - lastJamTime > restartTime) + condition = Condition::ABLE; + + if(destructable->getFreezeStatus() >= 100) condition = Condition::FROZEN; + + if(state != State::HOLD_FIRE) + autoAttackTargets(); + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + vector selectingPlayers = getSelectingPlayers(); + bool mainPlayerSelecting = (find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + bool mainPlayerOwner = (player == mainPlayer); + bool renderSelectables = (mainPlayerSelecting && mainPlayerOwner); + renderOrderLine(renderSelectables); + + executeOrders(); + destructable->displayStats(hpForegroundNode, hpBackgroundNode, destructable->getHealth(), destructable->getMaxHealth(), mainPlayerSelecting); + + for(GarrisonSlot &slot : garrisonSlots) + destructable->displayStats(slot.foreground, slot.background, (int)((bool)slot.vehicle), (int)true, renderSelectables, slot.offset); + + for(Weapon *weapon : weapons) + weapon->update(); + } + + //TODO remove order argument from action methods + void Unit::executeOrders() { + if(orders.empty() || condition != Condition::ABLE) return; + + if(!currOrderStarted) startCurrentOrder(); + + if(!orders[0].targets.empty() && orders[0].targets[0].unit){ + vector units; + + for(Player *pl : Game::getSingleton()->getPlayers(true)){ + vector us = pl->getUnits(); + units.insert(units.end(), us.begin(), us.end()); + } + + if(find(units.begin(), units.end(), orders[0].targets[0].unit) == units.end()){ + removeOrder(0); + return; + } + } + + switch (orders[0].type) { + case Order::TYPE::ATTACK: + attack(orders[0]); + break; + case Order::TYPE::BUILD: + build(orders[0]); + break; + case Order::TYPE::MOVE: + move(orders[0]); + break; + case Order::TYPE::GARRISON: + garrison(orders[0]); + break; + case Order::TYPE::EJECT: + eject(orders[0]); + break; + case Order::TYPE::PATROL: + patrol(orders[0]); + break; + case Order::TYPE::LAUNCH: + launch(orders[0]); + break; + break; + case Order::TYPE::LOAD: + case Order::TYPE::SUPPLY: + case Order::TYPE::UNLOAD: + handleResources(orders[0]); + break; + case Order::TYPE::HACK: + hack(orders[0]); + break; + default: + break; + } + } + + void Unit::eject(Order order){ + Map *map = Map::getSingleton(); + int currCellId = map->getCellId(pos); + vector &cells = map->getCells(); + int adjWaterCellId = -1, adjLandCellId = -1; + + for(Map::Edge edge : cells[currCellId].edges){ + if(adjLandCellId == -1 && cells[edge.destCellId].type == Map::Cell::LAND) + adjLandCellId = edge.destCellId; + if(adjWaterCellId == -1 && cells[edge.destCellId].type == Map::Cell::WATER) + adjWaterCellId = edge.destCellId; + } + + for(GarrisonSlot &slot : garrisonSlots) + if(slot.vehicle){ + bool exitToLandCell = (slot.vehicle->getType() == UnitType::LAND && adjLandCellId != -1); + bool exitToWaterCell = ((slot.vehicle->getType() == UnitType::SEA_LEVEL || slot.vehicle->getType() == UnitType::UNDERWATER) && adjWaterCellId != -1); + + if(exitToLandCell || exitToWaterCell) + slot.vehicle->exitGarrisonable(cells[exitToLandCell ? adjLandCellId : adjWaterCellId].pos); + } + + removeOrder(0); + } + + void Unit::attack(Order order){ + vector targets; + + for(Player *pl : Game::getSingleton()->getPlayers()){ + vector targs = pl->getDestructables(); + targets.insert(targets.end(), targs.begin(), targs.end()); + } + + vector attackWeapons = getWeaponsByOrder(Order::TYPE::ATTACK); + + for(Order::Target &target : orders[0].targets) + if(target.unit){ + if(find(targets.begin(), targets.end(), target.unit) != targets.end()){ + bool canAttack = false; + + for(Weapon *aw : attackWeapons){ + int tc; + + switch(target.unit->getType()){ + case GameObject::Type::UNIT: + tc = (int)((Unit*)target.unit)->getType(); + break; + case GameObject::Type::PROJECTILE: + tc = (int)((Projectile*)target.unit)->getProjectileClass(); + break; + } + + if(aw->canAttackTarget((int)target.unit->getType(), tc)) canAttack = true; + } + + if(!canAttack){ + removeOrder(0); + return; + } + } + else{ + removeOrder(0); + return; + } + } + + Vector3 targPos = (order.targets[0].unit ? order.targets[0].unit->getPos() : order.targets[0].pos); + + for(Weapon *weapon : attackWeapons) + weapon->trackTarget(targPos); + } + + bool Unit::validateOrder(Order order){ + switch (order.type) { + case Order::TYPE::ATTACK:{ + GameObject *target = order.targets[0].unit; + bool destructTarg = (!target || (target && target->getDestructable())); + return destructTarg && !getWeaponsByOrder(Order::TYPE::ATTACK).empty(); + } + case Order::TYPE::BUILD: + return (unitClass == UnitClass::ROBO_ENGINEER || unitClass == UnitClass::CYBORG_ENGINEER || unitClass == UnitClass::FREEZER); + case Order::TYPE::PATROL: + case Order::TYPE::MOVE: + return vehicle; + case Order::TYPE::GARRISON: + return validateGarrisonOrder(order); + case Order::TYPE::EJECT: + return !garrisonSlots.empty(); + case Order::TYPE::LAUNCH: + return validateLaunchOrder(); + case Order::TYPE::SUPPLY: + case Order::TYPE::LOAD: + case Order::TYPE::UNLOAD: + return unitClass == UnitClass::RESOURCE_ROVER; + case Order::TYPE::HACK: + return !getWeaponsByOrder(Order::TYPE::HACK).empty(); + default: + return false; + } + } + + vector Unit::getWeaponsByOrder(Order::TYPE type){ + vector weaps; + + for(Weapon *w : weapons) + if(w->getOrderType() == type) + weaps.push_back(w); + + return weaps; + } + + vector Unit::getWeaponsByType(int type){ + vector weaps; + + for(Weapon *w : weapons) + if(w->getType() == (Weapon::Type)type) + weaps.push_back(w); + + return weaps; + } + + void Unit::receiveOrder(Order order, bool add) { + if(!validateOrder(order)) return; + + if(!add){ + while (!orders.empty()) + removeOrder(0); + + orderLineDispTime = getTime(); + } + + orders.push_back(order); + + if(order.type == Order::TYPE::BUILD && order.targets[0].unit) + player->addUnit((Unit*)order.targets[0].unit); + + executeOrders(); + } + + void Unit::halt() { + while (orders.size() > 0) + removeOrder(orders.size() - 1); + } + + void Unit::updateGarrison(Vehicle *garrVeh, bool entering){ + for(GarrisonSlot &slot : garrisonSlots){ + if(entering && !slot.vehicle){ + slot.vehicle = garrVeh; + break; + } + else if(!entering && slot.vehicle == garrVeh){ + slot.vehicle = nullptr; + break; + } + } + } + + //TODO select only the closest cells based on unit size + void Unit::placeAt(Vector3 p){ + Map *map = Map::getSingleton(); + + if(alignToSurface){ + vector res = map->raycastTerrain(Vector3(p.x, 100, p.z), -Vector3::VEC_J, true); + + if(res.empty() || res[0].mesh->getNode() != map->getNodeParent()->getChild(0)) + model->lookAt(Vector3(dirVec.x, 0, dirVec.z).norm(), Vector3::VEC_J); + else if(res[0].mesh->getNode() == map->getNodeParent()->getChild(0)){ + float angle = upVec.getAngleBetween(res[0].norm); + + //if(angle > 0) + model->lookAt(leftVec.cross(res[0].norm), res[0].norm); + } + } + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + + //check twice in case the unit is warped over a long distance + if(activeState) map->blockCells(this); + GameObject::placeAt(p); + + if(activeState){ + map->blockCells(this); + + //if(activeState->getPlayer()) Map::Minimap::getSingleton()->updateImage(); + } + } + + vector Unit::getSelectingPlayers(){ + vector players = Game::getSingleton()->getPlayers(), selectingPlayers; + + for(Player *pl : players){ + int numSelectedUnits = pl->getNumSelectedUnits(); + + for(int i = 0; i < numSelectedUnits; i++) + if(pl->getSelectedUnit(i) == this){ + selectingPlayers.push_back(pl); + break; + } + } + + return selectingPlayers; + } + + void Unit::removeOrder(int id) { + if(orders[id].lineId != -1) + LineRenderer::getSingleton()->removeLine(orders[id].lineId); + + orders.erase(orders.begin() + id); + currOrderStarted = false; + } + + void Unit::select() { + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + + if(mainPlayer == player && mainPlayerSelecting && selectionSfx) + selectionSfx->play(); + + orderLineDispTime = getTime(); + } +} diff --git a/source/gameplay/gameObjects/units/unit.h b/source/gameplay/gameObjects/units/unit.h new file mode 100644 index 0000000..c5a8eac --- /dev/null +++ b/source/gameplay/gameObjects/units/unit.h @@ -0,0 +1,195 @@ +#ifndef UNIT_H +#define UNIT_H + +#include +#include +#include +#include + +#include + +#include "gameObject.h" +#include "buildableUnit.h" +#include "fxManager.h" + +namespace sf{ + class SoundBuffer; + class Sound; +} + +namespace vb01{ + class Mesh; + class Quad; + class Node; + class Light; + class Camera; +} + +namespace battleship{ + class Player; + class Weapon; + class GameObject; + class Vehicle; + class Projectile; + + struct Order { + enum class TYPE {ATTACK, BUILD, MOVE, GARRISON, EJECT, PATROL, LAUNCH, SUPPLY, LOAD, UNLOAD, HACK}; + struct Target{ + GameObject *unit = nullptr; + vb01::Vector3 pos; + + Target(){} + Target(GameObject *u, vb01::Vector3 p = vb01::Vector3::VEC_ZERO) : unit(u), pos(p){} + }; + + TYPE type; + int lineId = -1; + bool playerAssigned = true; + vb01::Vector3 direction; + std::vector targets; + + Order(){} + Order(TYPE t, std::vector targ, vb01::Vector3 dir = vb01::Vector3::VEC_ZERO, int lid = -1, bool pa = true) : + type(t), + playerAssigned(pa), + lineId(lid), + targets(targ), + direction(dir){} + }; + + enum class MoveDir {LEFT, UP, FORW}; + enum class Corner {FRONT_LEFT, FRONT_RIGHT, REAR_LEFT, REAR_RIGHT}; + enum class UnitType {UNDERWATER, SEA_LEVEL, HOVER, LAND, AIR, NONE = -1}; + enum class UnitClass { + MECH, + TANK, + ARTILLERY, + ROBO_ENGINEER, + CYBORG_ENGINEER, + TRANSPORT, + RESOURCE_ROVER, + CRUISER, + ANTI_SUB_CRUISER, + CARRIER, + SUBMARINE, + MISSILE_SUBMARINE, + ICEBREAKER, + FREEZER, + EMP_BOAT, + LAND_FACTORY, + NAVAL_FACTORY, + TRADE_CENTER, + LAB, + POINT_DEFENSE, + EXTRACTOR, + REFINERY, + ICE_SHEET + }; + + class Unit : public GameObject{ + public: + struct GarrisonSlot{ + Vehicle *vehicle = nullptr; + int category; + vb01::Node *background, *foreground; + vb01::Vector2 offset; + + GarrisonSlot(vb01::Node *bg, vb01::Node *fg, vb01::Vector2 off, int cat, Vehicle *v = nullptr) : background(bg), foreground(fg), offset(off), category(cat), vehicle(v){} + }; + + enum class State {CHASE, STAND_GROUND, HOLD_FIRE}; + enum class Condition{ABLE, FROZEN, EM_JAMMED}; + + Unit(Player*, int, vb01::Vector3, vb01::Quaternion, State = State::STAND_GROUND); + virtual ~Unit(); + virtual void update(); + virtual void halt(); + void updateGarrison(Vehicle*, bool); + virtual void select(); + void setOrder(Order); + std::vector getProjectiles(); + virtual void receiveOrder(Order, bool); + bool canGarrison(Vehicle*); + void initLosLight(); + void destroyLosLight(); + int getNumFreeGarrisonSlots(); + inline void setState(State s){state = s;} + inline State getState(){return state;} + inline int getNumGarrisonSlots(){return garrisonSlots.size();} + inline bool isGarrisonEmpty(){return getNumFreeGarrisonSlots() == getNumGarrisonSlots();} + inline const std::vector& getGarrisonSlots(){return garrisonSlots;} + inline float getLineOfSight() {return lineOfSight;} + inline UnitType getType() {return type;} + inline UnitClass getUnitClass() {return unitClass;} + inline int getPlayerId() {return playerId;} + inline bool isVehicle(){return vehicle;} + inline bool isTargetToTheRight(vb01::Vector3 dir, vb01::Vector3 lv){return lv.getAngleBetween(dir) > vb01::PI / 2;} + inline Order getOrder(int i){return orders[i];} + inline int getNumOrders(){return orders.size();} + inline std::string getGuiScreen(){return guiScreen;} + inline std::vector getBuildableUnits(){return buildableUnits;} + inline int getNumBuildableUnits(){return buildableUnits.size();} + inline BuildableUnit getBuildableUnit(int i){return buildableUnits[i];} + inline vb01::Node* getLosLightNode(){return losLightNode;} + inline Condition getCondition(){return condition;} + inline void setCondition(Condition cond){ + this->condition = cond; + + if(cond == Condition::EM_JAMMED) lastJamTime = vb01::getTime(); + } + private: + void renderOrderLine(bool); + void initWeapons(); + void destroyWeapons(); + bool validateOrder(Order); + inline bool canDisplayOrderLine(){return vb01::getTime() - orderLineDispTime < orderVecDispLength;} + + const int orderVecDispLength = 2000; + sf::SoundBuffer *selectionSfxBuffer; + sf::Sound *selectionSfx = nullptr; + vb01::Node *losLightNode = nullptr; + bool vehicle, currOrderStarted = false, alignToSurface = false; + Condition condition = Condition::ABLE; + protected: + UnitType type; + UnitClass unitClass; + std::vector orders; + std::string guiScreen = ""; + int playerId, restartTime, lenHpBar = 200; + vb01::s64 orderLineDispTime = 0, lastFireTime = 0, lastJamTime = 0; + float lineOfSight; + std::vector weapons; + std::vector garrisonSlots; + std::vector buildableUnits; + State state = State::STAND_GROUND; + vb01::Node *hpBackgroundNode = nullptr, *hpForegroundNode = nullptr; + + void placeAt(vb01::Vector3); + std::vector getSelectingPlayers(); + void removeOrder(int); + virtual void startCurrentOrder(){currOrderStarted = true;} + virtual bool validateLaunchOrder(){return !getWeaponsByOrder(Order::TYPE::LAUNCH).empty();} + virtual bool validateGarrisonOrder(Order){return false;} + virtual void autoAttackTargets(); + virtual void reinit(); + virtual void initProperties(); + virtual void destroySound(); + virtual void initSound(); + virtual void executeOrders(); + virtual void eject(Order); + virtual void attack(Order); + virtual void garrison(Order){} + virtual void build(Order){} + virtual void move(Order){} + virtual void patrol(Order){} + virtual void launch(Order); + virtual void handleResources(Order){} + virtual void hack(Order){} + virtual void freeze(Order){} + float calculateRotation(vb01::Vector3, float, float); + std::vector getWeaponsByOrder(Order::TYPE); + std::vector getWeaponsByType(int); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/vehicles/engineer.cpp b/source/gameplay/gameObjects/units/vehicles/engineer.cpp new file mode 100644 index 0000000..e8755e9 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/engineer.cpp @@ -0,0 +1,81 @@ +#include "engineer.h" +#include "activeGameState.h" +#include "destructable.h" +#include "structure.h" +#include "player.h" +#include "game.h" +#include "map.h" + +#include +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + Engineer::Engineer(Player *player, int id, Vector3 pos, Quaternion rot, Unit::State state) : Vehicle(player, id, pos, rot, state){ + Game *game = Game::getSingleton(); + vector currTechs = player->getTechnologies(); + hackRange = generateView()["units"][id + 1]["hackRange"]; hackRange += game->calcAbilFromTech(Ability::Type::HACK_RANGE, currTechs, (int)GameObject::type, id); + + Vector2 size = Vector2(lenHpBar, 10); + hackStatusBackground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 0, 1)); + hackStatusForeground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(1, 0, 1, 1)); + } + + Engineer::~Engineer(){ + destructable->removeBar(hackStatusBackground); + destructable->removeBar(hackStatusForeground); + } + + void Engineer::update(){ + Vehicle::update(); + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = Unit::getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + + if(hackStatus < 100) + destructable->displayStats(hackStatusForeground, hackStatusBackground, hackStatus, 100, mainPlayer == player && mainPlayerSelecting, Vector2(0, -10)); + else{ + hackStatusBackground->setVisible(false); + hackStatusForeground->setVisible(false); + } + + if(orders.empty() || orders[0].type != Order::TYPE::HACK) + hackStatus = 0; + } + + //TODO change unit colors after faction change + void Engineer::hack(Order order){ + Unit *targUnit = (Unit*)order.targets[0].unit; + bool withinRange = (targUnit->getPos().getDistanceFrom(pos) < hackRange); + int hackRate = int(generateView()["units"][id + 1]["hackTime"]) / 100; + bool canHack = (getTime() - lastHackTime > hackRate); + + if(withinRange && canHack){ + hackStatus++; + pursuingTarget = false; + } + else if(!withinRange){ + hackStatus = 0; + + if(!pursuingTarget){ + preparePathpoints(order, targUnit->getPos()); + pursuingTarget = true; + } + else + navigateToTarget(.9 * hackRange); + } + + if(hackStatus >= 100){ + order.targets[0].unit->changePlayer(player); + removeOrder(0); + } + } +} diff --git a/source/gameplay/gameObjects/units/vehicles/engineer.h b/source/gameplay/gameObjects/units/vehicles/engineer.h new file mode 100644 index 0000000..efa71b5 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/engineer.h @@ -0,0 +1,30 @@ +#ifndef ENGINEER_H +#define ENGINEER_H + +#include + +#include "vehicle.h" + +namespace vb01{ + class Node; +} + +namespace battleship{ + class Structure; + + class Engineer : public Vehicle{ + public: + Engineer(Player*, int, vb01::Vector3, vb01::Quaternion, Unit::State); + ~Engineer(); + void update(); + private: + int hackStatus = 0; + float hackRange; + vb01::s64 lastHackTime = 0; + vb01::Node *hackStatusBackground = nullptr, *hackStatusForeground = nullptr; + + void hack(Order); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/vehicles/freezer.cpp b/source/gameplay/gameObjects/units/vehicles/freezer.cpp new file mode 100644 index 0000000..5450a0a --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/freezer.cpp @@ -0,0 +1,39 @@ +#include "freezer.h" +#include "weapon.h" +#include "structure.h" +#include "destructable.h" + +#include + +namespace battleship{ + using namespace vb01; + using namespace std; + + //TODO rename the class to a more generic name + Freezer::Freezer(Player *player, int id, Vector3 pos, Quaternion rot, Unit::State state) : Vehicle(player, id, pos, rot, state){} + + void Freezer::attack(Order order){ + Vehicle::attack(order); + + Destructable *target = order.targets[0].unit->getDestructable(); + + if(target && target->getFreezeStatus() >= 100) + removeOrder(0); + } + + void Freezer::build(Order order){ + vector weapons = getWeaponsByType((int)Weapon::Type::FREEZER); + + if(weapons.empty()) return; + + Unit *targ = (Unit*)order.targets[0].unit; + + for(Weapon *w : weapons) + w->trackTarget(targ->getPos()); + + Vehicle::build(order); + + if(targ->getUnitClass() == UnitClass::ICE_SHEET && ((Structure*)targ)->isComplete()) + removeOrder(0); + } +} diff --git a/source/gameplay/gameObjects/units/vehicles/freezer.h b/source/gameplay/gameObjects/units/vehicles/freezer.h new file mode 100644 index 0000000..89459aa --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/freezer.h @@ -0,0 +1,16 @@ +#ifndef FREEZER_H +#define FREEZER_H + +#include "vehicle.h" + +namespace battleship{ + class Freezer : public Vehicle{ + public: + Freezer(Player*, int, vb01::Vector3, vb01::Quaternion, Unit::State); + private: + void attack(Order); + void build(Order); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/vehicles/resourceRover.cpp b/source/gameplay/gameObjects/units/vehicles/resourceRover.cpp new file mode 100644 index 0000000..c4aec9d --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/resourceRover.cpp @@ -0,0 +1,203 @@ +#include "resourceRover.h" +#include "resourceDeposit.h" +#include "destructable.h" +#include "map.h" +#include "game.h" +#include "player.h" +#include "extractor.h" +#include "structure.h" +#include "activeGameState.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + ResourceRover::ResourceRover(Player *player, int id, Vector3 pos, Quaternion rot, Unit::State state) : Vehicle(player, id, pos, rot, state) { + Vector2 size = Vector2(lenHpBar, 10); + loadBackground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(0, 0, 0, 1)); + loadForeground = destructable->createBar(Vector2::VEC_ZERO, size, Vector4(1, 1, 0, 1)); + + initProperties(); + } + + ResourceRover::~ResourceRover(){ + destructable->removeBar(loadForeground); + destructable->removeBar(loadBackground); + } + + void ResourceRover::initProperties(){ + Game *game = Game::getSingleton(); + vector currTechs = player->getTechnologies(); + + sol::table unitTable = generateView()["units"][id + 1]; + capacity = unitTable["capacity"]; capacity += game->calcAbilFromTech(Ability::Type::CAPACITY, currTechs, (int)GameObject::type, id); + loadSpeed = unitTable["loadSpeed"]; loadSpeed += game->calcAbilFromTech(Ability::Type::LOAD_SPEED, currTechs, (int)GameObject::type, id); + loadRate = unitTable["loadRate"]; loadRate += game->calcAbilFromTech(Ability::Type::LOAD_RATE, currTechs, (int)GameObject::type, id); + } + + //TODO implement a check of whether a vehicle is next to a building's outline + void ResourceRover::loadResources(Structure *targStruct, bool loadResource){ + float w = targStruct->getWidth(), l = targStruct->getLength(); + bool closeEnough = (pos.getDistanceFrom(targStruct->getPos()) <= sqrt(l * l + w * w)); + if(!closeEnough) return; + + ResourceType resType; + + switch(targStruct->getUnitClass()){ + case UnitClass::TRADE_CENTER: + resType = ResourceType::WEALTH; + break; + case UnitClass::REFINERY: + resType = ResourceType::REFINEDS; + break; + case UnitClass::LAB: + resType = ResourceType::RESEARCH; + break; + } + + if(player == targStruct->getPlayer()){ + if(!loadResource && canUnload((int)resType)){ + player->updateResource(resType, loadSpeed, true); + cargo[(int)resType] -= loadSpeed; + lastLoadTime = getTime(); + } + else if(loadResource && canLoad() && player->getResource(resType) > 0){ + player->updateResource(resType, -loadSpeed, true); + cargo[(int)resType] += loadSpeed; + lastLoadTime = getTime(); + } + } + else{ + vector offers = player->getTradeOffers(targStruct->getPlayer()); + if(offers.empty()) return; + + TradeOffer *offer = offers[0]; + + if(!loadResource && offer->tradeResources[(int)resType][1] > offer->deliveredResources[(int)resType][1] && canUnload((int)resType)){ + targStruct->getPlayer()->updateResource(resType, loadSpeed, true); + offer->deliveredResources[(int)resType][1]++; + cargo[(int)resType] -= loadSpeed; + lastLoadTime = getTime(); + } + else if( + loadResource && + offer->tradeResources[(int)resType][0] > offer->deliveredResources[(int)resType][0] && + targStruct->getPlayer()->getResource(resType) > 0 && + canLoad() + ) + { + targStruct->getPlayer()->updateResource(resType, -loadSpeed, true); + offer->deliveredResources[(int)resType][0]++; + cargo[(int)resType] += loadSpeed; + lastLoadTime = getTime(); + } + } + } + + //TODO equate rover load and extractor draw rates + void ResourceRover::collectRefineds(Order order){ + vector units = player->getUnits(); + vector extractors, refineries; + + for(Unit *unit : units){ + if(unit->getUnitClass() == UnitClass::EXTRACTOR && ((Extractor*)unit)->getDeposit()->getAmmount() > 0) + extractors.push_back((Structure*)unit); + else if(unit->getUnitClass() == UnitClass::REFINERY) + refineries.push_back((Structure*)unit); + } + + nearestExtractor = (Extractor*)getClosestUnit(extractors); + nearestRefinery = getClosestUnit(refineries); + + auto closeEnough = [](GameObject *obj, Vector3 pos){ + Vector3 neVec = pos - obj->getPos(); + float angle = obj->getDirVec().getAngleBetween(neVec.norm()); + + if(angle > PI / 2) angle = PI - angle; + + return cos(angle) * neVec.getLength() < .5 * obj->getLength(); + }; + + if(nearestExtractor && closeEnough(nearestExtractor, pos)){ + ResourceDeposit *deposit = nearestExtractor->getDeposit(); + + if(canLoad() && deposit->getAmmount() > 0){ + if(nearestExtractor->canDraw()){ + nearestExtractor->draw(); + cargo[(int)ResourceType::REFINEDS] += loadSpeed; + lastLoadTime = getTime(); + } + } + else if(calcTotalLoad() == capacity && nearestRefinery){ + order.targets[0].unit = nearestRefinery; + preparePathpoints(order, nearestRefinery->getPos()); + } + } + else if(!nearestExtractor){ + if(nearestRefinery && cargo[(int)ResourceType::REFINEDS] > 0){ + order.targets[0].unit = nearestRefinery; + preparePathpoints(order, nearestRefinery->getPos()); + } + else removeOrder(0); + } + + if(nearestRefinery && closeEnough(nearestRefinery, pos)){ + if(canUnload((int)ResourceType::REFINEDS)){ + cargo[(int)ResourceType::REFINEDS] -= loadSpeed; + player->updateResource(ResourceType::REFINEDS, loadSpeed, true); + lastLoadTime = getTime(); + } + else if(cargo[(int)ResourceType::REFINEDS] == 0 && nearestExtractor){ + order.targets[0].unit = nearestExtractor; + preparePathpoints(order, nearestExtractor->getPos()); + } + } + } + + void ResourceRover::handleResources(Order order){ + if(!pathPoints.empty()) navigate(.01); + else if(order.type == Order::TYPE::SUPPLY) + collectRefineds(order); + else + loadResources((Structure*)order.targets[0].unit, order.type == Order::TYPE::LOAD); + } + + void ResourceRover::update(){ + Vehicle::update(); + + vector units = player->getUnits(); + nearestExtractor = (find(units.begin(), units.end(), nearestExtractor) != units.end() ? nearestExtractor : nullptr); + nearestRefinery = (find(units.begin(), units.end(), nearestRefinery) != units.end() ? nearestRefinery : nullptr); + + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); + Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); + + vector selectingPlayers = getSelectingPlayers(); + bool mainPlayerSelecting = (activeState && find(selectingPlayers.begin(), selectingPlayers.end(), mainPlayer) != selectingPlayers.end()); + + destructable->displayStats(loadForeground, loadBackground, calcTotalLoad(), capacity, mainPlayer == player && mainPlayerSelecting, Vector2(0, -10)); + } + + Unit* ResourceRover::getClosestUnit(vector structs){ + if(structs.empty()) return nullptr; + + int minDistId = -1; + + for(int i = 0; i < structs.size(); i++) + if(structs[i]->isComplete()){ + minDistId = i; + break; + } + + if(minDistId == -1) return nullptr; + + for(int i = 0; i < structs.size(); i++) + if(structs[i]->isComplete() && structs[minDistId]->getPos().getDistanceFrom(pos) > structs[i]->getPos().getDistanceFrom(pos)) + minDistId = i; + + return structs[minDistId]; + } +} diff --git a/source/gameplay/gameObjects/units/vehicles/resourceRover.h b/source/gameplay/gameObjects/units/vehicles/resourceRover.h new file mode 100644 index 0000000..183d0a1 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/resourceRover.h @@ -0,0 +1,37 @@ +#ifndef RESOURCE_ROVER_H +#define RESOURCE_ROVER_H + +#include "vehicle.h" + +#include + +namespace battleship{ + class Structure; + class Extractor; + + class ResourceRover : public Vehicle{ + public: + ResourceRover(Player*, int, vb01::Vector3, vb01::Quaternion, Unit::State = Unit::State::STAND_GROUND); + ~ResourceRover(); + void update(); + inline int getLoad(int id){return cargo[id];} + inline int calcTotalLoad(){return cargo[0] + cargo[1] + cargo[2];} + inline int getCapacity(){return capacity;} + inline bool canLoad(){return vb01::getTime() - lastLoadTime > loadRate && calcTotalLoad() < capacity;} + inline bool canUnload(int id){return vb01::getTime() - lastLoadTime > loadRate && cargo[id] > 0;} + private: + void initProperties(); + void collectRefineds(Order); + void loadResources(Structure*, bool); + void handleResources(Order); + Unit* getClosestUnit(std::vector); + + vb01::s64 lastLoadTime = 0; + int cargo[3]{0, 0, 0}, capacity, loadRate, loadSpeed; + Extractor *nearestExtractor = nullptr; + Unit *nearestRefinery = nullptr; + vb01::Node *loadBackground = nullptr, *loadForeground = nullptr; + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/vehicles/submarine.cpp b/source/gameplay/gameObjects/units/vehicles/submarine.cpp new file mode 100644 index 0000000..2fc1110 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/submarine.cpp @@ -0,0 +1,27 @@ +#include "submarine.h" +#include "map.h" + +#include + +namespace battleship{ + using namespace vb01; + using namespace std; + + Submarine::Submarine(Player *player, int id, Vector3 pos, Quaternion rot, Unit::State state) : Vehicle(player, id, pos, rot, state){} + + bool Submarine::validateLaunchOrder(){ + if(!Unit::validateLaunchOrder()) return false; + + vector terrainNodes = Map::getSingleton()->getNodeParent()->getChildren(); + + for(int i = 1; i < terrainNodes.size(); i++){ + Vector3 wbPos = terrainNodes[i]->getPosition(); + Vector3 size = ((Quad*)terrainNodes[i]->getMesh(0))->getSize(); + + if(fabs(wbPos.x - pos.x) < .5 * size.x && fabs(wbPos.z - pos.z) < .5 * size.y && pos.y - wbPos.y > -.1) + return true; + } + + return false; + } +} diff --git a/source/gameplay/gameObjects/units/vehicles/submarine.h b/source/gameplay/gameObjects/units/vehicles/submarine.h new file mode 100644 index 0000000..4e1d914 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/submarine.h @@ -0,0 +1,15 @@ +#ifndef SUBMARINE_H +#define SUBMARINE_H + +#include "vehicle.h" + +namespace battleship{ + class Submarine : public Vehicle{ + public: + Submarine(Player*, int, vb01::Vector3, vb01::Quaternion, Unit::State); + private: + bool validateLaunchOrder(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/units/vehicles/vehicle.cpp b/source/gameplay/gameObjects/units/vehicles/vehicle.cpp new file mode 100644 index 0000000..adeebf5 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/vehicle.cpp @@ -0,0 +1,526 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "pathfinder.h" +#include "destructable.h" +#include "defConfigs.h" +#include "structure.h" +#include "vehicle.h" +#include "weapon.h" +#include "player.h" +#include "game.h" +#include "map.h" + +using namespace gameBase; +using namespace vb01; +using namespace std; + +namespace battleship{ + Vehicle::Vehicle(Player *player, int id, Vector3 pos, Quaternion rot, Unit::State state) : Unit(player, id, pos, rot, state){ + initProperties(); + } + + Vehicle::~Vehicle(){ + removeAllPathpoints(); + } + + void Vehicle::update(){ + Unit::update(); + + if(garrisonable) model->setVisible(false); + } + + void Vehicle::halt(){ + Unit::halt(); + removeAllPathpoints(); + + patrolPointId = 0; + pursuingTarget = false; + } + + void Vehicle::startCurrentOrder(){ + Unit::startCurrentOrder(); + + switch(orders[0].type){ + case Order::TYPE::LAUNCH: + case Order::TYPE::EJECT: + return; + } + + removeAllPathpoints(); + + Vector3 targPos = (orders[0].targets[0].unit ? orders[0].targets[0].unit->getPos() : orders[0].targets[0].pos); + preparePathpoints(orders[0], targPos); + } + + bool Vehicle::validateGarrisonOrder(Order order){ + Unit *targUnit = (Unit*)order.targets[0].unit; + + for(GarrisonSlot slot : targUnit->getGarrisonSlots()) + if(!slot.vehicle && slot.category >= garrisonCategory) + return true; + + return false; + } + + void Vehicle::turn(float angle) { + Quaternion newRot = Quaternion(angle, upVec) * model->getOrientation(); + model->setOrientation(newRot); + rot = newRot; + } + + void Vehicle::advance(float speed, MoveDir moveDir) { + Vector3 dir; + + switch(moveDir){ + case MoveDir::FORW: + dir = dirVec; + break; + case MoveDir::LEFT: + dir = leftVec; + break; + case MoveDir::UP: + dir = upVec; + break; + } + + placeAt(pos + dir * speed); + } + + void Vehicle::initProperties(){ + Game *game = Game::getSingleton(); + vector currTechs = player->getTechnologies(); + + sol::table unitTable = generateView()[GameObject::getGameObjTableName()][id + 1]; + maxTurnAngle = unitTable["maxTurnAngle"]; maxTurnAngle += game->calcAbilFromTech(Ability::Type::MAX_TURN_ANGLE, currTechs, (int)GameObject::type, id); + speed = unitTable["speed"]; speed += game->calcAbilFromTech(Ability::Type::SPEED, currTechs, (int)GameObject::type, id); + anglePrecision = unitTable["anglePrecision"]; + garrisonCategory = unitTable["garrisonCategory"]; + } + + void Vehicle::reinit(){ + Unit::reinit(); + initProperties(); + } + + void Vehicle::arrivedAtPathpoint(bool byPlane, float vertDist){ + bool orderHasDir = (orders[0].direction != Vector3::VEC_ZERO); + float angleToOrderDir = dirVec.getAngleBetween(orders[0].direction); + bool destDirWithin = (!orderHasDir || (orderHasDir && angleToOrderDir <= anglePrecision)); + + if(pathPoints.size() == 1 && !destDirWithin) + turn(calculateRotation(orders[0].direction, angleToOrderDir, maxTurnAngle)); + + if(byPlane && ( + (pathPoints.size() > 1 || (pathPoints.size() == 1 && destDirWithin)) && + (type != UnitType::UNDERWATER || (type == UnitType::UNDERWATER && vertDist < 0.5 * height)) + ) + ){ + removePathpoint(); + } + else if(!byPlane && (pathPoints.size() > 1 || (pathPoints.size() == 1 && destDirWithin))) + removePathpoint(); + } + + void Vehicle::moveByTerrainQuads(Vector3 hypVec, float destOffset){ + Map *map = Map::getSingleton(); + Quad *terrQuad = (Quad*)map->getNodeParent()->getChild(0)->getMesh(0); + int numVertDiv = configData::NUM_SUBDIVS, numHorDiv = configData::NUM_SUBDIVS; + Vector3 mapSize = map->getMapSize(); + Vector2 sqIdsVec = terrQuad->getSubquadIds(numVertDiv, numHorDiv, pos, mapSize); + Vector2 sqIdsEndVec = terrQuad->getSubquadIds(numVertDiv, numHorDiv, pathPoints[0], mapSize); + + int sqIds[]{sqIdsVec.x, sqIdsVec.y}; + vector points = vector{pos}; + + // y = ax + b + float a = hypVec.z / hypVec.x; + float b = pos.z - a * pos.x; + + while(!(sqIds[0] == (int)sqIdsEndVec.x && sqIds[1] == (int)sqIdsEndVec.y)){ + Vector3 c1 = terrQuad->getSubquadCorner(sqIds[0], sqIds[1], numVertDiv, numHorDiv, true, false); //top left + Vector3 c2 = terrQuad->getSubquadCorner(sqIds[0], sqIds[1], numVertDiv, numHorDiv, true, true); //top right + Vector3 c3 = terrQuad->getSubquadCorner(sqIds[0], sqIds[1], numVertDiv, numHorDiv, false, false); //bottom left + Vector3 c4 = terrQuad->getSubquadCorner(sqIds[0], sqIds[1], numVertDiv, numHorDiv, false, true); //bottom right + + float minX = c1.x, maxX = c2.x, minY = c1.z, maxY = c3.z; + bool bottom = (hypVec.z > 0), left = (hypVec.x < 0); + float intersecX = (left ? c1.x : c2.x); + float intersecY = (bottom ? c3.z : c1.z); + + float xSolY = a * intersecX + b; + float ySolX = (hypVec.x != 0 ? (intersecY - b) / a : points[points.size() - 1].x); + + float diff, vertDiff, x, y, z; + + if(minY <= xSolY && xSolY <= maxY){ + diff = (xSolY - c1.z) / (c3.z - c1.z); + vertDiff = (left ? c3.y - c1.y : c4.y - c2.y); + + x = (left ? c1.x : c2.x); + y = (left ? c1.y : c2.y) + vertDiff * diff; + z = xSolY; + + sqIds[0] += (left ? -1 : 1); + } + else if(hypVec.x == 0 || (minX <= ySolX && ySolX <= maxX)){ + diff = (ySolX - c1.x) / (c2.x - c1.x); + vertDiff = (bottom ? c4.y - c3.y : c2.y - c1.y); + + x = ySolX; + y = (bottom ? c3.y : c1.y) + vertDiff * diff; + z = (bottom ? c3.z : c1.z); + + sqIds[1] += (bottom ? 1 : -1); + } + + points.push_back(Vector3(x, y, z)); + } + + points.push_back(pathPoints[0]); + + float movementAmmount = speed, totalDist = 0, eps = .01; + Vector3 endPos = pos; + + for(int i = 0; i < points.size() - 1; i++){ + Vector3 diffVec = (points[i + 1] - points[i]).norm(); + float dist = points[i].getDistanceFrom(points[i + 1]); + float diff = dist; + + if(fabs(totalDist + dist - movementAmmount) > eps) + diff = movementAmmount - totalDist; + + endPos += diffVec * diff; + totalDist += diff; + } + + placeAt(endPos); + + if(fabs(pathPoints[0].x - pos.x) <= destOffset && fabs(pathPoints[0].z - pos.z) <= destOffset) + arrivedAtPathpoint(false); + } + + void Vehicle::moveByPlane(Vector3 hypVec, float destOffset){ + Vector3 linDest = Vector3(pathPoints[0].x, pos.y, pathPoints[0].z); + + if(pos.getDistanceFrom(linDest) > destOffset){ + float dist = pos.getDistanceFrom(linDest); + float movementAmmount = (speed > dist ? dist : speed); + advance(movementAmmount); + } + + float vertDist = fabs(pos.y - pathPoints[0].y); + + if(vertDist > .1){ + float dist = pos.y - pathPoints[0].y; + float movementAmmount = (speed > fabs(dist) ? dist : speed); + + if(dist > 0) movementAmmount *= -1; + + advance(movementAmmount, MoveDir::UP); + } + + if(pos.getDistanceFrom(linDest) <= destOffset) + arrivedAtPathpoint(true, vertDist); + } + + void Vehicle::navigate(float destOffset){ + if(pathPoints.empty()) return; + + Vector3 hypVec = (pathPoints[0] - pos); + Vector3 baseDir = getVecToPlane(pos, hypVec, upVec); + if(baseDir == Vector3::VEC_ZERO) baseDir = dirVec; + + float angle = baseDir.getAngleBetween(dirVec); + + if(angle > anglePrecision && pos.getDistanceFrom(pathPoints[0]) > destOffset) + turn(calculateRotation(baseDir, angle, maxTurnAngle)); + else if(type == UnitType::LAND) + moveByTerrainQuads(hypVec, destOffset); + else + moveByPlane(hypVec, destOffset); + } + + void Vehicle::move(Order order) { + navigate(0.5 * Map::getSingleton()->getCellSize().x); + + if(pathPoints.empty()) + removeOrder(0); + } + + void Vehicle::exitGarrisonable(Vector3 exitPos){ + placeAt(exitPos); + garrisonable->updateGarrison(this, false); + garrisonable = nullptr; + } + + void Vehicle::enterGarrisonable(){ + Unit *targUnit = (Unit*)orders[0].targets[0].unit; + targUnit->updateGarrison(this, true); + + removeAllPathpoints(); + removeOrder(0); + + garrisonable = targUnit; + pursuingTarget = false; + } + + void Vehicle::navigateToTarget(float minDist){ + if(!pursuingTarget){ + Vector3 targPos = (orders[0].targets[0].unit ? orders[0].targets[0].unit->getPos() : orders[0].targets[0].pos); + preparePathpoints(orders[0], targPos); + pursuingTarget = true; + + if(orders[0].type == Order::TYPE::GARRISON) addPathpoint(targPos); + } + + navigate(minDist); + } + + void Vehicle::garrison(Order order){ + Unit *targUnit = (Unit*)order.targets[0].unit; + float distToGarrisonable = pos.getDistanceFrom(targUnit->getPos()), garrisonDist = Map::getSingleton()->getCellSize().x; + + if(distToGarrisonable > garrisonDist) + navigateToTarget(garrisonDist); + else + enterGarrisonable(); + } + + void Vehicle::patrol(Order order){ + if(pathPoints.empty()){ + patrolPointId = getNextPatrolPointId(order.targets.size()); + preparePathpoints(order, order.targets[patrolPointId].pos); + } + + navigate(.5 * Map::getSingleton()->getCellSize().x); + } + + void Vehicle::addPathpoint(Vector3 pointPos){ + pathPoints.push_back(pointPos); + + Box *b = new Box(Vector3::VEC_IJK); + b->setMaterial(player->getColorMaterial()); + + Node *n = new Node(pointPos); + n->attachMesh(b); + n->setVisible(Game::getSingleton()->isDebug()); + Root::getSingleton()->getRootNode()->attachChild(n); + debugPathPoints.push_back(n); + } + + //TODO allow ships to attack land targets and vice versa + //TODO recursively search for vacant dest cell neibourghss + bool Vehicle::canReachTarget(Vector3 destPos, int &source, int &dest){ + Map *map = Map::getSingleton(); + vector &cells = map->getCells(); + + source = map->getCellId(pos); + bool ship = (type == UnitType::UNDERWATER || type == UnitType::SEA_LEVEL); + bool waterVehCanMove = (ship && cells[source].type == Map::Cell::WATER); + bool landVehCanMove = (type == UnitType::LAND && cells[source].type == Map::Cell::LAND); + + if(type != UnitType::HOVER && !(waterVehCanMove || landVehCanMove)) return false; + + dest = map->getCellId(destPos); + + if(type != UnitType::UNDERWATER && type == UnitType::SEA_LEVEL && fabs(destPos.y - cells[dest].pos.y) > .1) return false; + + if(cells[dest].blockedBy){ + vector surrCellIds = map->getSurroundingCells(cells[dest].pos, 1); + + for(int scid : surrCellIds){ + if(!cells[scid].blockedBy) + switch(type){ + case UnitType::HOVER: + dest = scid; + return true; + case UnitType::LAND: + if(cells[scid].type == Map::Cell::LAND){ + dest = scid; + return true; + } + break; + case UnitType::SEA_LEVEL: + case UnitType::UNDERWATER: + if(cells[scid].type == Map::Cell::WATER){ + dest = scid; + return true; + } + break; + } + } + + return false; + } + else return true; + } + + bool Vehicle::truncatePath(Order &order, vector &path, Vector3 destPos, bool appendDestPos){ + bool pathTruncated = false; + bool ship = (type == UnitType::UNDERWATER || type == UnitType::SEA_LEVEL); + vector &cells = Map::getSingleton()->getCells(); + + for(int i = 1; i < path.size(); i++){ + if((ship && cells[path[i]].type != Map::Cell::WATER) || (order.type != Order::TYPE::GARRISON && type == UnitType::LAND && cells[path[i]].type != Map::Cell::LAND)){ + path = vector(path.begin(), path.begin() + i); + order.targets[0].unit = nullptr; + order.targets[0].pos = cells[path[i - 1]].pos; + pathTruncated = true; + break; + } + else if(order.type == Order::TYPE::GARRISON && type == UnitType::LAND && cells[path[i]].type != Map::Cell::LAND && path.size() - 1 != i) + return false; + } + + for(int p : path) addPathpoint(cells[p].pos); + + if(order.targets[0].unit && !pathTruncated){ + GameObject *targObj = order.targets[0].unit; + + for(int i = path.size() - 1; i >= 0; i--){ + Vector3 targPos = targObj->getPos(); + Vector3 pointDir = cells[path[i]].pos - targPos; + pointDir = Vector3(pointDir.x, 0, pointDir.z); + + float dirAngle = targObj->getDirVec().getAngleBetween(pointDir.norm()); + if(dirAngle > PI / 2) dirAngle = PI - dirAngle; + + float pointDist = pointDir.getLength(); + + float length = targObj->getLength(); + float lengthComp = pointDist * cos(dirAngle); + bool withinLength = (.5 * length >= lengthComp); + + float width = targObj->getWidth(); + float widthComp = pointDist * sin(dirAngle); + bool withinWidth = (.5 * width >= widthComp); + + if(!(withinLength && withinWidth)){ + float a1 = atan((.5 * width) / (.5 * length)); + float a2 = atan((.5 * length) / (.5 * width)); + + float leftAngle = targObj->getLeftVec().getAngleBetween(pointDir.norm()); + if(leftAngle > PI / 2) leftAngle = PI - leftAngle; + + float dist; + + if(dirAngle <= a1) dist = (.5 * length) / cos(dirAngle); + else if(leftAngle <= a2) dist = (.5 * width) / cos(leftAngle); + + addPathpoint(targPos + pointDir.norm() * dist); + pathTruncated = true; + + break; + } + else removePathpoint(pathPoints.size() - 1); + } + } + + if(appendDestPos && !pathTruncated) + addPathpoint(destPos); + + return true; + } + + void Vehicle::preparePathpoints(Order &order, Vector3 destPos, bool appendDestPos){ + removeAllPathpoints(); + + vector &cells = Map::getSingleton()->getCells(); + int source, dest; + + if(!canReachTarget(destPos, source, dest)) return; + + vector heurs; + Pathfinder *pf = Pathfinder::getSingleton(); + vector path = pf->findPath(cells, heurs, source, dest, (int)type); + + if(path.empty() || !truncatePath(order, path, destPos, appendDestPos)) return; + + path.erase(path.begin()); + } + + void Vehicle::removePathpoint(int i){ + Node *rootNode = Root::getSingleton()->getRootNode(); + Node *debugPathPointNode = debugPathPoints[i]; + rootNode->dettachChild(debugPathPointNode); + + Mesh *mesh = debugPathPointNode->getMesh(0); + mesh->setMaterial(nullptr); + debugPathPoints.erase(debugPathPoints.begin() + i); + delete debugPathPointNode; + + pathPoints.erase(pathPoints.begin() + i); + + if(pathPoints.empty()) pursuingTarget = false; + } + + void Vehicle::removeAllPathpoints(){ + while(!pathPoints.empty()) + removePathpoint(); + } + + void Vehicle::attack(Order order){ + int prevNumOrders = orders.size(); + Unit::attack(order); + int currNumOrders = orders.size(); + + if(prevNumOrders != currNumOrders) return; + + Order::Target target = order.targets[0]; + Vector3 targVec = (target.unit ? target.unit->getPos() : target.pos) - pos; + float distToTarg = targVec.getLength(); + + vector attackWeapons = getWeaponsByOrder(Order::TYPE::ATTACK); + Weapon *weapon = attackWeapons[0]; + + for(Weapon *w : attackWeapons) + if(w->getMaxRange() > weapon->getMaxRange()) + weapon = w; + + float minDist = weapon->getMaxRange(); + + if(order.playerAssigned || (!order.playerAssigned && state == Unit::State::CHASE)){ + if(distToTarg > minDist) + navigateToTarget(.5 * Map::getSingleton()->getCellSize().x); + else + pursuingTarget = false; + } + else if(!order.playerAssigned && state == Unit::State::STAND_GROUND && distToTarg > minDist){ + removeOrder(0); + return; + } + } + + void Vehicle::build(Order order){ + if(pathPoints.empty()){ + Structure *structure = (Structure*)order.targets[0].unit; + sol::table targTable = generateView()["units"][structure->getId()]; + int costRate = (int)targTable["cost"] / 100, buildRate = (int)targTable["buildTime"] / 100; + + if(!structure->isComplete() && player->getResource(ResourceType::REFINEDS) >= costRate && getTime() - lastBuildTime > buildRate){ + structure->incrementBuildStatus(); + player->updateResource(ResourceType::REFINEDS, -costRate, true); + lastBuildTime = getTime(); + } + else if(structure->isComplete()){ + removeOrder(0); + player->incStructuresBuilt(); + } + } + else navigate(0.5 * Map::getSingleton()->getCellSize().x); + } + + void Vehicle::select(){ + if(!garrisonable) + Unit::select(); + } +} diff --git a/source/gameplay/gameObjects/units/vehicles/vehicle.h b/source/gameplay/gameObjects/units/vehicles/vehicle.h new file mode 100644 index 0000000..9b5bf64 --- /dev/null +++ b/source/gameplay/gameObjects/units/vehicles/vehicle.h @@ -0,0 +1,59 @@ +#ifndef VEHICLE_H +#define VEHICLE_H + +#include "unit.h" + +namespace vb01{ + class Material; +} + +namespace battleship{ + class Vehicle : public Unit{ + public: + Vehicle(Player*, int, vb01::Vector3, vb01::Quaternion, Unit::State); + ~Vehicle(); + virtual void update(); + void move(Order); + void exitGarrisonable(vb01::Vector3); + inline Unit* getGarrisonable(){return garrisonable;} + inline int getGarrisonCategory(){return garrisonCategory;} + private: + Unit *garrisonable = nullptr; + int patrolPointId = 0, garrisonCategory; + float speed, maxTurnAngle, anglePrecision; + std::vector debugPathPoints; + vb01::s64 lastBuildTime = 0; + + inline int getNextPatrolPointId(int numPoints) {return patrolPointId == numPoints - 1 ? 0 : patrolPointId + 1;} + bool canReachTarget(vb01::Vector3, int&, int&); + bool truncatePath(Order&, std::vector&, vb01::Vector3, bool); + void startCurrentOrder(); + bool validateGarrisonOrder(Order); + void enterGarrisonable(); + void halt(); + void turn(float); + void advance(float, MoveDir = MoveDir::FORW); + void addPathpoint(vb01::Vector3); + void removePathpoint(int = 0); + void removeAllPathpoints(); + void select(); + void reinit(); + protected: + std::vector pathPoints; + bool pursuingTarget = false; + + void arrivedAtPathpoint(bool, float = 0); + void moveByTerrainQuads(vb01::Vector3, float); + void moveByPlane(vb01::Vector3, float); + void navigate(float = 0.); + void navigateToTarget(float); + void preparePathpoints(Order&, vb01::Vector3, bool = false); + virtual void attack(Order); + virtual void build(Order); + void garrison(Order); + void patrol(Order); + virtual void initProperties(); + }; +} + +#endif diff --git a/source/gameplay/gameObjects/weapon.cpp b/source/gameplay/gameObjects/weapon.cpp new file mode 100644 index 0000000..a14d919 --- /dev/null +++ b/source/gameplay/gameObjects/weapon.cpp @@ -0,0 +1,367 @@ +#include +#include +#include + +#include "map.h" +#include "unit.h" +#include "util.h" +#include "weapon.h" +#include "player.h" +#include "fxManager.h" +#include "destructable.h" +#include "structure.h" +#include "gameObjectFactory.h" +#include "projectile.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + Weapon::Weapon(Unit *u, sol::table unitTable, int wid) : + unit(u), + id(wid), + rateOfFire(unitTable["weapons"][wid + 1]["rateOfFire"]), + damage(unitTable["weapons"][wid + 1]["damage"].get_or(0)) + { + sol::table weaponTable = unitTable["weapons"][wid + 1]; + maxRange = weaponTable["maxRange"]; + maxFireAngle = weaponTable["maxFireAngle"].get_or(.1); + orderType = (Order::TYPE)weaponTable["orderType"]; + + sol::optional typeOpt = unitTable["weapons"][wid + 1]["type"]; + + if(typeOpt != sol::nullopt) + type = (Type)unitTable["weapons"][wid + 1]["type"]; + + sol::optional fireDirOpt = weaponTable["fireDir"]; + + if(fireDirOpt != sol::nullopt){ + sol::table fireDirTbl = weaponTable["fireDir"]; + fireDir = Vector3((float)fireDirTbl["x"], (float)fireDirTbl["y"], (float)fireDirTbl["z"]); + } + + initTargetData(targetUnits, weaponTable, "targetUnits", vector{(int)UnitType::UNDERWATER, (int)UnitType::SEA_LEVEL, (int)UnitType::HOVER, (int)UnitType::LAND}); + initTargetData(targetProjectiles, weaponTable, "targetProjeciles", vector{(int)ProjectileClass::SHELL, (int)ProjectileClass::CRUISE_MISSILE, (int)ProjectileClass::MISSILE, (int)ProjectileClass::TORPEDO, (int)ProjectileClass::DEPTH_CHARGE}); + + initProjectileData(weaponTable); + initNodes(weaponTable); + + sol::optional fireFxOpt = weaponTable["fireFx"]; + + if(fireFxOpt != sol::nullopt){ + FxManager *fxManager = FxManager::getSingleton(); + fireFx = fxManager->initFx(weaponTable["fireFx"], unit->getModel(), true); + + if(fireFx) fxManager->addFx(fireFx); + } + } + + void Weapon::initTargetData(vector &targetVec, sol::table weaponTable, string tblKey, vector allValues){ + sol::optional unitTblOpt = weaponTable[tblKey]; + + if(unitTblOpt != sol::nullopt){ + sol::table tbl = weaponTable[tblKey]; + int tblSize = tbl.size(); + + for(int i = 0; i < tblSize; i++) + targetVec.push_back((int)tbl[i + 1]); + } + else targetVec = allValues; + } + + void Weapon::initNodes(sol::table weaponTable){ + sol::optional nodesTblOpt = weaponTable["nodes"]; + + if(nodesTblOpt == sol::nullopt) return; + + sol::table tbl = weaponTable["nodes"]; + int numNodes = tbl.size(); + + for(int i = 0; i < numNodes; i++){ + sol::table nodeTbl = weaponTable["nodes"][i + 1]; + float rotSpeed = nodeTbl["rotationSpeed"]; + bool vertical = nodeTbl["vertical"]; + + sol::optional angleConstrOpt = nodeTbl["angleConstraints"]; + float minAngle, maxAngle; + + if(angleConstrOpt != sol::nullopt){ + sol::table acTbl = nodeTbl["angleConstraints"]; + minAngle = acTbl["min"]; + maxAngle = acTbl["max"]; + } + else{ + minAngle = 0; + maxAngle = (vertical ? PI / 2 : 0); + } + + string name = nodeTbl["name"]; + Node *node = unit->getModel()->findDescendant(name, true); + + components.push_back(Component(node, rotSpeed, minAngle, maxAngle, vertical)); + } + + //using the last node to determine a weapon's init direction + Node *node = components[components.size() - 1].node; + Vector3 p0 = unit->getModel()->globalToLocalPosition(node->localToGlobalPosition(Vector3::VEC_ZERO)); + Vector3 p1 = unit->getModel()->globalToLocalPosition(node->localToGlobalPosition(Vector3::VEC_K)); + initUnitSpaceDir = (p1 - p0).norm(); + } + + void Weapon::initProjectileData(sol::table weaponTable){ + string projTableKey = "projectile"; + sol::optional proj = weaponTable[projTableKey]; + + if(proj == sol::nullopt) return; + + projId = weaponTable[projTableKey]["id"]; + sol::table projTable = generateView()["projectiles"]; + ProjectileClass pc = (ProjectileClass)projTable[projId + 1]["projectileClass"]; + + if(pc == ProjectileClass::CRUISE_MISSILE){ + minRange = 0; + float rotAngle = projTable[projId + 1]["rotAngle"]; + float speed = projTable[projId + 1]["speed"]; + float base = PI / 2, alpha = 0; + + while(base - alpha > .001){ + minRange += speed * sin(alpha); + alpha += (base - alpha > rotAngle ? rotAngle : base - alpha); + } + + minRange *= 2; + } + + projPar = unit->getModel(); + sol::optional parNameOpt = weaponTable[projTableKey]["parent"]; + + if(parNameOpt != sol::nullopt){ + string parName = weaponTable[projTableKey]["parent"]; + projPar = unit->getModel()->findDescendant(parName, true); + } + + sol::table posTable = weaponTable[projTableKey]["pos"]; + projPos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + sol::table rotTable = weaponTable[projTableKey]["rot"]; + projRot = Quaternion(rotTable["w"], rotTable["x"], rotTable["y"], rotTable["z"]); + } + + Weapon::~Weapon(){ + FxManager *fm = FxManager::getSingleton(); + + if(fireFx) fm->removeFx(fireFx); + } + + //TODO replace unit pos with absolute weapon pos for withinAngle + void Weapon::update(){ + if(unit->getCondition() != Unit::Condition::ABLE) return; + + int numOrders = unit->getNumOrders(); + int ordTp = (numOrders > 0 ? (int)unit->getOrder(0).type : -1); + GameObject *targDestruct = (ordTp != -1 ? unit->getOrder(0).targets[0].unit : nullptr); + + bool isWeaponPos = (!components.empty() && components[0].node); + Vector3 refPos = (isWeaponPos ? components[0].node->localToGlobalPosition(Vector3::VEC_ZERO) : unit->getPos()); + + if(ordTp == (int)orderType){ + Vector3 dirVec; + + if(!isWeaponPos) + dirVec = (fireDir.x * unit->getLeftVec() + fireDir.y * unit->getUpVec() + fireDir.z * unit->getDirVec()).norm(); + else + dirVec = components[components.size() - 1].node->getGlobalAxis(2); + + bool aimedAtTarget = false; + + if(targDestruct && targDestruct->getHitbox()){ + vector res = RayCaster::cast(refPos, dirVec, targDestruct->getHitbox()); + + if(res.empty()) return; + + float targDist = refPos.getDistanceFrom(res[0].pos); + aimedAtTarget = (minRange <= targDist && targDist <= maxRange); + } + else{ + Vector3 targPos = (targDestruct ? targDestruct->getPos() : unit->getOrder(0).targets[0].pos); + float targDist = refPos.getDistanceFrom(targPos); + bool withinRange = (minRange <= targDist && targDist <= maxRange); + bool withinAngle = (dirVec.getAngleBetween((targPos - refPos).norm()) <= maxFireAngle); + aimedAtTarget = withinRange && withinAngle; + } + + if(aimedAtTarget) fire(unit->getOrder(0)); + } + else{ + Vector3 dir = ( + initUnitSpaceDir.x * unit->getLeftVec() + + initUnitSpaceDir.y * unit->getUpVec() + + initUnitSpaceDir.z * unit->getDirVec() + ).norm(); + trackTarget(refPos + dir); + } + } + + void Weapon::useFx(FxManager::Fx *fx, Vector3 targPos, bool fire){ + if(fx && !fire) FxManager::getSingleton()->addFx(fx); + else if(!fx) return; + + fx->toggleComponents(true); + + Vector3 plCol = unit->getPlayer()->getColor(); + + for(int i = 0; i < fx->components.size(); i++){ + FxManager::Fx::Component &comp = fx->components[i]; + + if(!comp.vfx) continue; + + if(fire && ((Node*)comp.comp)->getName() == "laser"){ + Vector3 initPos = unit->getPos(), laserDir = targPos - initPos; + float targDist = laserDir.getLength(); + float angle = unit->getDirVec().getAngleBetween(laserDir); + Vector3 crossProd = unit->getDirVec().cross(laserDir); + + Node *compNode = (Node*)comp.comp; + compNode->setOrientation(Quaternion(angle, crossProd) * compNode->getOrientation()); + + Box *box = (Box*)compNode->getMesh(0); + Vector3 size = box->getSize(); + box->setSize(Vector3(size.x, size.y, targDist)); + box->updateVerts(box->getMeshBase()); + box->getMaterial()->setVec4Uniform("diffuseColor", Vector4(plCol.x, plCol.y, plCol.z, 1)); + + compNode->setPosition(comp.pos + .5 * targDist * Vector3::VEC_K); + } + else if(!fire) + ((Node*)comp.comp)->setPosition(targPos); + } + } + + void Weapon::updateTarget(GameObject *target){ + Destructable *destr = target->getDestructable(); + + switch(type){ + case Type::FREEZER: + if(target->getType() == GameObject::Type::UNIT && ((Unit*)target)->getUnitClass() == UnitClass::ICE_SHEET) + ((Structure*)target)->incrementBuildStatus(); + else + destr->setFreezeStatus(destr->getFreezeStatus() + 1); + + break; + default: + destr->takeDamage(damage); + break; + } + + if(target->getType() == GameObject::Type::UNIT) + unit->getPlayer()->updateGameStats((Unit*)target); + } + + //TODO replace the 'laser' flag literal + void Weapon::fire(Order order){ + if(!canFire()) return; + + GameObject *target = order.targets[0].unit; + Vector3 targPos = (target ? target->getPos() : order.targets[0].pos); + + if(fireFx) useFx(fireFx, targPos, true); + + if(projId == -1){ + sol::table weaponTbl = generateView()["units"][unit->getId() + 1]["weapons"][id + 1]; + FxManager *fxManager = FxManager::getSingleton(); + string fxKey = "unitHitFx"; + + if(target) updateTarget(target); + else{ + Map *map = Map::getSingleton(); + Map::Cell::Type cellType = map->getCells()[map->getCellId(targPos)].type; + fxKey = (cellType == Map::Cell::Type::LAND ? "landHitFx" : "waterHitFx"); + } + + sol::optional hitFxOpt = weaponTbl[fxKey]; + + if(hitFxOpt != sol::nullopt){ + sol::table fxTbl = weaponTbl[fxKey]; + int numFx = fxTbl.size(); + + if(numFx > 0) useFx(fxManager->initFx(weaponTbl[fxKey], unit->getModel(), false), targPos, false); + } + } + else{ + Quaternion r = projPar->localToGlobalOrientation(projRot); + Vector3 p = projPar->localToGlobalPosition(projPos); + unit->getPlayer()->addProjectile(GameObjectFactory::createProjectile(unit, projId, p, r)); + } + + lastFireTime = getTime(); + } + + //TODO improve to allow for vertical alignment + void Weapon::trackTarget(Vector3 targPos){ + for(Component &component : components){ + bool isWeaponPos = components[0].node; + Vector3 weaponPos = (isWeaponPos ? components[0].node->localToGlobalPosition(Vector3::VEC_ZERO) : unit->getPos()); + + Vector3 unitUp = unit->getUpVec(); + Vector3 targDir = (targPos - weaponPos).norm(); + Vector3 nodeDir = component.node->getGlobalAxis(2); + + Vector3 rotAxis; + float rotAngle; + + if(component.vertical){ + float angle1 = unitUp.getAngleBetween(targDir); + float angle2 = unitUp.getAngleBetween(nodeDir); + float angleDiff = angle1 - angle2; + rotAngle = (component.rotSpeed < fabs(angleDiff) ? component.rotSpeed : fabs(angleDiff)) * (angleDiff > 0 ? 1 : -1); + + if(PI / 2 - (angle2 + rotAngle) > component.maxAngle) + rotAngle = PI / 2 - component.maxAngle - angle2; + else if(PI / 2 - (angle2 + rotAngle) < component.minAngle) + rotAngle = PI / 2 - component.minAngle - angle2; + + rotAngle = (fabs(rotAngle) > .001 ? rotAngle : 0); + rotAxis = Vector3::VEC_I; + } + else{ + rotAxis = Vector3::VEC_J; + Vector3 targDirProj = getVecToPlane(weaponPos, targDir, unitUp); + Vector3 nodeLeft = component.node->getGlobalAxis(0); + float angle = nodeDir.getAngleBetween(targDirProj); + bool negate = (nodeLeft.getAngleBetween(targDirProj) > PI / 2); + rotAngle = (negate ? -1 : 1) * (component.rotSpeed < angle ? component.rotSpeed : angle); + + if(!(component.minAngle == 0 && component.maxAngle == 0)){ + Vector3 refDir = ( + initUnitSpaceDir.x * unit->getLeftVec() + + initUnitSpaceDir.y * unit->getUpVec() + + initUnitSpaceDir.z * unit->getDirVec() + ).norm(); + float angle2 = refDir.getAngleBetween(nodeDir); + + bool dirOnLeft = (nodeLeft.getAngleBetween(refDir) > PI / 2); + + if(!dirOnLeft && -angle2 + rotAngle < component.minAngle) + rotAngle = component.minAngle + angle2; + else if(dirOnLeft && angle2 + rotAngle > component.maxAngle) + rotAngle = component.maxAngle - angle2; + } + + } + + Quaternion rot = Quaternion(rotAngle, rotAxis) * component.node->getOrientation(); + component.node->setOrientation(rot); + } + } + + bool Weapon::canAttackTarget(int objType, int typeOrClass){ + switch((GameObject::Type)objType){ + case GameObject::Type::UNIT: + return (find(targetUnits.begin(), targetUnits.end(), typeOrClass) != targetUnits.end()); + case GameObject::Type::PROJECTILE: + return (find(targetProjectiles.begin(), targetProjectiles.end(), typeOrClass) != targetProjectiles.end()); + } + + return true; + } +} diff --git a/source/gameplay/gameObjects/weapon.h b/source/gameplay/gameObjects/weapon.h new file mode 100644 index 0000000..7db6dfd --- /dev/null +++ b/source/gameplay/gameObjects/weapon.h @@ -0,0 +1,79 @@ +#ifndef WEAPON_H +#define WEAPON_H + +#include "unit.h" + +#include +#include +#include + +#include + +namespace vb01{ + class Node; +} + +namespace battleship{ + class FxManager; + class GameObject; + + class Weapon{ + public: + enum class Type{DAMAGE = 0, FREEZER = 1}; + + struct Component{ + vb01::Node *node = nullptr; + vb01::Vector3 initUnitSpaceDir; + float rotSpeed, minAngle, maxAngle; + bool vertical; + + Component(vb01::Node *n, float rs, float min, float max, bool v) : + node(n), + rotSpeed(rs), + minAngle(min), + maxAngle(max), + vertical(v) {} + }; + + Weapon(Unit*, sol::table, int); + ~Weapon(); + virtual void update(); + virtual void fire(Order); + void trackTarget(vb01::Vector3); + bool canAttackTarget(int, int); + inline int getProjectileId(){return projId;} + inline int getRateOfFire(){return rateOfFire;} + inline int getDamage(){return damage;} + inline int getMinRange(){return minRange;} + inline int getMaxRange(){return maxRange;} + inline Unit* getUnit(){return unit;} + inline Type getType(){return type;} + inline Order::TYPE getOrderType(){return orderType;} + private: + Unit *unit = nullptr; + Order::TYPE orderType; + int id, projId = -1, damage = 0; + std::vector targetUnits, targetProjectiles; + float minRange = 0, maxRange, maxFireAngle; + vb01::s64 lastFireTime = 0; + FxManager::Fx *fireFx = nullptr; + vb01::Quaternion projRot; + vb01::Vector3 projPos, initUnitSpaceDir, fireDir = vb01::Vector3::VEC_K; + vb01::Node *projPar = nullptr; + static std::string LASER_FLAG; + Type type = Type::DAMAGE; + + void initTargetData(std::vector&, sol::table, std::string, std::vector); + void initProjectileData(sol::table); + void initNodes(sol::table); + void useFx(FxManager::Fx*, vb01::Vector3, bool); + inline bool canFire(){return vb01::getTime() - lastFireTime > rateOfFire;} + protected: + int rateOfFire; + std::vector components; + + virtual void updateTarget(GameObject*); + }; +} + +#endif diff --git a/source/gameplay/player/player.cpp b/source/gameplay/player/player.cpp new file mode 100644 index 0000000..9b1fa4a --- /dev/null +++ b/source/gameplay/player/player.cpp @@ -0,0 +1,358 @@ +#include +#include + +#include "player.h" +#include "game.h" +#include "structure.h" +#include "vehicle.h" +#include "projectile.h" +#include "tradeOffer.h" +#include "destructable.h" +#include "activeGameState.h" +#include "resourceDeposit.h" + +namespace battleship{ + using namespace vb01; + using namespace gameBase; + using namespace std; + + Player::Player(int diff, int fac, int t, Vector3 col, bool cpuPl, int sp, string n) : + difficulty(diff), + faction(fac), + team(t), + color(col), + cpuPlayer(cpuPl), + spawnPointId(sp), + name("pl"), + trader(new Trader()) + { + resources[0] = 30000; + + colorMaterial = new Material(Root::getSingleton()->getLibPath() + "texture"); + colorMaterial->addBoolUniform("lightingEnabled", false); + colorMaterial->addBoolUniform("texturingEnabled", false); + colorMaterial->addVec4Uniform("diffuseColor", Vector4(color.x, color.y, color.z, 1)); + } + + Player::~Player() { + delete colorMaterial; + } + + void Player::update() { + trader->update(); + + vector units = this->units; + for(Unit *u : units){ + if(!u->isRemove()) u->update(); + else removeUnit(u); + } + + vector projectiles = this->projectiles; + for(Projectile *proj : projectiles){ + if(!proj->isRemove()) proj->update(); + else removeProjectile(proj); + } + + for(ResourceDeposit *rd : resourceDeposits) rd->update(); + + for(pair> &pair : tradeOffers){ + if(pair.second.empty()) continue; + + bool fulfiled = true; + + for(int i = 0; i < NUM_RESOURCES && fulfiled; i++){ + bool b = (pair.second[0]->tradeResources[i][0] == pair.second[0]->deliveredResources[i][0]); + bool s = (pair.second[0]->tradeResources[i][1] == pair.second[0]->deliveredResources[i][1]); + + if(!(b && s)) fulfiled = false; + } + + if(fulfiled) + pair.second.erase(pair.second.begin()); + } + } + + void Player::haltUnits(){ + for (Unit *u : selectedUnits) + u->halt(); + } + + int Player::getOrderLineId(Order::TYPE type, Vector3 startPos, Vector3 endPos){ + Vector3 color; + + switch(type){ + case Order::TYPE::MOVE: + color = Vector3::VEC_J; + break; + case Order::TYPE::ATTACK: + color = Vector3::VEC_I; + break; + case Order::TYPE::PATROL: + case Order::TYPE::GARRISON: + case Order::TYPE::EJECT: + color = Vector3::VEC_K; + break; + case Order::TYPE::BUILD: + case Order::TYPE::SUPPLY: + case Order::TYPE::LOAD: + case Order::TYPE::UNLOAD: + color = Vector3(1, 1, 0); + break; + case Order::TYPE::HACK: + color = Vector3(1, 0, 1); + break; + } + + LineRenderer *lineRenderer = LineRenderer::getSingleton(); + lineRenderer->addLine(startPos, endPos, color); + vector lines = lineRenderer->getLines(); + + return lines[lines.size() - 1].id; + } + + void Player::issueOrder(Order::TYPE type, Vector3 destDir, vector targets, bool append){ + vector selectedUnits = getSelectedUnits(); + + for (Unit *u : selectedUnits) { + bool targetingSelf = false, structBuilt = true; + + for(Order::Target &targ : targets){ + if(targ.unit && targ.unit == u){ + targetingSelf = true; + break; + } + + if(!u->isVehicle() && ((Structure*)u)->isComplete()){ + structBuilt = false; + break; + } + } + + if(targetingSelf || !structBuilt) continue; + + int lineId = -1; + ActiveGameState *activeState = ((ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE)); + + if(activeState && activeState->getPlayer() == this && type != Order::TYPE::EJECT) + lineId = getOrderLineId(type, u->getPos(), targets[0].pos); + + u->receiveOrder(Order(type, targets, destDir, lineId), append); + } + } + + void Player::removeUnit(Unit *unit){ + for(int i = 0; i < units.size(); i++) + if(unit == units[i]){ + removeUnit(i); + break; + } + } + + void Player::removeUnit(int id){ + if(find(selectedUnits.begin(), selectedUnits.end(), units[id]) != selectedUnits.end()) + deselectUnit(units[id]); + + delete units[id]; + units.erase(units.begin() + id); + + if(cpuPlayer){ + sol::state_view SOL_LUA_VIEW = generateView(); + int id = getCpuPlayerId() + 1; + SOL_LUA_VIEW.script("game.cpuPlayers[" + to_string(id) + "]:updateTaskForces()"); + } + } + + void Player::removeResourceDeposit(int id){ + delete resourceDeposits[id]; + resourceDeposits.erase(resourceDeposits.begin() + id); + } + + void Player::removeProjectile(Projectile *proj){ + for(int i = 0; i < projectiles.size(); i++) + if(proj == projectiles[i]){ + removeProjectile(i); + break; + } + } + + void Player::removeProjectile(int id){ + delete projectiles[id]; + projectiles.erase(projectiles.begin() + id); + } + + void Player::selectUnits(vector selUnits){ + for(Unit *u : selUnits){ + bool garrisonable = (!u->isVehicle() || (u->isVehicle() && !((Vehicle*)u)->getGarrisonable())); + bool selected = (find(selectedUnits.begin(), selectedUnits.end(), u) != selectedUnits.end()); + + if(garrisonable && !selected){ + selectedUnits.push_back(u); + u->select(); + } + } + } + + vector Player::getUnitsById(int id, int numUnits){ + vector idUnits; + + for(Unit *unit : units){ + if(numUnits != -1 && idUnits.size() == numUnits) + break; + + if(unit->getId() == id) + idUnits.push_back(unit); + } + + return idUnits; + } + + vector Player::getUnitsByClass(UnitClass uc, int numUnits){ + vector ucUnits; + + for(Unit *unit : units){ + if(numUnits != -1 && ucUnits.size() == numUnits) + break; + + if(unit->getUnitClass() == uc) + ucUnits.push_back(unit); + } + + return ucUnits; + } + + void Player::updateTradedResource(Player *player, ResourceType type, int amount, bool selfDistributed, bool increased){ + for(pair> pair : tradedResources) + if(pair.first == player){ + TradedResource &tr = pair.second[(int)type]; + + if(selfDistributed) (increased ? tr.taken : tr.given) += amount; + else (increased ? tr.received : tr.hadTaken) += amount; + + break; + } + } + + void Player::initTradingVecs(){ + vector players = Game::getSingleton()->getPlayers(); + + for(Player *pl : players){ + if(this == pl) continue; + + if(team == pl->getTeam()){ + tradeOffers.push_back(make_pair(pl, vector{})); + tradedResources.push_back(make_pair(pl, vector{ + TradedResource(ResourceType::REFINEDS), + TradedResource(ResourceType::WEALTH), + TradedResource(ResourceType::RESEARCH) + })); + } + } + } + + void Player::addTradeOffer(Player *player, TradeOffer *offer){ + if(tradeOffers.empty()) initTradingVecs(); + + for(pair> &offers : tradeOffers) + if(offers.first == player) + offers.second.push_back(offer); + } + + vector Player::getTradeOffers(Player *player){ + for(pair> offers : tradeOffers) + if(offers.first == player) + return offers.second; + + return vector{}; + } + + void Player::deselectUnit(Unit *unit){ + for(int i = 0; i < selectedUnits.size(); i++) + if(selectedUnits[i] == unit){ + selectedUnits.erase(selectedUnits.begin() + i); + break; + } + } + + vector Player::getDestructables(){ + vector destructables; + + for(Projectile *p : projectiles) + if(p->getDestructable()) + destructables.push_back(p); + + for(Unit *unit : units) + destructables.push_back((GameObject*)unit); + + return destructables; + } + + void Player::updateGameStats(Unit *targetUnit){ + Destructable *destructTarg = targetUnit->getDestructable(); + + if(destructTarg->getHealth() <= destructTarg->getDeathHp()){ + Player *targUnitPlayer = targetUnit->getPlayer(); + + if(targetUnit->isVehicle()){ + incVehiclesDestroyed(); + targUnitPlayer->incVehiclesLost(); + } + else{ + incStructuresDestroyed(); + targUnitPlayer->incStructuresLost(); + } + } + } + + vector Player::getFriendlyUnits(bool includeOwn){ + vector friendlyUnits; + + for (Player *pl : Game::getSingleton()->getPlayers(true)) + for (Unit *u : pl->getUnits()) + if((includeOwn && pl == this) || pl->getTeam() == getTeam()) + friendlyUnits.push_back(u); + + return friendlyUnits; + } + + vector Player::getHostileUnits(){ + vector hostileUnits, friendlyUnits = getFriendlyUnits(true); + + for (Player *pl : Game::getSingleton()->getPlayers(true)) + for (Unit *u : pl->getUnits()) + if(pl->getTeam() != getTeam() && isObjectVisible(u, friendlyUnits)) + hostileUnits.push_back(u); + + return hostileUnits; + } + + //TODO improve this for greater accuracy + bool Player::isObjectVisible(GameObject *object, std::vector friendlyUnits) { + Player *objPlayer = object->getPlayer(); + + for(Unit *friendlyUnit : friendlyUnits){ + if(objPlayer == this || objPlayer->getTeam() == getTeam()) + return true; + + Vector3 obsUnitPos = object->getPos(); + Vector2 oup2d = Vector2(obsUnitPos.x, obsUnitPos.z); + + Vector3 compUnitPos = friendlyUnit->getPos(); + Vector2 cup2d = Vector2(compUnitPos.x, compUnitPos.z); + + if(cup2d.getDistanceFrom(oup2d) <= friendlyUnit->getLineOfSight()) + return true; + } + + return false; + } + + int Player::getCpuPlayerId(){ + vector cpuPlayers = Game::getSingleton()->getCpuPlayers(); + + for(int i = 0; i < cpuPlayers.size(); i++) + if(cpuPlayers[i] == this) + return i; + + return -1; + } +} diff --git a/source/gameplay/player/player.h b/source/gameplay/player/player.h new file mode 100644 index 0000000..b7e6891 --- /dev/null +++ b/source/gameplay/player/player.h @@ -0,0 +1,118 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include + +#include "gameManager.h" +#include "trader.h" +#include "unit.h" + +namespace battleship{ + class ResourceDeposit; + class Projectile; + class Unit; + struct TradeOffer; + + const int NUM_RESOURCES = 3; + enum class ResourceType{REFINEDS, WEALTH, RESEARCH}; + + class Player { + public: + Player(int, int, int, vb01::Vector3, bool = true, int = -1, std::string = ""); + ~Player(); + void update(); + void haltUnits(); + void issueOrder(Order::TYPE, vb01::Vector3, std::vector, bool); + void removeUnit(Unit*); + void removeUnit(int); + void removeResourceDeposit(int); + void removeProjectile(int); + void removeProjectile(Projectile*); + bool isThisPlayersUnit(GameObject*); + void selectUnits(std::vector); + std::vector getUnitsById(int, int = -1); + std::vector getUnitsByClass(UnitClass, int = -1); + void addTechnology(int id){technologies.push_back(id);} + void updateTradedResource(Player*, ResourceType, int, bool, bool); + void addTradeOffer(Player*, TradeOffer*); + std::vector getTradeOffers(Player*); + void deselectUnit(Unit*); + std::vector getDestructables(); + void updateGameStats(Unit*); + std::vector getFriendlyUnits(bool = true); + std::vector getHostileUnits(); + bool isObjectVisible(GameObject*, std::vector); + int getCpuPlayerId(); + inline int getResource(ResourceType rt){return resources[(int)rt];} + inline void updateResource(ResourceType rt, int amount, bool add){resources[(int)rt] = (add ? resources[(int)rt] + amount : amount);} + inline Trader* getTrader(){return trader;} + inline void deselectUnits(){selectedUnits.clear();} + inline Unit* getSelectedUnit(int id){return selectedUnits[id];} + inline std::vector getSelectedUnits(){return selectedUnits;} + inline void deselectUnit(int i){selectedUnits.erase(selectedUnits.begin() + i);} + inline int getNumSelectedUnits(){return getSelectedUnits().size();} + inline void selectUnit(Unit *u){selectUnits(std::vector{u});} + inline void addUnit(Unit *u){units.push_back(u);} + inline std::vector& getResourceDeposits(){return resourceDeposits;} + inline void addResourceDeposit(ResourceDeposit *rd){resourceDeposits.push_back(rd);} + inline int getNumResourceDeposits(){return resourceDeposits.size();} + inline void addProjectile(Projectile *proj){projectiles.push_back(proj);} + inline int getNumProjectiles(){return projectiles.size();} + inline std::vector& getProjectiles(){return projectiles;} + inline Unit* getUnit(int i){return units[i];} + inline std::vector& getUnits(){return units;} + inline void setTeam(int t){team = t;} + inline int getTeam(){return team;} + inline int getNumUnits(){return units.size();} + inline int getFaction(){return faction;} + inline int getSpawnPointId(){return spawnPointId;} + inline bool isCpuPlayer(){return cpuPlayer;} + inline int getNumVehiclesBuilt(){return vehiclesBuilt;} + inline int getNumVehiclesDestroyed(){return vehiclesDestroyed;} + inline int getNumVehiclesLost(){return vehiclesLost;} + inline int getNumStructuresBuilt(){return structuresBuilt;} + inline int getNumStructuresDestroyed(){return structuresDestroyed;} + inline int getNumStructuresLost(){return structuresLost;} + inline void incVehiclesBuilt(){vehiclesBuilt++;} + inline void incVehiclesDestroyed(){vehiclesDestroyed++;} + inline void incVehiclesLost(){vehiclesLost++;} + inline void incStructuresBuilt(){structuresBuilt++;} + inline void incStructuresDestroyed(){structuresDestroyed++;} + inline void incStructuresLost(){structuresLost++;} + inline vb01::Vector3 getColor(){return color;} + inline std::string getName(){return name;} + inline vb01::Material* getColorMaterial(){return colorMaterial;} + inline std::vector getTechnologies(){return technologies;} + private: + struct TradedResource{ + ResourceType type; + int taken = 0, given = 0, received = 0, hadTaken = 0; + + TradedResource(ResourceType t) : type(t){} + }; + + bool cpuPlayer = false; + std::vector technologies; + int luaPlayerId; + int resources[3]{0, 0, 0}; + int faction, difficulty, team; + int vehiclesBuilt = 0, vehiclesDestroyed = 0, vehiclesLost = 0; + int structuresBuilt = 0, structuresDestroyed = 0, structuresLost = 0; + int spawnPointId = -1; + Trader *trader = nullptr; + std::string name; + std::vector units, selectedUnits; + std::vector projectiles; + std::vector resourceDeposits; + vb01::Vector3 color; + vb01::Material *colorMaterial = nullptr; + std::vector>> tradeOffers; + std::vector>> tradedResources; + + int getOrderLineId(Order::TYPE, vb01::Vector3, vb01::Vector3); + void initTradingVecs(); + }; +} + +#endif diff --git a/source/gameplay/player/tradeOffer.h b/source/gameplay/player/tradeOffer.h new file mode 100644 index 0000000..bc59b5a --- /dev/null +++ b/source/gameplay/player/tradeOffer.h @@ -0,0 +1,27 @@ +#ifndef TRADE_OFFER_H +#define TRADE_OFFER_H + +#include "player.h" + +namespace battleship{ + struct TradeOffer{ + bool sellerAggrees = true, sellerDelivers = true, buyerAggrees = true, buyerDelivers = false; + int tradeResources[NUM_RESOURCES][2], deliveredResources[NUM_RESOURCES][2]; + + TradeOffer(int br, int sr, int bw, int sw, int bt, int st){ + tradeResources[(int)ResourceType::REFINEDS][0] = br; + tradeResources[(int)ResourceType::REFINEDS][1] = sr; + tradeResources[(int)ResourceType::WEALTH][0] = bw; + tradeResources[(int)ResourceType::WEALTH][1] = sw; + tradeResources[(int)ResourceType::RESEARCH][0] = bt; + tradeResources[(int)ResourceType::RESEARCH][1] = st; + + for(int i = 0; i < NUM_RESOURCES; i++){ + deliveredResources[i][0] = 0; + deliveredResources[i][1] = 0; + } + } + }; +} + +#endif diff --git a/source/gameplay/player/trader.cpp b/source/gameplay/player/trader.cpp new file mode 100644 index 0000000..5e5af2e --- /dev/null +++ b/source/gameplay/player/trader.cpp @@ -0,0 +1,69 @@ +#include "trader.h" +#include "player.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + Trader::Trader(){ + initProperties(); + } + + void Trader::initProperties(){ + sol::table traderTable = generateView()["trading"]; + fluctuationRate = traderTable["fluctuationRate"]; + fluctuationAmmount = traderTable["fluctuationAmmount"]; + refinedsRate = traderTable["refinedsRate"]; + researchRate = traderTable["researchRate"]; + } + + void Trader::update(){ + if(getTime() - lastUpdateTime > fluctuationRate){ + int flucFact = rand() % 3; + int flSpd; + + switch(flucFact){ + case 0: + flSpd = -fluctuationAmmount; + break; + case 1: + flSpd = 0; + break; + case 2: + flSpd = fluctuationAmmount; + break; + } + + refinedsRate += fluctuationAmmount; + researchRate += fluctuationAmmount; + lastUpdateTime = getTime(); + } + } + + void Trader::trade(Player *tradingPlayer, int rt, int ammount, bool buying){ + int tradeResRate; + + switch((ResourceType)rt){ + case ResourceType::REFINEDS: + tradeResRate = refinedsRate; + break; + case ResourceType::RESEARCH: + tradeResRate = researchRate; + break; + } + + if(buying && ammount * tradeResRate <= tradingPlayer->getResource(ResourceType::WEALTH)){ + tradingPlayer->updateResource((ResourceType)rt, ammount, true); + tradingPlayer->updateResource(ResourceType::WEALTH, -ammount * tradeResRate, true); + } + else if(!buying && tradingPlayer->getResource((ResourceType)rt) >= ammount){ + tradingPlayer->updateResource((ResourceType)rt, -ammount, true); + tradingPlayer->updateResource(ResourceType::WEALTH, ammount * tradeResRate, true); + } + } +} diff --git a/source/gameplay/player/trader.h b/source/gameplay/player/trader.h new file mode 100644 index 0000000..1121cf4 --- /dev/null +++ b/source/gameplay/player/trader.h @@ -0,0 +1,22 @@ +#ifndef TRADER_H +#define TRADER_H + +#include + +namespace battleship{ + class Player; + + class Trader{ + public: + Trader(); + void update(); + void trade(Player*, int, int, bool); + private: + void initProperties(); + + vb01::s64 lastUpdateTime = 0; + int fluctuationRate, refinedsRate, researchRate, fluctuationAmmount; + }; +} + +#endif diff --git a/source/gui/buttons/activeStateBackButton.cpp b/source/gui/buttons/activeStateBackButton.cpp new file mode 100644 index 0000000..5610986 --- /dev/null +++ b/source/gui/buttons/activeStateBackButton.cpp @@ -0,0 +1,21 @@ +#include "activeStateBackButton.h" +#include "activeGameState.h" +#include "gameManager.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + ActiveStateBackButton::ActiveStateBackButton(Vector3 pos, Vector2 size, string name) : BackButton(pos, size, name, "activeGameState.lua"){} + + void ActiveStateBackButton::onClick(){ + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)stateManager->getAppStateByType((int)AppStateType::ACTIVE_STATE); + activeState->setTradingScreen(false); + + BackButton::onClick(); + } +} diff --git a/source/gui/buttons/activeStateBackButton.h b/source/gui/buttons/activeStateBackButton.h new file mode 100644 index 0000000..ad57879 --- /dev/null +++ b/source/gui/buttons/activeStateBackButton.h @@ -0,0 +1,15 @@ +#ifndef ACTIVE_STATE_BACK_BUTTON_H +#define ACTIVE_STATE_BACK_BUTTON_H + +#include "backButton.h" + +namespace battleship{ + class ActiveStateBackButton : public BackButton{ + public: + ActiveStateBackButton(vb01::Vector3, vb01::Vector2, std::string); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/activeStateButton.cpp b/source/gui/buttons/activeStateButton.cpp new file mode 100644 index 0000000..703b6f7 --- /dev/null +++ b/source/gui/buttons/activeStateButton.cpp @@ -0,0 +1,45 @@ +#include "activeStateButton.h" +#include "gameManager.h" +#include "activeGameState.h" +#include "concreteGuiManager.h" + +#include +#include +#include +#include + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + ActiveStateButton::ActiveStateButton(Vector3 pos, Vector2 size, string gs, string name, string fontPath, int trigger, string imagePath) : + Button(pos, size, name, fontPath, trigger, true, imagePath), guiScreen(gs){} + + void ActiveStateButton::onClick(){ + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)stateManager->getAppStateByType((int)AppStateType::ACTIVE_STATE); + + vector buttons = activeState->getGuiButtons(); + buttons.push_back(this); + + vector rects = activeState->getGuiRects(); + vector texts = activeState->getGuiTexts(); + + ConcreteGuiManager::getSingleton()->readLuaScreenScript( + guiScreen, + buttons, + vector{}, + vector{}, + vector{}, + vector{}, + rects, + texts + ); + } +} diff --git a/source/gui/buttons/activeStateButton.h b/source/gui/buttons/activeStateButton.h new file mode 100644 index 0000000..48a7c14 --- /dev/null +++ b/source/gui/buttons/activeStateButton.h @@ -0,0 +1,18 @@ +#ifndef ACTIVE_STATE_BUTTON_H +#define ACTIVE_STATE_BUTTON_H + +#include + +namespace battleship{ + class ActiveStateButton : public vb01Gui::Button{ + public: + ActiveStateButton(vb01::Vector3, vb01::Vector2, std::string, std::string, std::string, int = -1, std::string = ""); + void onClick(); + private: + std::string guiScreen = ""; + protected: + std::vector buttons; + }; +} + +#endif diff --git a/source/gui/buttons/backButton.cpp b/source/gui/buttons/backButton.cpp new file mode 100644 index 0000000..e221666 --- /dev/null +++ b/source/gui/buttons/backButton.cpp @@ -0,0 +1,17 @@ +#include "backButton.h" +#include "gameManager.h" +#include "concreteGuiManager.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + BackButton::BackButton(Vector3 pos, Vector2 size, string name, string scr) : + Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true), + screen(scr){} + + void BackButton::onClick() { + ConcreteGuiManager::getSingleton()->readLuaScreenScript(screen); + } +} diff --git a/source/gui/buttons/backButton.h b/source/gui/buttons/backButton.h new file mode 100644 index 0000000..504eef7 --- /dev/null +++ b/source/gui/buttons/backButton.h @@ -0,0 +1,16 @@ +#ifndef BACK_BUTTON_H +#define BACK_BUTTON_H + +#include + +namespace battleship{ + class BackButton : public vb01Gui::Button{ + public: + BackButton(vb01::Vector3, vb01::Vector2, std::string, std::string); + void onClick(); + private: + std::string screen; + }; +} + +#endif diff --git a/source/gui/buttons/buildButton.cpp b/source/gui/buttons/buildButton.cpp new file mode 100644 index 0000000..af69db7 --- /dev/null +++ b/source/gui/buttons/buildButton.cpp @@ -0,0 +1,37 @@ +#include "buildButton.h" +#include "gameManager.h" +#include "gameObject.h" +#include "unit.h" +#include "activeGameState.h" +#include "buildableUnit.h" +#include "gameObjectFrameController.h" + +#include +#include + +namespace battleship{ + using namespace sol; + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + BuildButton::BuildButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath, int slId) : + UnitButton(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, imagePath), slotId(slId){} + + void BuildButton::onClick(){ + ActiveGameState *activeState = (ActiveGameState*)(GameManager::getSingleton()->getStateManager()->getAppStateByType((int)AppStateType::ACTIVE_STATE)); + Player *player = activeState->getPlayer(); + + sol::state_view SOL_LUA_VIEW = generateView(); + int unitId = SOL_LUA_VIEW["_mainUnitId"]; + Unit* builder = player->getUnitsById(unitId)[0]; + + if(builder->getBuildableUnit(slotId).buildable){ + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + ufCtr->addGameObjectFrame(GameObjectFrame(builder->getBuildableUnit(slotId).id, GameObject::Type::UNIT, player)); + ufCtr->setPlacingOnSurface(true); + activeState->setBuildableStructSelected(true); + } + } +} diff --git a/source/gui/buttons/buildButton.h b/source/gui/buttons/buildButton.h new file mode 100644 index 0000000..770a479 --- /dev/null +++ b/source/gui/buttons/buildButton.h @@ -0,0 +1,16 @@ +#ifndef BUILD_BUTTON_H +#define BUILD_BUTTON_H + +#include "unitButton.h" + +namespace battleship{ + class BuildButton : public UnitButton{ + public: + BuildButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string, int); + void onClick(); + private: + int slotId; + }; +} + +#endif diff --git a/source/gui/buttons/concreteGuiManager.cpp b/source/gui/buttons/concreteGuiManager.cpp new file mode 100644 index 0000000..76c6939 --- /dev/null +++ b/source/gui/buttons/concreteGuiManager.cpp @@ -0,0 +1,652 @@ +#include +#include + +#include + +#include "concreteGuiManager.h" +#include "unit.h" +#include "gameManager.h" +#include "singlePlayerButton.h" +#include "mapEditorButton.h" +#include "optionsButton.h" +#include "exitButton.h" +#include "tabButton.h" +#include "okButton.h" +#include "defaultsButton.h" +#include "backButton.h" +#include "newMapButton.h" +#include "loadMapButton.h" +#include "exportButton.h" +#include "mapListbox.h" +#include "skyboxTextureListbox.h" +#include "landTextureListbox.h" +#include "gameObjectListbox.h" +#include "playButton.h" +#include "inGameAppState.h" +#include "mainMenuButton.h" +#include "buildButton.h" +#include "trainButton.h" +#include "statsButton.h" +#include "researchButton.h" +#include "tradeButton.h" +#include "activeStateButton.h" +#include "playerTradeButton.h" +#include "tradingScreenButton.h" +#include "offerButton.h" +#include "resourceAmmountButton.h" +#include "orderButton.h" +#include "stateToggleButton.h" +#include "minimapButton.h" +#include "activeStateBackButton.h" + +namespace battleship{ + using namespace std; + using namespace gameBase; + using namespace vb01; + using namespace vb01Gui; + + static ConcreteGuiManager *concreteGuiManager = nullptr; + + ConcreteGuiManager::ConcreteGuiManager(){ + string assetPath = GameManager::getSingleton()->getPath(); + texBasePath = assetPath + "Textures/"; + fontBasePath = assetPath + "Fonts/"; + } + + ConcreteGuiManager* ConcreteGuiManager::getSingleton(){ + if(!concreteGuiManager) + concreteGuiManager = new ConcreteGuiManager(); + + return concreteGuiManager; + } + + //TODO refactor player difficulty and faction listbox selection + //TODO remove hardcoded font path values + //TODO use configurable map path values + Button* ConcreteGuiManager::parseButton(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + sol::table posTable = guiTable["pos"]; + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + sol::table sizeTable = guiTable["size"]; + Vector2 size = Vector2(sizeTable["x"], sizeTable["y"]); + + //TODO factor out repetetive optional lua key checks + string name = "", nk = "name"; + sol::optional nameOpt = guiTable[nk]; + + if(nameOpt != sol::nullopt) name = guiTable[nk]; + + ButtonType type = (ButtonType)guiTable["buttonType"]; + + string imagePath = "", ipk = "imagePath"; + sol::optional pathOpt = guiTable[ipk]; + bool texturingEnabled = false; + + if(pathOpt != sol::nullopt){ + imagePath = texBasePath; + imagePath += guiTable[ipk]; + bool texturingEnabled = true; + } + + Button *button = nullptr; + string guiScreen = ""; + + switch(type){ + case SINGLE_PLAYER: + button = new SinglePlayerButton(pos, size, name); + break; + case EDITOR: + button = new MapEditorButton(pos, size); + break; + case OPTIONS: + button = new OptionsButton(pos, size, name, true); + break; + case EXIT: + button = new ExitButton(pos, size); + break; + case OK: + button = new OkButton(pos, size, name); + break; + case DEFAULTS: + button = new DefaultsButton(pos, size, name); + break; + case BACK: { + string screen = guiTable["screen"]; + button = new BackButton(pos, size, name, screen); + break; + } + case CONTROLS_TAB: + case MOUSE_TAB: + case VIDEO_TAB: + case AUDIO_TAB: + case MULTIPLAYER_TAB: { + string screens[]{ + "controlsTab.lua", + "mouseTab.lua", + "videoTab.lua", + "audioTab.lua", + "multiplayerTab.lua" + }; + int diff = ((int)type - (int)CONTROLS_TAB); + button = new TabButton(pos, size, name, screens[diff]); + break; + } + case NEW_MAP: + button = new NewMapButton(pos, size); + break; + case NEW_MAP_OK:{ + int numTextboxes = guiTable["numDependencies"]; + vector t; + + for(int i = 0; i < numTextboxes; i++){ + int tid = guiTable["dependencies"][i + 1]["id"]; + t.push_back((Textbox*)guiElements[tid].second); + } + + button = new NewMapButton::OkButton(pos, size, t[0], t[1], t[2]); + break; + } + case LOAD_MAP: + button = new LoadMapButton(pos, size); + break; + case LOAD_MAP_OK:{ + int lid = guiTable["dependencies"][1]["id"]; + button = new LoadMapButton::OkButton(pos, size, (Listbox*)guiElements[lid].second); + break; + } + case EXPORT: + button = new ExportButton(pos, size); + break; + case PLAY:{ + int mid = guiTable["dependencies"][1]["id"]; + Listbox *mapListbox = (MapListbox*)guiElements[mid].second; + button = new PlayButton(mapListbox, pos, size, name, true); + break; + } + case RESUME: + button = new InGameAppState::ResumeButton(pos, size); + break; + case CONSOLE_SCREEN: + button = new InGameAppState::ConsoleButton(pos, size); + break; + case MAIN_MENU: + button = new MainMenuButton(pos, size, name); + break; + case CONSOLE_COMMAND_OK:{ + int lid = guiTable["dependencies"][1]["id"]; + Listbox *listbox = (Listbox*)guiElements[lid].second; + + int tid = guiTable["dependencies"][2]["id"]; + Textbox *textbox = (Textbox*)guiElements[tid].second; + + button = new InGameAppState::ConsoleButton::ConsoleCommandEntryButton(textbox, listbox, pos, size, name); + break; + } + case BUILD: + button = new BuildButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["slotId"]); + break; + case TRAIN: + button = new TrainButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["slotId"]); + break; + case STATISTICS: + button = new StatsButton(pos, size, name, (int)guiTable["trigger"], imagePath); + break; + case RESEARCH: + button = new ResearchButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["techId"]); + break; + case BUY_REFINEDS: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::BUY_REFINEDS); + break; + case SELL_REFINEDS: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::SELL_REFINEDS); + break; + case BUY_RESEARCH: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::BUY_RESEARCH); + break; + case SELL_RESEARCH: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::SELL_RESEARCH); + break; + case ACTIVE_STATE_BUTTON: + button = new ActiveStateButton(pos, size, guiTable["guiScreen"], name, fontBasePath + "batang.ttf", (int)guiTable["trigger"], imagePath); + break; + case ACTIVE_STATE_BACK: + button = new ActiveStateBackButton(pos, size, name); + break; + case PLAYER_TRADE: + guiScreen = guiTable["guiScreen"]; + button = new PlayerTradeButton(pos, size, guiScreen, name, (int)guiTable["trigger"], imagePath); + break; + case TRADING_SCREEN:{ + int lid = guiTable["dependencies"][1]["id"]; + Listbox *listbox = (Listbox*)guiElements[lid].second; + guiScreen = guiTable["guiScreen"]; + + button = new TradingScreenButton(pos, size, listbox, (int)SOL_LUA_STATE["playerId"], guiScreen, name, (int)guiTable["trigger"], imagePath); + break; + } + case TRADE_OFFER: + button = new OfferButton(pos, size, (int)SOL_LUA_STATE["playerId"], name, (int)guiTable["trigger"], imagePath); + break; + case RESOURCE_AMMOUNT: + button = new ResourceAmmountButton(pos, size, name, (int)guiTable["ammount"], (int)guiTable["trigger"], imagePath); + break; + case ORDER: + button = new OrderButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["orderType"]); + break; + case UNIT_STATE: + button = new StateToggleButton(pos, size, name, (int)guiTable["trigger"], imagePath); + break; + case MINIMAP:{ + string minimapPath = GameManager::getSingleton()->getPath() + "Models/Maps/" + Map::getSingleton()->getMapName() + "/minimap.jpg"; + button = new MinimapButton(pos, size, minimapPath); + break; + } + } + + int typeArr[2]{(int)GuiElementType::BUTTON, (int)type}; + guiElements.push_back(make_pair(typeArr, (void*)button)); + + return button; + } + + Listbox* ConcreteGuiManager::parseGameObjectListbox(){ + return nullptr; + } + + Listbox* ConcreteGuiManager::parseListbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + + string sizeTable = "size"; + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + + int numMaxDisplay = guiTable["numMaxDisplay"]; + ListboxType listboxType = (ListboxType)guiTable["listboxType"]; + + int maxDisplay, numLines; + bool closable; + vector lines; + sol::optional linesOpt = guiTable["lines"]; + + if(linesOpt != sol::nullopt){ + sol::table linesTbl = guiTable["lines"]; + + for(int i = 0; i < linesTbl.size(); i++) + lines.push_back(guiTable["lines"][i + 1]); + } + + Listbox *listbox = nullptr; + string fontPath = fontBasePath + "batang.ttf"; + sol::optional nameOpt = guiTable["name"]; + string name = ""; + + if(nameOpt != sol::nullopt) name = guiTable["name"]; + + switch(listboxType){ + case CONTROLS:{ + numLines = 6; + closable = false; + + for(int i = 0; i < numLines; i++) + lines.push_back(to_string(i)); + + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + + break; + } + case RESOLUTION:{ + numLines = guiTable["numLines"]; + + for(int i = 0; i < numLines; i++) + lines.push_back(guiTable["lines"][i + 1]); + + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + + break; + } + case MAPS:{ + lines = readDir(GameManager::getSingleton()->getPath() + "Models/Maps/", true); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + bool addPlayers = guiTable["addPlayerGui"]; + + listbox = new MapListbox(pos, size, lines, maxDisplay, addPlayers, fontPath, closable); + break; + } + case VEHICLES: + case STRUCTURES: + case RESOURCE_DEPOSITS:{ + bool resources = (listboxType == RESOURCE_DEPOSITS); + sol::table gameObjTable = SOL_LUA_STATE[resources ? "resources" : "units"]; + int numGameObjs = gameObjTable.size(); + std::vector gameObjIds; + + for(int i = 0; i < numGameObjs; i++){ + bool canAdd = true; + + if(!resources){ + bool vehicles = (listboxType == VEHICLES); + bool v = gameObjTable[i + 1]["isVehicle"]; + canAdd = (v == vehicles); + } + + if(canAdd){ + lines.push_back(gameObjTable[i + 1]["name"]); + gameObjIds.push_back(i); + } + } + + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new GameObjectListbox(!resources, pos, size, lines, gameObjIds, maxDisplay, fontPath); + break; + } + case SKYBOX_TEXTURES: + lines = readDir(texBasePath + "Skyboxes", true); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new SkyboxTextureListbox(pos, size, lines, maxDisplay, fontPath); + break; + case LAND_TEXTURES: + lines = readDir(texBasePath + "Landmass", false); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new LandTextureListbox(pos, size, lines, maxDisplay, fontPath); + break; + case CPU_DIFFICULTIES: + case FACTIONS: + case COLORS: + case TEAMS: + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath); + break; + case CONSOLE:{ + for(int i = 0; i < numMaxDisplay; i++) + lines.push_back(""); + + closable = false; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + } + break; + case TRADE_OFFERS: + closable = true; + listbox = new Listbox(pos, size, lines, numMaxDisplay, fontPath, closable); + break; + } + + int typeArr[2]{(int)GuiElementType::LISTBOX, (int)listboxType}; + guiElements.push_back(make_pair(typeArr, (void*)listbox)); + + listbox->setName(name); + + return listbox; + } + + Checkbox* ConcreteGuiManager::parseCheckbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Checkbox *checkbox = new Checkbox(pos, fontBasePath + "batang.ttf"); + + int typeArr[2]{(int)GuiElementType::CHECKBOX, -1}; + guiElements.push_back(make_pair(typeArr, (void*)checkbox)); + + return checkbox; + } + + Slider* ConcreteGuiManager::parseSlider(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos", sizeTable = "size"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + + Slider *slider = new Slider(pos, size, guiTable["minValue"], guiTable["maxValue"]); + + int typeArr[2]{(int)GuiElementType::SLIDER, -1}; + guiElements.push_back(make_pair(typeArr, (void*)slider)); + + return slider; + } + + Textbox* ConcreteGuiManager::parseTextbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos", sizeTable = "size"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + Textbox *textbox = new Textbox(pos, size, fontBasePath + "batang.ttf"); + + int typeArr[2]{(int)GuiElementType::TEXTBOX, -1}; + guiElements.push_back(make_pair(typeArr, (void*)textbox)); + + return textbox; + } + + //TODO factor out checking for optional lua values + Node* ConcreteGuiManager::parseGuiRectangle(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->setTransparent(true); + + bool texturingEnabled = false; + string imagePath = "", ipk = "imagePath"; + sol::optional pathOpt = guiTable[ipk]; + + if(pathOpt != sol::nullopt){ + imagePath = guiTable[ipk]; + texturingEnabled = true; + } + + string name = "", nk = "name"; + sol::optional nameOpt = guiTable[nk]; + + if(nameOpt != sol::nullopt) name = guiTable[nk]; + + mat->addBoolUniform("texturingEnabled", texturingEnabled); + + if(texturingEnabled){ + string p[]{texBasePath + imagePath}; + Texture *tex = new Texture(p, 1, false); + mat->addTexUniform("diffuseMap", tex, false); + } + else{ + sol::table colorTable = guiTable["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["w"])); + } + + sol::table sizeTable = guiTable["size"]; + Quad *quad = new Quad(Vector3(sizeTable["x"], sizeTable["y"], 1), false); + quad->setMaterial(mat); + + sol::table posTable = guiTable["pos"]; + Node *guiRectangle = new Node(Vector3(posTable["x"], posTable["y"], posTable["z"]), Quaternion::QUAT_W, Vector3::VEC_IJK, name); + guiRectangle->attachMesh(quad); + root->getGuiNode()->attachChild(guiRectangle); + + int typeArr[2]{(int)GuiElementType::GUI_RECTANGLE, -1}; + guiElements.push_back(make_pair(typeArr, (void*)guiRectangle)); + + return guiRectangle; + } + + //TODO distinguish between floats and vector-like tables for scale + Text* ConcreteGuiManager::parseText(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + sol::table posTable = guiTable["pos"]; + + Root *root = Root::getSingleton(); + + Material *mat = new Material(root->getLibPath() + "text"); + mat->addBoolUniform("texturingEnabled", false); + sol::table colorTable = guiTable["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["w"])); + + string font = guiTable["font"]; + wstring entry = guiTable["text"]; + Text *text = new Text(fontBasePath + font, entry, guiTable["fontFirstChar"], guiTable["fontLastChar"]); + text->setMaterial(mat); + + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + SOL_LUA_STATE.script("tp = type(gui[" + to_string(guiId + 1) + "].scale)"); + string st = SOL_LUA_STATE["tp"]; + Vector3 scale; + + if(st == "table"){ + sol::table scaleTable = guiTable["scale"]; + scale = Vector3(scaleTable["x"], scaleTable["y"], 1); + } + else if(st == "number"){ + float sc = guiTable["scale"]; + scale = Vector3(sc, sc, 1); + } + + Node *node = new Node(pos, Quaternion::QUAT_W, scale, guiTable["name"]); + node->addText(text); + root->getGuiNode()->attachChild(node); + + int typeArr[2]{(int)GuiElementType::TEXT, -1}; + guiElements.push_back(make_pair(typeArr, (void*)text)); + + return text; + } + + void ConcreteGuiManager::parseMusic(){ + sol::state_view SOL_STATE_VIEW = generateView(); + sol::optional musicTblOpt = SOL_STATE_VIEW["music"]; + + if(musicTblOpt == sol::nullopt) return; + + sol::table musicTbl = SOL_STATE_VIEW["music"], tracksTbl = musicTbl["tracks"]; + SoundManager *sm = SoundManager::getSingleton(); + + if(tracksTbl.size() == 0){ + sm->clearPlaylist(); + return; + } + + bool loop = musicTbl["loop"], shuffle = musicTbl["shuffle"]; + int delay = musicTbl["delay"].get_or(0); + int numTracks = tracksTbl.size(); + + vector trackPaths; + + for(int i = 0; i < numTracks; i++){ + string track = tracksTbl[i + 1]; + trackPaths.push_back(GameManager::getSingleton()->getPath() + "Sounds/Music/" + track); + } + + sm->play(trackPaths, 100, delay, loop, shuffle); + } + + void ConcreteGuiManager::readLuaScreenScript( + string script, + vector buttonExceptions, + vector listboxExceptions, + vector checkboxExceptions, + vector sliderExceptions, + vector textboxExceptions, + vector guiRectboxExceptions, + vector textExceptions, + string luaCode + ){ + removeAllGuiElements(buttonExceptions, listboxExceptions, checkboxExceptions, sliderExceptions, textboxExceptions, guiRectboxExceptions, textExceptions); + parseLuaScript(script, luaCode); + } + + void ConcreteGuiManager::readLuaScreenScriptDel( + string script, + vector buttons, + vector listboxs, + vector checkboxs, + vector sliders, + vector textboxs, + vector guiRectboxs, + vector texts + ){ + for(Button *b : buttons) removeButton(b); + for(Listbox *l : listboxs) removeListbox(l); + for(Checkbox *c : checkboxs) removeCheckbox(c); + for(Slider *s : sliders) removeSlider(s); + for(Textbox *t : textboxs) removeTextbox(t); + for(Node *r : guiRectboxs) removeGuiRectangle(r); + for(Text *t : texts) removeText(t); + + parseLuaScript(script); + } + + void ConcreteGuiManager::parseLuaScript(string script, string luaCode){ + guiElements.clear(); + + string basePath = GameManager::getSingleton()->getPath() + "Scripts/Gui/"; + sol::state_view SOL_LUA_VIEW = generateView(); + SOL_LUA_VIEW.script("music = nil"); + SOL_LUA_VIEW.script_file(basePath + script); + + if(luaCode != "") SOL_LUA_VIEW.script(luaCode); + + SOL_LUA_VIEW.script("numGui = #gui"); + int numGuiElements = SOL_LUA_VIEW["numGui"]; + + for(int i = 0; i < numGuiElements; i++){ + int guiTypeId = SOL_LUA_VIEW["gui"][i + 1]["guiType"]; + + switch((GuiElementType)guiTypeId){ + case BUTTON: + addButton(parseButton(i)); + break; + case LISTBOX: + addListbox(parseListbox(i)); + break; + case CHECKBOX: + addCheckbox(parseCheckbox(i)); + break; + case SLIDER: + addSlider(parseSlider(i)); + break; + case TEXTBOX: + addTextbox(parseTextbox(i)); + break; + case GUI_RECTANGLE: + addGuiRectangle(parseGuiRectangle(i)); + break; + case TEXT: + addText(parseText(i)); + break; + } + } + + parseMusic(); + } +} diff --git a/source/gui/buttons/concreteGuiManager.h b/source/gui/buttons/concreteGuiManager.h new file mode 100644 index 0000000..06ae6a2 --- /dev/null +++ b/source/gui/buttons/concreteGuiManager.h @@ -0,0 +1,117 @@ +#ifndef CONCRETE_GUI_MANAGER_H +#define CONCRETE_GUI_MANAGER_H + +#include + +#include +#include +#include + +namespace vb01{ + class Node; + class Text; +} + +namespace battleship{ + enum GuiElementType {BUTTON, LISTBOX, CHECKBOX, SLIDER, TEXTBOX, GUI_RECTANGLE, TEXT, MUSIC}; + enum ButtonType { + SINGLE_PLAYER, + EDITOR, + OPTIONS, + EXIT, + OK, + DEFAULTS, + BACK, + CONTROLS_TAB, + MOUSE_TAB, + VIDEO_TAB, + AUDIO_TAB, + MULTIPLAYER_TAB, + NEW_MAP, + NEW_MAP_OK, + LOAD_MAP, + LOAD_MAP_OK, + EXPORT, + PLAY, + RESUME, + CONSOLE_SCREEN, + MAIN_MENU, + CONSOLE_COMMAND_OK, + BUILD, + TRAIN, + STATISTICS, + RESEARCH, + BUY_REFINEDS, + SELL_REFINEDS, + BUY_RESEARCH, + SELL_RESEARCH, + ACTIVE_STATE_BUTTON, + ACTIVE_STATE_BACK, + PLAYER_TRADE, + TRADING_SCREEN, + TRADE_OFFER, + RESOURCE_AMMOUNT, + UNIT_STATE, + ORDER, + MINIMAP, + }; + enum ListboxType { + CONTROLS, + RESOLUTION, + MAPS, + VEHICLES, + STRUCTURES, + RESOURCE_DEPOSITS, + SKYBOX_TEXTURES, + LAND_TEXTURES, + CPU_DIFFICULTIES, + FACTIONS, + COLORS, + TEAMS, + CONSOLE, + TRADE_OFFERS + }; + + class ConcreteGuiManager : public vb01Gui::AbstractGuiManager{ + public: + static ConcreteGuiManager* getSingleton(); + void readLuaScreenScript( + std::string, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::string = "" + ); + void readLuaScreenScriptDel( + std::string, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{} + ); + void parseLuaScript(std::string, std::string = ""); + private: + ConcreteGuiManager(); + vb01Gui::Button* parseButton(int); + vb01Gui::Listbox* parseGameObjectListbox(); + vb01Gui::Listbox* parseListbox(int); + vb01Gui::Checkbox* parseCheckbox(int); + vb01Gui::Slider* parseSlider(int); + vb01Gui::Textbox* parseTextbox(int); + vb01::Node* parseGuiRectangle(int); + vb01::Text* parseText(int); + void parseMusic(); + + std::vector> guiElements; + std::string texBasePath, fontBasePath; + }; +} + +#endif diff --git a/source/gui/buttons/defaultsButton.cpp b/source/gui/buttons/defaultsButton.cpp new file mode 100644 index 0000000..8658e2c --- /dev/null +++ b/source/gui/buttons/defaultsButton.cpp @@ -0,0 +1,12 @@ +#include "defaultsButton.h" +#include "gameManager.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + DefaultsButton::DefaultsButton(Vector3 pos, Vector2 size, string name) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true) {} + + void DefaultsButton::onClick() {} +} diff --git a/source/gui/buttons/defaultsButton.h b/source/gui/buttons/defaultsButton.h new file mode 100644 index 0000000..f7ac6dd --- /dev/null +++ b/source/gui/buttons/defaultsButton.h @@ -0,0 +1,15 @@ +#ifndef DEFAULTS_BUTTON_H +#define DEFAULTS_BUTTON_H + +#include + +namespace battleship{ + class DefaultsButton : public vb01Gui::Button{ + public: + DefaultsButton(vb01::Vector3, vb01::Vector2, std::string); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/exitButton.cpp b/source/gui/buttons/exitButton.cpp new file mode 100644 index 0000000..b1b895c --- /dev/null +++ b/source/gui/buttons/exitButton.cpp @@ -0,0 +1,13 @@ +#include "exitButton.h" +#include "gameManager.h" + +using namespace std; +using namespace vb01; + +namespace battleship{ + ExitButton::ExitButton(Vector3 pos, Vector2 size) : Button(pos, size, "Exit", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true) {} + + void ExitButton::onClick() { + GameManager::getSingleton()->setRunning(false); + } +} diff --git a/source/gui/buttons/exitButton.h b/source/gui/buttons/exitButton.h new file mode 100644 index 0000000..c845342 --- /dev/null +++ b/source/gui/buttons/exitButton.h @@ -0,0 +1,15 @@ +#ifndef EXIT_BUTTON_H +#define EXIT_BUTTON_H + +#include +#include + +namespace battleship { + class ExitButton : public vb01Gui::Button { + public: + ExitButton(vb01::Vector3, vb01::Vector2); + void onClick(); + }; +} + +#endif diff --git a/source/gui/buttons/exportButton.cpp b/source/gui/buttons/exportButton.cpp new file mode 100644 index 0000000..f38eea6 --- /dev/null +++ b/source/gui/buttons/exportButton.cpp @@ -0,0 +1,20 @@ +#include "exportButton.h" +#include "gameManager.h" +#include "mapEditorAppState.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + ExportButton::ExportButton(Vector3 pos, Vector2 size) : Button(pos, size, "Export", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf"){} + + void ExportButton::onClick(){ + StateManager *sm = GameManager::getSingleton()->getStateManager(); + MapEditorAppState::MapEditor *mapEditor = ((MapEditorAppState*)sm->getAppStateByType((int)AppStateType::MAP_EDITOR))->getMapEditor(); + mapEditor->exportMap(); + } +} diff --git a/source/gui/buttons/exportButton.h b/source/gui/buttons/exportButton.h new file mode 100644 index 0000000..6066051 --- /dev/null +++ b/source/gui/buttons/exportButton.h @@ -0,0 +1,15 @@ +#ifndef EXPORT_BUTTON_H +#define EXPORT_BUTTON_H + +#include + +namespace battleship{ + class ExportButton : public vb01Gui::Button{ + public: + ExportButton(vb01::Vector3 pos, vb01::Vector2 size); + void onClick(); + }; +} + +#endif + diff --git a/source/gui/buttons/loadMapButton.cpp b/source/gui/buttons/loadMapButton.cpp new file mode 100644 index 0000000..9d16784 --- /dev/null +++ b/source/gui/buttons/loadMapButton.cpp @@ -0,0 +1,28 @@ +#include "loadMapButton.h" +#include "concreteGuiManager.h" +#include "gameManager.h" +#include "mapEditorAppState.h" +#include "loadingAppState.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + LoadMapButton::OkButton::OkButton(vb01::Vector3 pos, vb01::Vector2 size, vb01Gui::Listbox *listbox) : Button(pos, size, "Ok", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf"){ this->listbox = listbox; } + + void LoadMapButton::OkButton::onClick(){ + StateManager *sm = GameManager::getSingleton()->getStateManager(); + string name = wstringToString(listbox->getContents()[listbox->getSelectedOption()]); + handleLoadingGui(new LoadingAppState(new MapEditorAppState(name, Vector2::VEC_ZERO, false), "mapEditor.lua")); + } + + LoadMapButton::LoadMapButton(Vector3 pos, Vector2 size) : Button(pos, size, "Load map", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf"){} + + void LoadMapButton::onClick(){ + ConcreteGuiManager::getSingleton()->readLuaScreenScript("loadMap.lua"); + } +} diff --git a/source/gui/buttons/loadMapButton.h b/source/gui/buttons/loadMapButton.h new file mode 100644 index 0000000..997fea3 --- /dev/null +++ b/source/gui/buttons/loadMapButton.h @@ -0,0 +1,27 @@ +#ifndef LOAD_MAP_BUTTON_H +#define LOAD_MAP_BUTTON_H + +#include + +namespace vb01Gui{ + class Listbox; +} + +namespace battleship{ + class LoadMapButton : public vb01Gui::Button{ + public: + class OkButton : public vb01Gui::Button{ + public: + OkButton(vb01::Vector3, vb01::Vector2, vb01Gui::Listbox*); + void onClick(); + private: + vb01Gui::Listbox *listbox; + }; + + LoadMapButton(vb01::Vector3, vb01::Vector2); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/mainMenuButton.cpp b/source/gui/buttons/mainMenuButton.cpp new file mode 100644 index 0000000..f7516c9 --- /dev/null +++ b/source/gui/buttons/mainMenuButton.cpp @@ -0,0 +1,14 @@ +#include "mainMenuButton.h" +#include "gameManager.h" +#include "concreteGuiManager.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + MainMenuButton::MainMenuButton(Vector3 pos, Vector2 size, string name) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true){} + + void MainMenuButton::onClick(){ + } +} diff --git a/source/gui/buttons/mainMenuButton.h b/source/gui/buttons/mainMenuButton.h new file mode 100644 index 0000000..dff8db3 --- /dev/null +++ b/source/gui/buttons/mainMenuButton.h @@ -0,0 +1,15 @@ +#ifndef MAIN_MENU_BUTTON_H +#define MAIN_MENU_BUTTON_H + +#include + +namespace battleship{ + class MainMenuButton : public vb01Gui::Button{ + public: + MainMenuButton(vb01::Vector3, vb01::Vector2, std::string); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/mapEditorButton.cpp b/source/gui/buttons/mapEditorButton.cpp new file mode 100644 index 0000000..2e7ef4e --- /dev/null +++ b/source/gui/buttons/mapEditorButton.cpp @@ -0,0 +1,8 @@ +#include "mapEditorButton.h" +#include "concreteGuiManager.h" + +namespace battleship{ + void MapEditorButton::onClick(){ + ConcreteGuiManager::getSingleton()->readLuaScreenScript("mapEditorMenu.lua"); + } +} diff --git a/source/gui/buttons/mapEditorButton.h b/source/gui/buttons/mapEditorButton.h new file mode 100644 index 0000000..5c3be9d --- /dev/null +++ b/source/gui/buttons/mapEditorButton.h @@ -0,0 +1,17 @@ +#ifndef MAP_EDITOR_BUTTON_H +#define MAP_EDITOR_BUTTON_H + +#include + +#include "gameManager.h" + +namespace battleship{ + class MapEditorButton : public vb01Gui::Button{ + public: + MapEditorButton(vb01::Vector3 pos, vb01::Vector2 size) : vb01Gui::Button(pos, size, "Map editor", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf"){} + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/minimapButton.cpp b/source/gui/buttons/minimapButton.cpp new file mode 100644 index 0000000..7949da1 --- /dev/null +++ b/source/gui/buttons/minimapButton.cpp @@ -0,0 +1,30 @@ +#include "minimapButton.h" +#include "activeGameState.h" +#include "game.h" +#include "map.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace battleship{ + using namespace vb01; + using namespace std; + + void MinimapButton::onClick(){ + Vector2 clickPos = getCursorPos(); + float posXRatio = (clickPos.x - pos.x) / size.x; + float posYRatio = (clickPos.y - pos.y) / size.y; + + Camera *cam = Root::getSingleton()->getCamera(); + Vector3 mapSize = Map::getSingleton()->getMapSize(); + cam->setPosition(Vector3(mapSize.x * (-.5 + posXRatio), cam->getPosition().y, mapSize.z * (-.5 + posYRatio))); + } +} diff --git a/source/gui/buttons/minimapButton.h b/source/gui/buttons/minimapButton.h new file mode 100644 index 0000000..bd6a402 --- /dev/null +++ b/source/gui/buttons/minimapButton.h @@ -0,0 +1,20 @@ +#ifndef MINIMAP_BUTTON_H +#define MINIMAP_BUTTON_H + +#include "activeStateButton.h" + +namespace vb01{ + class Node; +} + +namespace battleship{ + class MinimapButton : public ActiveStateButton{ + public: + MinimapButton(vb01::Vector3 pos, vb01::Vector2 size, std::string ip) : ActiveStateButton(pos, size, "", "minimap", "", -1, ip){} + ~MinimapButton(){} + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/newMapButton.cpp b/source/gui/buttons/newMapButton.cpp new file mode 100644 index 0000000..f85fee5 --- /dev/null +++ b/source/gui/buttons/newMapButton.cpp @@ -0,0 +1,38 @@ +#include "newMapButton.h" +#include "gameManager.h" +#include "mapEditorAppState.h" +#include "loadingAppState.h" +#include "concreteGuiManager.h" + +#include + +#include +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + NewMapButton::OkButton::OkButton(vb01::Vector3 pos, vb01::Vector2 size, vb01Gui::Textbox *name, vb01Gui::Textbox *sx, vb01Gui::Textbox *sy) : Button(pos, size, "Ok", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true){ + this->name = name; + this->sizeX = sx; + this->sizeY = sy; + } + + void NewMapButton::OkButton::onClick(){ + Vector2 size = Vector2( + atof(wstringToString(sizeX->getText()).c_str()), + atof(wstringToString(sizeY->getText()).c_str()) + ); + handleLoadingGui(new LoadingAppState(new MapEditorAppState(wstringToString(name->getText()), size, true), "mapEditor.lua")); + } + + NewMapButton::NewMapButton(Vector3 pos, Vector2 size) : Button(pos, size, "New map", GameManager::getSingleton()->getPath() + "Fonts/batang.ttf"){} + + void NewMapButton::onClick(){ + ConcreteGuiManager::getSingleton()->readLuaScreenScript("newMap.lua"); + } +} + diff --git a/source/gui/buttons/newMapButton.h b/source/gui/buttons/newMapButton.h new file mode 100644 index 0000000..fd48fb0 --- /dev/null +++ b/source/gui/buttons/newMapButton.h @@ -0,0 +1,27 @@ +#ifndef NEW_MAP_BUTTON_H +#define NEW_MAP_BUTTON_H + +#include + +namespace vb01Gui{ + class Textbox; +} + +namespace battleship{ + class NewMapButton : public vb01Gui::Button{ + public: + class OkButton : public vb01Gui::Button{ + public: + OkButton(vb01::Vector3, vb01::Vector2, vb01Gui::Textbox*, vb01Gui::Textbox*, vb01Gui::Textbox*); + void onClick(); + private: + vb01Gui::Textbox *name, *sizeX, *sizeY; + }; + + NewMapButton(vb01::Vector3, vb01::Vector2); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/offerButton.cpp b/source/gui/buttons/offerButton.cpp new file mode 100644 index 0000000..c9bf9f7 --- /dev/null +++ b/source/gui/buttons/offerButton.cpp @@ -0,0 +1,40 @@ +#include "offerButton.h" +#include "concreteGuiManager.h" +#include "activeGameState.h" +#include "gameManager.h" +#include "tradeOffer.h" +#include "player.h" +#include "game.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + OfferButton::OfferButton(Vector3 pos, Vector2 size, int plId, string name, int trigger, string imagePath) : + Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, true, imagePath), + playerId(plId){} + + void OfferButton::onClick(){ + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + vector textboxes = guiManager->getTextboxes(); + + int buyRef = (textboxes[0]->getText() == L"" ? 0 : stoi(textboxes[0]->getText())); + int sellRef = (textboxes[1]->getText() == L"" ? 0 : stoi(textboxes[1]->getText())); + int buyWealth = (textboxes[2]->getText() == L"" ? 0 : stoi(textboxes[2]->getText())); + int sellWealth = (textboxes[3]->getText() == L"" ? 0 : stoi(textboxes[3]->getText())); + int buyRes = (textboxes[4]->getText() == L"" ? 0 : stoi(textboxes[4]->getText())); + int sellRes = (textboxes[5]->getText() == L"" ? 0 : stoi(textboxes[5]->getText())); + + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)stateManager->getAppStateByType((int)AppStateType::ACTIVE_STATE); + Player *mainPlayer = activeState->getPlayer(); + + mainPlayer->addTradeOffer(Game::getSingleton()->getPlayer(playerId), new TradeOffer(buyRef, sellRef, buyWealth, sellWealth, buyRes, sellRes)); + } +} diff --git a/source/gui/buttons/offerButton.h b/source/gui/buttons/offerButton.h new file mode 100644 index 0000000..6de5592 --- /dev/null +++ b/source/gui/buttons/offerButton.h @@ -0,0 +1,17 @@ +#ifndef OFFER_BUTTON_H +#define OFFER_BUTTON_H + +#include + +namespace battleship{ + class OfferButton : public vb01Gui::Button{ + public: + OfferButton(vb01::Vector3, vb01::Vector2, int, std::string, int, std::string); + void onClick(); + inline void setPlayerId(int plId){playerId = plId;} + private: + int playerId; + }; +} + +#endif diff --git a/source/gui/buttons/okButton.cpp b/source/gui/buttons/okButton.cpp new file mode 100644 index 0000000..91aa580 --- /dev/null +++ b/source/gui/buttons/okButton.cpp @@ -0,0 +1,12 @@ +#include "okButton.h" +#include "gameManager.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + OkButton::OkButton(Vector3 pos, Vector2 size, string name) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true) {} + + void OkButton::onClick() {} +} diff --git a/source/gui/buttons/okButton.h b/source/gui/buttons/okButton.h new file mode 100644 index 0000000..9aedbd7 --- /dev/null +++ b/source/gui/buttons/okButton.h @@ -0,0 +1,15 @@ +#ifndef OK_BUTTON_H +#define OK_BUTTON_H + +#include + +namespace battleship{ + class OkButton : public vb01Gui::Button{ + public: + OkButton(vb01::Vector3, vb01::Vector2, std::string); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/optionsButton.cpp b/source/gui/buttons/optionsButton.cpp new file mode 100644 index 0000000..1694a2e --- /dev/null +++ b/source/gui/buttons/optionsButton.cpp @@ -0,0 +1,15 @@ +#include "optionsButton.h" +#include "gameManager.h" +#include "concreteGuiManager.h" + +using namespace vb01Gui; +using namespace vb01; +using namespace std; + +namespace battleship{ + OptionsButton::OptionsButton(Vector3 pos, Vector2 size, string name, bool separate) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, separate) {} + + void OptionsButton::onClick() { + ConcreteGuiManager::getSingleton()->readLuaScreenScript("options.lua"); + } +} diff --git a/source/gui/buttons/optionsButton.h b/source/gui/buttons/optionsButton.h new file mode 100644 index 0000000..becfa2b --- /dev/null +++ b/source/gui/buttons/optionsButton.h @@ -0,0 +1,15 @@ +#pragma once +#ifndef OPTIONS_BUTTON_H +#define OPTIONS_BUTTON_H + +#include + +namespace battleship { + class OptionsButton : public vb01Gui::Button { + public: + OptionsButton(vb01::Vector3, vb01::Vector2, std::string, bool); + virtual void onClick(); + }; +} + +#endif diff --git a/source/gui/buttons/orderButton.cpp b/source/gui/buttons/orderButton.cpp new file mode 100644 index 0000000..0251c06 --- /dev/null +++ b/source/gui/buttons/orderButton.cpp @@ -0,0 +1,69 @@ +#include "orderButton.h" +#include "gameManager.h" +#include "activeGameState.h" +#include "player.h" +#include "unit.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + OrderButton::OrderButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath, int oid) : + Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, true, imagePath), + orderId(oid) + {} + + void OrderButton::onClick(){ + ActiveGameState *activeState = ((ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE)); + Player *player = activeState->getPlayer(); + vector units = player->getSelectedUnits(); + + if(orderId > -1){ + switch(Order::TYPE(orderId)){ + case Order::TYPE::EJECT: + { + for(Unit *unit : units) + unit->receiveOrder(Order(Order::TYPE::EJECT, vector{}), false); + + break; + } + default: + { + ActiveGameState::CursorState cs; + + switch((Order::TYPE)orderId){ + case Order::TYPE::ATTACK: + cs = ActiveGameState::CursorState::ATTACK; + break; + case Order::TYPE::GARRISON: + cs = ActiveGameState::CursorState::GARRISON; + break; + case Order::TYPE::SUPPLY: + cs = ActiveGameState::CursorState::SUPPLY; + break; + case Order::TYPE::LOAD: + cs = ActiveGameState::CursorState::LOAD; + break; + case Order::TYPE::UNLOAD: + cs = ActiveGameState::CursorState::UNLOAD; + break; + case Order::TYPE::HACK: + cs = ActiveGameState::CursorState::HACK; + break; + } + + activeState->setCursorState(cs); + activeState->setForceCursorState(true); + break; + } + } + } + else + for(Unit *unit : units) + unit->halt(); + } +} diff --git a/source/gui/buttons/orderButton.h b/source/gui/buttons/orderButton.h new file mode 100644 index 0000000..fa05fc0 --- /dev/null +++ b/source/gui/buttons/orderButton.h @@ -0,0 +1,16 @@ +#ifndef ORDER_BUTTON_H +#define ORDER_BUTTON_H + +#include + +namespace battleship{ + class OrderButton : public vb01Gui::Button{ + public: + OrderButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string, int); + void onClick(); + private: + int orderId; + }; +} + +#endif diff --git a/source/gui/buttons/playButton.cpp b/source/gui/buttons/playButton.cpp new file mode 100644 index 0000000..4f5d38c --- /dev/null +++ b/source/gui/buttons/playButton.cpp @@ -0,0 +1,72 @@ +#include "playButton.h" +#include "gameManager.h" +#include "inGameAppState.h" +#include "loadingAppState.h" +#include "concreteGuiManager.h" +#include "game.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + PlayButton::PlayButton(Listbox *ml, Vector3 pos, Vector2 size, string name, bool separate) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", GLFW_KEY_P, separate), mapListbox(ml) {} + + void PlayButton::onClick() { + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + vector factionsListboxes, difficultiesListboxes, colorsListboxes, teamsListboxes; + + for(Listbox *listbox : guiManager->getListboxes()){ + string name = listbox->getName(); + + if(name == "factions") factionsListboxes.push_back(listbox); + else if(name == "difficulties") difficultiesListboxes.push_back(listbox); + else if(name == "colors") colorsListboxes.push_back(listbox); + else if(name == "teams") teamsListboxes.push_back(listbox); + } + + Game *game = Game::getSingleton(); + game->initTechnologies(); + + int selectedMap = mapListbox->getSelectedOption(); + string mapName = wstringToString(mapListbox->getContents()[selectedMap]); + int numPlayers = Map::getSingleton()->getNumMapSpawnPoints(mapName); + + for(int i = 0; i < numPlayers; i++){ + int factionChoice = factionsListboxes[i]->getSelectedOption(); + + if(factionChoice == 0) continue; + + int faction = factionChoice - 1; + + bool cpuPlayer = (i > 0); + string diffStr = (cpuPlayer ? wstringToString(difficultiesListboxes[i]->getContents()[colorsListboxes[i]->getSelectedOption()]) : ""); + int difficulty = -1; + + if(diffStr == "Easy") difficulty = 0; + else if(diffStr == "Medium") difficulty = 1; + else if(diffStr == "Hard") difficulty = 2; + + string colorStr = wstringToString(colorsListboxes[i]->getContents()[colorsListboxes[i]->getSelectedOption()]); + Vector3 color; + + if(colorStr == "Black") color = Vector3::VEC_ZERO; + else if(colorStr == "Red") color = Vector3::VEC_I; + else if(colorStr == "Green") color = Vector3::VEC_J; + else if(colorStr == "Blue") color = Vector3::VEC_K; + else if(colorStr == "White") color = Vector3::VEC_IJK; + + int team = stoi(teamsListboxes[i]->getContents()[teamsListboxes[i]->getSelectedOption()]); + + string name = (cpuPlayer ? "CPU player #" + to_string(i) : "Player"); + game->addPlayer(new Player(difficulty, faction, team, color, cpuPlayer, i, name)); + } + + handleLoadingGui(new LoadingAppState(new InGameAppState(mapName), "inGame.lua")); + } +} diff --git a/source/gui/buttons/playButton.h b/source/gui/buttons/playButton.h new file mode 100644 index 0000000..d265cdc --- /dev/null +++ b/source/gui/buttons/playButton.h @@ -0,0 +1,20 @@ +#ifndef PLAY_BUTTON_H +#define PLAY_BUTTON_H + +#include + +namespace vb01Gui{ + class Listbox; +} + +namespace battleship{ + class PlayButton : public vb01Gui::Button { + public: + PlayButton(vb01Gui::Listbox*, vb01::Vector3, vb01::Vector2, std::string, bool); + void onClick(); + private: + vb01Gui::Listbox *mapListbox = nullptr; + }; +} + +#endif diff --git a/source/gui/buttons/playerTradeButton.cpp b/source/gui/buttons/playerTradeButton.cpp new file mode 100644 index 0000000..547a15a --- /dev/null +++ b/source/gui/buttons/playerTradeButton.cpp @@ -0,0 +1,65 @@ +#include "playerTradeButton.h" +#include "concreteGuiManager.h" +#include "activeGameState.h" +#include "offerButton.h" +#include "game.h" + +#include +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + PlayerTradeButton::PlayerTradeButton(Vector3 pos, Vector2 size, string gs, string name, int trigger, string imagePath) : ActiveStateButton(pos, size, gs, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, imagePath){} + + void PlayerTradeButton::onClick(){ + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)stateManager->getAppStateByType((int)AppStateType::ACTIVE_STATE); + + if(activeState->isTradingScreen() && !activeState->isOfferScreen()) return; + else if(!activeState->isTradingScreen()) activeState->setTradingScreen(true); + else if(activeState->isOfferScreen()) activeState->setOfferScreen(false); + + ActiveStateButton::onClick(); + + Player *mainPlayer = activeState->getPlayer(); + Game *game = Game::getSingleton(); + + vector players = game->getPlayers(); + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + sol::state_view SOL_LUA_VIEW = generateView(); + + for(int i = 0, lineId = 0; i < players.size(); i++){ + if(players[i] == mainPlayer) continue; + + SOL_LUA_VIEW.script("lineId = " + to_string(lineId)); + SOL_LUA_VIEW.script("playerId = " + to_string(i)); + SOL_LUA_VIEW.script(string("relationsIcon = ") + (players[i]->getTeam() == mainPlayer->getTeam() ? "allianceIcon" : "warIcon")); + guiManager->parseLuaScript("tradingPlayerGuiTray.lua"); + + vector guiButtons = guiManager->getButtons(); + + for(int j = guiButtons.size() - 1; j > 0; j--) + if(guiButtons[j]->getName() == "Offer") + ((OfferButton*)guiButtons[j])->setPlayerId(i); + + int numListboxes = guiManager->getListboxes().size(); + Listbox *listbox = guiManager->getListboxes()[numListboxes - 1]; + int numOffers = mainPlayer->getTradeOffers(players[i]).size(); + + for(int j = 0; j < numOffers; j++) + listbox->addLine(L"TRADE_OFFER_" + to_wstring(j)); + + if(listbox->getNumLines() < listbox->getMaxDisplay()) + listbox->setMaxDisplay(listbox->getNumLines()); + + int numTexts = guiManager->getTexts().size(); + guiManager->getTexts()[numTexts - 1]->setText(stringToWstring(players[i]->getName())); + + lineId++; + } + } +} diff --git a/source/gui/buttons/playerTradeButton.h b/source/gui/buttons/playerTradeButton.h new file mode 100644 index 0000000..3528056 --- /dev/null +++ b/source/gui/buttons/playerTradeButton.h @@ -0,0 +1,15 @@ +#ifndef PLAYER_TRADE_BUTTON_H +#define PLAYER_TRADE_BUTTON_H + +#include "activeStateButton.h" + +namespace battleship{ + class PlayerTradeButton : public ActiveStateButton{ + public: + PlayerTradeButton(vb01::Vector3, vb01::Vector2, std::string, std::string, int, std::string); + void onClick(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/researchButton.cpp b/source/gui/buttons/researchButton.cpp new file mode 100644 index 0000000..21d1944 --- /dev/null +++ b/source/gui/buttons/researchButton.cpp @@ -0,0 +1,94 @@ +#include "researchButton.h" +#include "activeGameState.h" +#include "researchStruct.h" +#include "game.h" + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +namespace battleship{ + using namespace vb01; + using namespace std; + + ResearchButton::ResearchButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath, int tid) : + UnitButton(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, imagePath), techId(tid){ + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->addVec4Uniform("diffuseColor", Vector4::VEC_IJKL); + + Quad *quad = new Quad(Vector3(size.x, size.y, 1), false); + quad->setMaterial(mat); + + overlay = new Node(Vector3(pos.x, pos.y, .12)); + overlay->attachMesh(quad); + root->getGuiNode()->attachChild(overlay); + } + + ResearchButton::~ResearchButton(){ + Root::getSingleton()->getGuiNode()->dettachChild(overlay); + delete overlay; + } + + void ResearchButton::onClick(){ + if(!active) return; + + vector labs = getUnits(); + + for(Unit *lab : labs) + ((ResearchStruct*)lab)->appendToQueue(techId); + } + + void ResearchButton::update(){ + Button::update(); + + ActiveGameState *activeState = (ActiveGameState*)(GameManager::getSingleton()->getStateManager()->getAppStateByType((int)AppStateType::ACTIVE_STATE)); + vector technologies = activeState->getPlayer()->getTechnologies(); + bool hasTech = (find(technologies.begin(), technologies.end(), techId) != technologies.end()); + + float alpha = .6; + string uniform = "diffuseColor"; + active = false; + + if(!hasTech){ + vector techParents = Game::getSingleton()->getTechnology(techId).parents; + bool hasTechParents = true; + + for(int tp : techParents) + if(find(technologies.begin(), technologies.end(), tp) == technologies.end()){ + hasTechParents = false; + break; + } + + if(hasTechParents){ + active = true; + overlay->setVisible(false); + + vector labs = getUnits(); + + for(Unit *lab : labs){ + vector researchQueue = ((ResearchStruct*)lab)->getQueue(); + + if(find(researchQueue.begin(), researchQueue.end(), techId) != researchQueue.end()){ + active = false; + overlay->setVisible(true); + overlay->getMesh(0)->getMaterial()->setVec4Uniform(uniform, Vector4(0, 0, 1, alpha)); + break; + } + } + } + else + overlay->getMesh(0)->getMaterial()->setVec4Uniform(uniform, Vector4(1, 0, 0, alpha)); + } + else + overlay->getMesh(0)->getMaterial()->setVec4Uniform(uniform, Vector4(0, 0, 0, alpha)); + } +} diff --git a/source/gui/buttons/researchButton.h b/source/gui/buttons/researchButton.h new file mode 100644 index 0000000..f6e2d2b --- /dev/null +++ b/source/gui/buttons/researchButton.h @@ -0,0 +1,20 @@ +#ifndef RESEARCH_BUTTON_H +#define RESEARCH_BUTTON_H + +#include "unitButton.h" + +namespace battleship{ + class ResearchButton : public UnitButton{ + public: + ResearchButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string, int); + ~ResearchButton(); + void onClick(); + void update(); + private: + int techId; + bool active = false; + vb01::Node *overlay = nullptr; + }; +} + +#endif diff --git a/source/gui/buttons/resourceAmmountButton.cpp b/source/gui/buttons/resourceAmmountButton.cpp new file mode 100644 index 0000000..2d1448f --- /dev/null +++ b/source/gui/buttons/resourceAmmountButton.cpp @@ -0,0 +1,25 @@ +#include "resourceAmmountButton.h" +#include "concreteGuiManager.h" +#include "gameManager.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + ResourceAmmountButton::ResourceAmmountButton(Vector3 pos, Vector2 size, string name, int amm, int trigger, string imagePath) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, true, imagePath), ammount(amm) { + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + int numTextboxes = guiManager->getTextboxes().size(); + textbox = guiManager->getTextboxes()[numTextboxes - 1]; + } + + void ResourceAmmountButton::onClick(){ + wstring text = textbox->getText(); + int amm = (text != L"" ? stoi(text) : 0) + ammount; + + if(minAmmount <= amm && amm <= maxAmmount) + textbox->setEntry(to_wstring(amm)); + } +} diff --git a/source/gui/buttons/resourceAmmountButton.h b/source/gui/buttons/resourceAmmountButton.h new file mode 100644 index 0000000..390dedd --- /dev/null +++ b/source/gui/buttons/resourceAmmountButton.h @@ -0,0 +1,21 @@ +#ifndef RESOURCE_AMMOUNT_BUTTON_H +#define RESOURCE_AMMOUNT_BUTTON_H + +#include + +namespace vb01Gui{ + class Textbox; +} + +namespace battleship{ + class ResourceAmmountButton : public vb01Gui::Button{ + public: + ResourceAmmountButton(vb01::Vector3, vb01::Vector2, std::string, int, int, std::string); + void onClick(); + private: + vb01Gui::Textbox *textbox = nullptr; + int ammount, minAmmount = 0, maxAmmount = 10000; + }; +} + +#endif diff --git a/source/gui/buttons/singlePlayerButton.cpp b/source/gui/buttons/singlePlayerButton.cpp new file mode 100644 index 0000000..82cb408 --- /dev/null +++ b/source/gui/buttons/singlePlayerButton.cpp @@ -0,0 +1,31 @@ +#include "singlePlayerButton.h" +#include "gameManager.h" +#include "concreteGuiManager.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + SinglePlayerButton::SinglePlayerButton(Vector3 pos, Vector2 size, string name) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", GLFW_KEY_S, true) {} + + void SinglePlayerButton::onMouseOver(){ + setColor(Vector4(.8, .8, .8, 1)); + } + + void SinglePlayerButton::onMouseOff(){ + setColor(Vector4(.6, .6, .6, 1)); + } + + void SinglePlayerButton::onClick() { + ConcreteGuiManager::getSingleton()->readLuaScreenScript("singlePlayerMenu.lua"); + + Listbox *listbox = ConcreteGuiManager::getSingleton()->getListboxes()[0]; + listbox->openUp(); + listbox->close(); + } +} diff --git a/source/gui/buttons/singlePlayerButton.h b/source/gui/buttons/singlePlayerButton.h new file mode 100644 index 0000000..3b3dff6 --- /dev/null +++ b/source/gui/buttons/singlePlayerButton.h @@ -0,0 +1,19 @@ +#ifndef SINGLE_PLAYER_BUTTON_H +#define SINGLE_PLAYER_BUTTON_H + +#include + +#include + +namespace battleship{ + class SinglePlayerButton : public vb01Gui::Button { + public: + SinglePlayerButton(vb01::Vector3, vb01::Vector2, std::string); + void onClick(); + void onMouseOver(); + void onMouseOff(); + private: + }; +} + +#endif diff --git a/source/gui/buttons/stateToggleButton.cpp b/source/gui/buttons/stateToggleButton.cpp new file mode 100644 index 0000000..52b09af --- /dev/null +++ b/source/gui/buttons/stateToggleButton.cpp @@ -0,0 +1,71 @@ +#include "stateToggleButton.h" +#include "gameManager.h" +#include "activeGameState.h" +#include "player.h" +#include "unit.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + StateToggleButton::StateToggleButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, true, imagePath) { + updateStateId(); + toggleImage(); + } + + void StateToggleButton::updateStateId(){ + const int NUM_STATES = 3; + int numUnitsByState[NUM_STATES]{0, 0, 0}; + + Player *player = ((ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE))->getPlayer(); + vector units = player->getSelectedUnits(); + + for(Unit *unit : units) + numUnitsByState[(int)unit->getState()]++; + + int maxId = 0, maxNum = numUnitsByState[maxId]; + + for(int i = 0; i < NUM_STATES; i++) + if(maxNum < numUnitsByState[i]){ + maxId = i; + maxNum = numUnitsByState[maxId]; + } + + currStateId = maxId; + } + + void StateToggleButton::toggleImage(){ + string imgPath = ""; + + switch(Unit::State(currStateId)){ + case Unit::State::HOLD_FIRE: + imgPath = "holdFire.png"; + break; + case Unit::State::CHASE: + imgPath = "chase.png"; + break; + case Unit::State::STAND_GROUND: + imgPath = "standGround.png"; + break; + } + + setImage(GameManager::getSingleton()->getPath() + "Textures/Icons/Buttons/" + imgPath); + } + + void StateToggleButton::onClick(){ + const int NUM_STATES = 3; + Unit::State nextState = (currStateId + 1 == NUM_STATES ? Unit::State(0) : Unit::State(currStateId + 1)); + + Player *player = ((ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE))->getPlayer(); + vector units = player->getSelectedUnits(); + + for(Unit *unit : units) unit->setState(nextState); + + updateStateId(); + toggleImage(); + } +} diff --git a/source/gui/buttons/stateToggleButton.h b/source/gui/buttons/stateToggleButton.h new file mode 100644 index 0000000..774e087 --- /dev/null +++ b/source/gui/buttons/stateToggleButton.h @@ -0,0 +1,19 @@ +#ifndef STATE_TOGGLE_BUTTON_H +#define STATE_TOGGLE_BUTTON_H + +#include + +namespace battleship{ + class StateToggleButton : public vb01Gui::Button{ + public: + StateToggleButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string); + void onClick(); + private: + void updateStateId(); + void toggleImage(); + + int currStateId, x; + }; +} + +#endif diff --git a/source/gui/buttons/statsButton.cpp b/source/gui/buttons/statsButton.cpp new file mode 100644 index 0000000..d542bb9 --- /dev/null +++ b/source/gui/buttons/statsButton.cpp @@ -0,0 +1,75 @@ +#include "statsButton.h" +#include "gameManager.h" +#include "concreteGuiManager.h" +#include "inGameAppState.h" +#include "game.h" +#include "map.h" + +#include +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + StatsButton::StatsButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, true, imagePath){} + + void StatsButton::addPlayerDataGuiElements(int playerId){ + const int numPairs = 7; + Player *player = Game::getSingleton()->getPlayer(playerId); + pair unitDataPairs[numPairs]{ + make_pair("pl", stringToWstring(player->getName())), + make_pair("vb", to_wstring(player->getNumVehiclesBuilt())), + make_pair("vd", to_wstring(player->getNumVehiclesDestroyed())), + make_pair("vl", to_wstring(player->getNumVehiclesLost())), + make_pair("sb", to_wstring(player->getNumStructuresBuilt())), + make_pair("sd", to_wstring(player->getNumStructuresDestroyed())), + make_pair("sl", to_wstring(player->getNumStructuresLost())), + }; + + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + Vector3 plCol = player->getColor(); + Root *root = Root::getSingleton(); + sol::state_view SOL_STATE_VIEW = generateView(); + float statsInitHeight = SOL_STATE_VIEW["statsInitHeight"], statsSpace = SOL_STATE_VIEW["statsSpace"]; + string fontName = SOL_STATE_VIEW["fontName"]; + + for(int i = 0; i < numPairs; i++){ + Text *category = guiManager->getText(unitDataPairs[i].first); + + Material *categoryMat = new Material(root->getLibPath() + "text"); + categoryMat->addBoolUniform("texturingEnabled", false); + categoryMat->addVec4Uniform("diffuseColor", Vector4(plCol.x, plCol.y, plCol.z, 1)); + + Text *categoryVal = new Text(GameManager::getSingleton()->getPath() + "Fonts/" + fontName, unitDataPairs[i].second); + categoryVal->setScale(category->getScale()); + categoryVal->setMaterial(categoryMat); + + Node *categoryValNode = new Node(category->getNode()->getPosition() + Vector3(0, statsInitHeight + statsSpace * playerId, 0)); + categoryValNode->addText(categoryVal); + root->getGuiNode()->attachChild(categoryValNode); + + guiManager->addText(categoryVal); + } + } + + void StatsButton::onClick(){ + ConcreteGuiManager::getSingleton()->readLuaScreenScript("statistics.lua"); + + Map::getSingleton()->unload(); + + StateManager *sm = GameManager::getSingleton()->getStateManager(); + InGameAppState *inGameState = (InGameAppState*)sm->getAppStateByType(int(AppStateType::IN_GAME_STATE)); + sm->dettachAppState(inGameState); + delete inGameState; + + Game *game = Game::getSingleton(); + int numPlayers = game->getNumPlayers(); + + for(int i = 0; i < numPlayers; i++) + addPlayerDataGuiElements(i); + + game->removeAllElements(); + } +} diff --git a/source/gui/buttons/statsButton.h b/source/gui/buttons/statsButton.h new file mode 100644 index 0000000..57e73cf --- /dev/null +++ b/source/gui/buttons/statsButton.h @@ -0,0 +1,16 @@ +#ifndef STATS_BUTTON_H +#define STATS_BUTTON_H + +#include + +namespace battleship { + class StatsButton : public vb01Gui::Button{ + public: + StatsButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string); + void onClick(); + private: + void addPlayerDataGuiElements(int); + }; +} + +#endif diff --git a/source/gui/buttons/tabButton.cpp b/source/gui/buttons/tabButton.cpp new file mode 100644 index 0000000..64cf1cd --- /dev/null +++ b/source/gui/buttons/tabButton.cpp @@ -0,0 +1,17 @@ +#include "tabButton.h" +#include "concreteGuiManager.h" +#include "gameManager.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + + TabButton::TabButton(Vector3 pos, Vector2 size, string name, string screenScript) : Button(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", -1, true) { + this->screenScript = screenScript; + } + + void TabButton::onClick() { + ConcreteGuiManager::getSingleton()->readLuaScreenScript(screenScript); + } +} diff --git a/source/gui/buttons/tabButton.h b/source/gui/buttons/tabButton.h new file mode 100644 index 0000000..5d91331 --- /dev/null +++ b/source/gui/buttons/tabButton.h @@ -0,0 +1,16 @@ +#ifndef TAB_BUTTON_H +#define TAB_BUTTON_H + +#include + +namespace battleship{ + class TabButton : public vb01Gui::Button{ + public: + TabButton(vb01::Vector3, vb01::Vector2, std::string, std::string); + void onClick(); + private: + std::string screenScript; + }; +} + +#endif diff --git a/source/gui/buttons/tradeButton.cpp b/source/gui/buttons/tradeButton.cpp new file mode 100644 index 0000000..51b98a5 --- /dev/null +++ b/source/gui/buttons/tradeButton.cpp @@ -0,0 +1,42 @@ +#include "tradeButton.h" +#include "activeGameState.h" +#include "player.h" + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + TradeButton::TradeButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath, Type t) : UnitButton(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, imagePath), type(t){} + + void TradeButton::onClick(){ + ActiveGameState *activeState = (ActiveGameState*)(GameManager::getSingleton()->getStateManager()->getAppStateByType((int)AppStateType::ACTIVE_STATE)); + Player *player = activeState->getPlayer(); + + int resType, ammount = 10; + bool buy; + + switch(type){ + case Type::BUY_REFINEDS: + resType = (int)ResourceType::REFINEDS; + buy = true; + break; + case Type::SELL_REFINEDS: + resType = (int)ResourceType::REFINEDS; + buy = false; + break; + case Type::BUY_RESEARCH: + resType = (int)ResourceType::RESEARCH; + buy = true; + break; + case Type::SELL_RESEARCH: + resType = (int)ResourceType::RESEARCH; + buy = false; + break; + } + + player->getTrader()->trade(player, resType, ammount, buy); + } +} diff --git a/source/gui/buttons/tradeButton.h b/source/gui/buttons/tradeButton.h new file mode 100644 index 0000000..604858b --- /dev/null +++ b/source/gui/buttons/tradeButton.h @@ -0,0 +1,18 @@ +#ifndef TRADE_BUTTON_H +#define TRADE_BUTTON_H + +#include "unitButton.h" + +namespace battleship{ + class TradeButton : public UnitButton{ + public: + enum class Type{BUY_REFINEDS, SELL_REFINEDS, BUY_RESEARCH, SELL_RESEARCH}; + + TradeButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string, Type); + void onClick(); + private: + Type type; + }; +} + +#endif diff --git a/source/gui/buttons/tradingScreenButton.cpp b/source/gui/buttons/tradingScreenButton.cpp new file mode 100644 index 0000000..ef5d591 --- /dev/null +++ b/source/gui/buttons/tradingScreenButton.cpp @@ -0,0 +1,59 @@ +#include "tradingScreenButton.h" +#include "concreteGuiManager.h" +#include "activeGameState.h" +#include "offerButton.h" +#include "gameManager.h" +#include "tradeOffer.h" +#include "game.h" + +#include +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + TradingScreenButton::TradingScreenButton(Vector3 pos, Vector2 size, Listbox *lb, int plId, string gs, string name, int trigger, string imagePath) : + ActiveStateButton(pos, size, gs, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, imagePath), + listbox(lb), + playerId(plId) + {} + + void TradingScreenButton::onClick(){ + generateView().script("playerId = " + to_string(playerId)); + + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + Game *game = Game::getSingleton(); + + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)stateManager->getAppStateByType((int)AppStateType::ACTIVE_STATE); + activeState->setOfferScreen(true); + + if(listbox->getNumLines() > 0){ + Player *mainPlayer = activeState->getPlayer(); + + TradeOffer *tradeOffer = mainPlayer->getTradeOffers(game->getPlayer(playerId))[listbox->getSelectedOption()]; + + ActiveStateButton::onClick(); + vector textboxes = guiManager->getTextboxes(); + + textboxes[0]->setEntry(to_wstring(tradeOffer->tradeResources[0][0])); + textboxes[1]->setEntry(to_wstring(tradeOffer->tradeResources[0][1])); + textboxes[2]->setEntry(to_wstring(tradeOffer->tradeResources[1][0])); + textboxes[3]->setEntry(to_wstring(tradeOffer->tradeResources[1][1])); + textboxes[4]->setEntry(to_wstring(tradeOffer->tradeResources[2][0])); + textboxes[5]->setEntry(to_wstring(tradeOffer->tradeResources[2][1])); + } + else + ActiveStateButton::onClick(); + + vector texts = guiManager->getTexts(); + texts[texts.size() - 2]->setText(stringToWstring(game->getPlayer(playerId)->getName())); + + guiManager->removeButton(this); + } +} diff --git a/source/gui/buttons/tradingScreenButton.h b/source/gui/buttons/tradingScreenButton.h new file mode 100644 index 0000000..3fa4dcc --- /dev/null +++ b/source/gui/buttons/tradingScreenButton.h @@ -0,0 +1,21 @@ +#ifndef TRADING_SCREEN_BUTTON_H +#define TRADING_SCREEN_BUTTON_H + +#include "activeStateButton.h" + +namespace vb01Gui{ + class Listbox; +} + +namespace battleship{ + class TradingScreenButton : public ActiveStateButton{ + public: + TradingScreenButton(vb01::Vector3, vb01::Vector2, vb01Gui::Listbox*, int, std::string, std::string, int, std::string); + void onClick(); + private: + int playerId; + vb01Gui::Listbox *listbox; + }; +} + +#endif diff --git a/source/gui/buttons/trainButton.cpp b/source/gui/buttons/trainButton.cpp new file mode 100644 index 0000000..eedc5ce --- /dev/null +++ b/source/gui/buttons/trainButton.cpp @@ -0,0 +1,24 @@ +#include "trainButton.h" +#include "activeGameState.h" +#include "buildableUnit.h" +#include "factory.h" + +#include +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + TrainButton::TrainButton(Vector3 pos, Vector2 size, string name, int trigger, string imagePath, int slId) : + UnitButton(pos, size, name, GameManager::getSingleton()->getPath() + "Fonts/batang.ttf", trigger, imagePath), + slotId(slId) {} + + void TrainButton::onClick(){ + vector factories = getUnits(); + + for(Unit *fac : factories) + ((Factory*)fac)->appendToQueue(slotId); + } +} diff --git a/source/gui/buttons/trainButton.h b/source/gui/buttons/trainButton.h new file mode 100644 index 0000000..6b7ca6e --- /dev/null +++ b/source/gui/buttons/trainButton.h @@ -0,0 +1,16 @@ +#ifndef TRAIN_BUTTON_H +#define TRAIN_BUTTON_H + +#include "unitButton.h" + +namespace battleship{ + class TrainButton : public UnitButton{ + public: + TrainButton(vb01::Vector3, vb01::Vector2, std::string, int, std::string, int); + void onClick(); + private: + int slotId; + }; +} + +#endif diff --git a/source/gui/buttons/unitButton.cpp b/source/gui/buttons/unitButton.cpp new file mode 100644 index 0000000..8b0c3f3 --- /dev/null +++ b/source/gui/buttons/unitButton.cpp @@ -0,0 +1,39 @@ +#include "unitButton.h" +#include "gameManager.h" +#include "activeGameState.h" + +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + UnitButton::UnitButton(Vector3 pos, Vector2 size, string name, string fontPath, int trigger, string imagePath) : Button(pos, size, name, fontPath, trigger, true, imagePath){ + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + ActiveGameState *activeState = (ActiveGameState*)stateManager->getAppStateByType((int)AppStateType::ACTIVE_STATE); + + if(activeState) activeState->addButton(this); + } + + //TODO optimize this code + vector UnitButton::getUnits(){ + vector units; + + ActiveGameState *activeState = (ActiveGameState*)(GameManager::getSingleton()->getStateManager()->getAppStateByType((int)AppStateType::ACTIVE_STATE)); + Player *player = activeState->getPlayer(); + + sol::state_view SOL_LUA_VIEW = generateView(); + int unitId = SOL_LUA_VIEW["_mainUnitId"]; + vector selUnits = player->getSelectedUnits(), researchStructs = player->getUnitsById(unitId); + + for(Unit *rs : researchStructs) + if(find(selUnits.begin(), selUnits.end(), rs) != selUnits.end()) + units.push_back(rs); + + return units; + } +} diff --git a/source/gui/buttons/unitButton.h b/source/gui/buttons/unitButton.h new file mode 100644 index 0000000..86944ad --- /dev/null +++ b/source/gui/buttons/unitButton.h @@ -0,0 +1,17 @@ +#ifndef UNIT_BUTTON_H +#define UNIT_BUTTON_H + +#include + +namespace battleship{ + class Unit; + + class UnitButton : public vb01Gui::Button{ + public: + UnitButton(vb01::Vector3, vb01::Vector2, std::string, std::string, int, std::string); + protected: + std::vector getUnits(); + }; +} + +#endif diff --git a/source/gui/concreteGuiManager.cpp b/source/gui/concreteGuiManager.cpp new file mode 100644 index 0000000..76c6939 --- /dev/null +++ b/source/gui/concreteGuiManager.cpp @@ -0,0 +1,652 @@ +#include +#include + +#include + +#include "concreteGuiManager.h" +#include "unit.h" +#include "gameManager.h" +#include "singlePlayerButton.h" +#include "mapEditorButton.h" +#include "optionsButton.h" +#include "exitButton.h" +#include "tabButton.h" +#include "okButton.h" +#include "defaultsButton.h" +#include "backButton.h" +#include "newMapButton.h" +#include "loadMapButton.h" +#include "exportButton.h" +#include "mapListbox.h" +#include "skyboxTextureListbox.h" +#include "landTextureListbox.h" +#include "gameObjectListbox.h" +#include "playButton.h" +#include "inGameAppState.h" +#include "mainMenuButton.h" +#include "buildButton.h" +#include "trainButton.h" +#include "statsButton.h" +#include "researchButton.h" +#include "tradeButton.h" +#include "activeStateButton.h" +#include "playerTradeButton.h" +#include "tradingScreenButton.h" +#include "offerButton.h" +#include "resourceAmmountButton.h" +#include "orderButton.h" +#include "stateToggleButton.h" +#include "minimapButton.h" +#include "activeStateBackButton.h" + +namespace battleship{ + using namespace std; + using namespace gameBase; + using namespace vb01; + using namespace vb01Gui; + + static ConcreteGuiManager *concreteGuiManager = nullptr; + + ConcreteGuiManager::ConcreteGuiManager(){ + string assetPath = GameManager::getSingleton()->getPath(); + texBasePath = assetPath + "Textures/"; + fontBasePath = assetPath + "Fonts/"; + } + + ConcreteGuiManager* ConcreteGuiManager::getSingleton(){ + if(!concreteGuiManager) + concreteGuiManager = new ConcreteGuiManager(); + + return concreteGuiManager; + } + + //TODO refactor player difficulty and faction listbox selection + //TODO remove hardcoded font path values + //TODO use configurable map path values + Button* ConcreteGuiManager::parseButton(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + sol::table posTable = guiTable["pos"]; + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + sol::table sizeTable = guiTable["size"]; + Vector2 size = Vector2(sizeTable["x"], sizeTable["y"]); + + //TODO factor out repetetive optional lua key checks + string name = "", nk = "name"; + sol::optional nameOpt = guiTable[nk]; + + if(nameOpt != sol::nullopt) name = guiTable[nk]; + + ButtonType type = (ButtonType)guiTable["buttonType"]; + + string imagePath = "", ipk = "imagePath"; + sol::optional pathOpt = guiTable[ipk]; + bool texturingEnabled = false; + + if(pathOpt != sol::nullopt){ + imagePath = texBasePath; + imagePath += guiTable[ipk]; + bool texturingEnabled = true; + } + + Button *button = nullptr; + string guiScreen = ""; + + switch(type){ + case SINGLE_PLAYER: + button = new SinglePlayerButton(pos, size, name); + break; + case EDITOR: + button = new MapEditorButton(pos, size); + break; + case OPTIONS: + button = new OptionsButton(pos, size, name, true); + break; + case EXIT: + button = new ExitButton(pos, size); + break; + case OK: + button = new OkButton(pos, size, name); + break; + case DEFAULTS: + button = new DefaultsButton(pos, size, name); + break; + case BACK: { + string screen = guiTable["screen"]; + button = new BackButton(pos, size, name, screen); + break; + } + case CONTROLS_TAB: + case MOUSE_TAB: + case VIDEO_TAB: + case AUDIO_TAB: + case MULTIPLAYER_TAB: { + string screens[]{ + "controlsTab.lua", + "mouseTab.lua", + "videoTab.lua", + "audioTab.lua", + "multiplayerTab.lua" + }; + int diff = ((int)type - (int)CONTROLS_TAB); + button = new TabButton(pos, size, name, screens[diff]); + break; + } + case NEW_MAP: + button = new NewMapButton(pos, size); + break; + case NEW_MAP_OK:{ + int numTextboxes = guiTable["numDependencies"]; + vector t; + + for(int i = 0; i < numTextboxes; i++){ + int tid = guiTable["dependencies"][i + 1]["id"]; + t.push_back((Textbox*)guiElements[tid].second); + } + + button = new NewMapButton::OkButton(pos, size, t[0], t[1], t[2]); + break; + } + case LOAD_MAP: + button = new LoadMapButton(pos, size); + break; + case LOAD_MAP_OK:{ + int lid = guiTable["dependencies"][1]["id"]; + button = new LoadMapButton::OkButton(pos, size, (Listbox*)guiElements[lid].second); + break; + } + case EXPORT: + button = new ExportButton(pos, size); + break; + case PLAY:{ + int mid = guiTable["dependencies"][1]["id"]; + Listbox *mapListbox = (MapListbox*)guiElements[mid].second; + button = new PlayButton(mapListbox, pos, size, name, true); + break; + } + case RESUME: + button = new InGameAppState::ResumeButton(pos, size); + break; + case CONSOLE_SCREEN: + button = new InGameAppState::ConsoleButton(pos, size); + break; + case MAIN_MENU: + button = new MainMenuButton(pos, size, name); + break; + case CONSOLE_COMMAND_OK:{ + int lid = guiTable["dependencies"][1]["id"]; + Listbox *listbox = (Listbox*)guiElements[lid].second; + + int tid = guiTable["dependencies"][2]["id"]; + Textbox *textbox = (Textbox*)guiElements[tid].second; + + button = new InGameAppState::ConsoleButton::ConsoleCommandEntryButton(textbox, listbox, pos, size, name); + break; + } + case BUILD: + button = new BuildButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["slotId"]); + break; + case TRAIN: + button = new TrainButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["slotId"]); + break; + case STATISTICS: + button = new StatsButton(pos, size, name, (int)guiTable["trigger"], imagePath); + break; + case RESEARCH: + button = new ResearchButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["techId"]); + break; + case BUY_REFINEDS: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::BUY_REFINEDS); + break; + case SELL_REFINEDS: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::SELL_REFINEDS); + break; + case BUY_RESEARCH: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::BUY_RESEARCH); + break; + case SELL_RESEARCH: + button = new TradeButton(pos, size, name, (int)guiTable["trigger"], imagePath, TradeButton::Type::SELL_RESEARCH); + break; + case ACTIVE_STATE_BUTTON: + button = new ActiveStateButton(pos, size, guiTable["guiScreen"], name, fontBasePath + "batang.ttf", (int)guiTable["trigger"], imagePath); + break; + case ACTIVE_STATE_BACK: + button = new ActiveStateBackButton(pos, size, name); + break; + case PLAYER_TRADE: + guiScreen = guiTable["guiScreen"]; + button = new PlayerTradeButton(pos, size, guiScreen, name, (int)guiTable["trigger"], imagePath); + break; + case TRADING_SCREEN:{ + int lid = guiTable["dependencies"][1]["id"]; + Listbox *listbox = (Listbox*)guiElements[lid].second; + guiScreen = guiTable["guiScreen"]; + + button = new TradingScreenButton(pos, size, listbox, (int)SOL_LUA_STATE["playerId"], guiScreen, name, (int)guiTable["trigger"], imagePath); + break; + } + case TRADE_OFFER: + button = new OfferButton(pos, size, (int)SOL_LUA_STATE["playerId"], name, (int)guiTable["trigger"], imagePath); + break; + case RESOURCE_AMMOUNT: + button = new ResourceAmmountButton(pos, size, name, (int)guiTable["ammount"], (int)guiTable["trigger"], imagePath); + break; + case ORDER: + button = new OrderButton(pos, size, name, (int)guiTable["trigger"], imagePath, (int)guiTable["orderType"]); + break; + case UNIT_STATE: + button = new StateToggleButton(pos, size, name, (int)guiTable["trigger"], imagePath); + break; + case MINIMAP:{ + string minimapPath = GameManager::getSingleton()->getPath() + "Models/Maps/" + Map::getSingleton()->getMapName() + "/minimap.jpg"; + button = new MinimapButton(pos, size, minimapPath); + break; + } + } + + int typeArr[2]{(int)GuiElementType::BUTTON, (int)type}; + guiElements.push_back(make_pair(typeArr, (void*)button)); + + return button; + } + + Listbox* ConcreteGuiManager::parseGameObjectListbox(){ + return nullptr; + } + + Listbox* ConcreteGuiManager::parseListbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + + string sizeTable = "size"; + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + + int numMaxDisplay = guiTable["numMaxDisplay"]; + ListboxType listboxType = (ListboxType)guiTable["listboxType"]; + + int maxDisplay, numLines; + bool closable; + vector lines; + sol::optional linesOpt = guiTable["lines"]; + + if(linesOpt != sol::nullopt){ + sol::table linesTbl = guiTable["lines"]; + + for(int i = 0; i < linesTbl.size(); i++) + lines.push_back(guiTable["lines"][i + 1]); + } + + Listbox *listbox = nullptr; + string fontPath = fontBasePath + "batang.ttf"; + sol::optional nameOpt = guiTable["name"]; + string name = ""; + + if(nameOpt != sol::nullopt) name = guiTable["name"]; + + switch(listboxType){ + case CONTROLS:{ + numLines = 6; + closable = false; + + for(int i = 0; i < numLines; i++) + lines.push_back(to_string(i)); + + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + + break; + } + case RESOLUTION:{ + numLines = guiTable["numLines"]; + + for(int i = 0; i < numLines; i++) + lines.push_back(guiTable["lines"][i + 1]); + + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + + break; + } + case MAPS:{ + lines = readDir(GameManager::getSingleton()->getPath() + "Models/Maps/", true); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + bool addPlayers = guiTable["addPlayerGui"]; + + listbox = new MapListbox(pos, size, lines, maxDisplay, addPlayers, fontPath, closable); + break; + } + case VEHICLES: + case STRUCTURES: + case RESOURCE_DEPOSITS:{ + bool resources = (listboxType == RESOURCE_DEPOSITS); + sol::table gameObjTable = SOL_LUA_STATE[resources ? "resources" : "units"]; + int numGameObjs = gameObjTable.size(); + std::vector gameObjIds; + + for(int i = 0; i < numGameObjs; i++){ + bool canAdd = true; + + if(!resources){ + bool vehicles = (listboxType == VEHICLES); + bool v = gameObjTable[i + 1]["isVehicle"]; + canAdd = (v == vehicles); + } + + if(canAdd){ + lines.push_back(gameObjTable[i + 1]["name"]); + gameObjIds.push_back(i); + } + } + + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new GameObjectListbox(!resources, pos, size, lines, gameObjIds, maxDisplay, fontPath); + break; + } + case SKYBOX_TEXTURES: + lines = readDir(texBasePath + "Skyboxes", true); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new SkyboxTextureListbox(pos, size, lines, maxDisplay, fontPath); + break; + case LAND_TEXTURES: + lines = readDir(texBasePath + "Landmass", false); + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new LandTextureListbox(pos, size, lines, maxDisplay, fontPath); + break; + case CPU_DIFFICULTIES: + case FACTIONS: + case COLORS: + case TEAMS: + numLines = lines.size(); + closable = true; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath); + break; + case CONSOLE:{ + for(int i = 0; i < numMaxDisplay; i++) + lines.push_back(""); + + closable = false; + maxDisplay = (numLines > numMaxDisplay ? numMaxDisplay : numLines); + + listbox = new Listbox(pos, size, lines, maxDisplay, fontPath, closable); + } + break; + case TRADE_OFFERS: + closable = true; + listbox = new Listbox(pos, size, lines, numMaxDisplay, fontPath, closable); + break; + } + + int typeArr[2]{(int)GuiElementType::LISTBOX, (int)listboxType}; + guiElements.push_back(make_pair(typeArr, (void*)listbox)); + + listbox->setName(name); + + return listbox; + } + + Checkbox* ConcreteGuiManager::parseCheckbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Checkbox *checkbox = new Checkbox(pos, fontBasePath + "batang.ttf"); + + int typeArr[2]{(int)GuiElementType::CHECKBOX, -1}; + guiElements.push_back(make_pair(typeArr, (void*)checkbox)); + + return checkbox; + } + + Slider* ConcreteGuiManager::parseSlider(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos", sizeTable = "size"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + + Slider *slider = new Slider(pos, size, guiTable["minValue"], guiTable["maxValue"]); + + int typeArr[2]{(int)GuiElementType::SLIDER, -1}; + guiElements.push_back(make_pair(typeArr, (void*)slider)); + + return slider; + } + + Textbox* ConcreteGuiManager::parseTextbox(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + string posTable = "pos", sizeTable = "size"; + Vector3 pos = Vector3(guiTable[posTable]["x"], guiTable[posTable]["y"], guiTable[posTable]["z"]); + Vector2 size = Vector2(guiTable[sizeTable]["x"], guiTable[sizeTable]["y"]); + Textbox *textbox = new Textbox(pos, size, fontBasePath + "batang.ttf"); + + int typeArr[2]{(int)GuiElementType::TEXTBOX, -1}; + guiElements.push_back(make_pair(typeArr, (void*)textbox)); + + return textbox; + } + + //TODO factor out checking for optional lua values + Node* ConcreteGuiManager::parseGuiRectangle(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + + Root *root = Root::getSingleton(); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->setTransparent(true); + + bool texturingEnabled = false; + string imagePath = "", ipk = "imagePath"; + sol::optional pathOpt = guiTable[ipk]; + + if(pathOpt != sol::nullopt){ + imagePath = guiTable[ipk]; + texturingEnabled = true; + } + + string name = "", nk = "name"; + sol::optional nameOpt = guiTable[nk]; + + if(nameOpt != sol::nullopt) name = guiTable[nk]; + + mat->addBoolUniform("texturingEnabled", texturingEnabled); + + if(texturingEnabled){ + string p[]{texBasePath + imagePath}; + Texture *tex = new Texture(p, 1, false); + mat->addTexUniform("diffuseMap", tex, false); + } + else{ + sol::table colorTable = guiTable["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["w"])); + } + + sol::table sizeTable = guiTable["size"]; + Quad *quad = new Quad(Vector3(sizeTable["x"], sizeTable["y"], 1), false); + quad->setMaterial(mat); + + sol::table posTable = guiTable["pos"]; + Node *guiRectangle = new Node(Vector3(posTable["x"], posTable["y"], posTable["z"]), Quaternion::QUAT_W, Vector3::VEC_IJK, name); + guiRectangle->attachMesh(quad); + root->getGuiNode()->attachChild(guiRectangle); + + int typeArr[2]{(int)GuiElementType::GUI_RECTANGLE, -1}; + guiElements.push_back(make_pair(typeArr, (void*)guiRectangle)); + + return guiRectangle; + } + + //TODO distinguish between floats and vector-like tables for scale + Text* ConcreteGuiManager::parseText(int guiId){ + sol::state_view SOL_LUA_STATE = generateView(); + sol::table guiTable = SOL_LUA_STATE["gui"][guiId + 1]; + sol::table posTable = guiTable["pos"]; + + Root *root = Root::getSingleton(); + + Material *mat = new Material(root->getLibPath() + "text"); + mat->addBoolUniform("texturingEnabled", false); + sol::table colorTable = guiTable["color"]; + mat->addVec4Uniform("diffuseColor", Vector4(colorTable["x"], colorTable["y"], colorTable["z"], colorTable["w"])); + + string font = guiTable["font"]; + wstring entry = guiTable["text"]; + Text *text = new Text(fontBasePath + font, entry, guiTable["fontFirstChar"], guiTable["fontLastChar"]); + text->setMaterial(mat); + + Vector3 pos = Vector3(posTable["x"], posTable["y"], posTable["z"]); + + SOL_LUA_STATE.script("tp = type(gui[" + to_string(guiId + 1) + "].scale)"); + string st = SOL_LUA_STATE["tp"]; + Vector3 scale; + + if(st == "table"){ + sol::table scaleTable = guiTable["scale"]; + scale = Vector3(scaleTable["x"], scaleTable["y"], 1); + } + else if(st == "number"){ + float sc = guiTable["scale"]; + scale = Vector3(sc, sc, 1); + } + + Node *node = new Node(pos, Quaternion::QUAT_W, scale, guiTable["name"]); + node->addText(text); + root->getGuiNode()->attachChild(node); + + int typeArr[2]{(int)GuiElementType::TEXT, -1}; + guiElements.push_back(make_pair(typeArr, (void*)text)); + + return text; + } + + void ConcreteGuiManager::parseMusic(){ + sol::state_view SOL_STATE_VIEW = generateView(); + sol::optional musicTblOpt = SOL_STATE_VIEW["music"]; + + if(musicTblOpt == sol::nullopt) return; + + sol::table musicTbl = SOL_STATE_VIEW["music"], tracksTbl = musicTbl["tracks"]; + SoundManager *sm = SoundManager::getSingleton(); + + if(tracksTbl.size() == 0){ + sm->clearPlaylist(); + return; + } + + bool loop = musicTbl["loop"], shuffle = musicTbl["shuffle"]; + int delay = musicTbl["delay"].get_or(0); + int numTracks = tracksTbl.size(); + + vector trackPaths; + + for(int i = 0; i < numTracks; i++){ + string track = tracksTbl[i + 1]; + trackPaths.push_back(GameManager::getSingleton()->getPath() + "Sounds/Music/" + track); + } + + sm->play(trackPaths, 100, delay, loop, shuffle); + } + + void ConcreteGuiManager::readLuaScreenScript( + string script, + vector buttonExceptions, + vector listboxExceptions, + vector checkboxExceptions, + vector sliderExceptions, + vector textboxExceptions, + vector guiRectboxExceptions, + vector textExceptions, + string luaCode + ){ + removeAllGuiElements(buttonExceptions, listboxExceptions, checkboxExceptions, sliderExceptions, textboxExceptions, guiRectboxExceptions, textExceptions); + parseLuaScript(script, luaCode); + } + + void ConcreteGuiManager::readLuaScreenScriptDel( + string script, + vector buttons, + vector listboxs, + vector checkboxs, + vector sliders, + vector textboxs, + vector guiRectboxs, + vector texts + ){ + for(Button *b : buttons) removeButton(b); + for(Listbox *l : listboxs) removeListbox(l); + for(Checkbox *c : checkboxs) removeCheckbox(c); + for(Slider *s : sliders) removeSlider(s); + for(Textbox *t : textboxs) removeTextbox(t); + for(Node *r : guiRectboxs) removeGuiRectangle(r); + for(Text *t : texts) removeText(t); + + parseLuaScript(script); + } + + void ConcreteGuiManager::parseLuaScript(string script, string luaCode){ + guiElements.clear(); + + string basePath = GameManager::getSingleton()->getPath() + "Scripts/Gui/"; + sol::state_view SOL_LUA_VIEW = generateView(); + SOL_LUA_VIEW.script("music = nil"); + SOL_LUA_VIEW.script_file(basePath + script); + + if(luaCode != "") SOL_LUA_VIEW.script(luaCode); + + SOL_LUA_VIEW.script("numGui = #gui"); + int numGuiElements = SOL_LUA_VIEW["numGui"]; + + for(int i = 0; i < numGuiElements; i++){ + int guiTypeId = SOL_LUA_VIEW["gui"][i + 1]["guiType"]; + + switch((GuiElementType)guiTypeId){ + case BUTTON: + addButton(parseButton(i)); + break; + case LISTBOX: + addListbox(parseListbox(i)); + break; + case CHECKBOX: + addCheckbox(parseCheckbox(i)); + break; + case SLIDER: + addSlider(parseSlider(i)); + break; + case TEXTBOX: + addTextbox(parseTextbox(i)); + break; + case GUI_RECTANGLE: + addGuiRectangle(parseGuiRectangle(i)); + break; + case TEXT: + addText(parseText(i)); + break; + } + } + + parseMusic(); + } +} diff --git a/source/gui/concreteGuiManager.h b/source/gui/concreteGuiManager.h new file mode 100644 index 0000000..06ae6a2 --- /dev/null +++ b/source/gui/concreteGuiManager.h @@ -0,0 +1,117 @@ +#ifndef CONCRETE_GUI_MANAGER_H +#define CONCRETE_GUI_MANAGER_H + +#include + +#include +#include +#include + +namespace vb01{ + class Node; + class Text; +} + +namespace battleship{ + enum GuiElementType {BUTTON, LISTBOX, CHECKBOX, SLIDER, TEXTBOX, GUI_RECTANGLE, TEXT, MUSIC}; + enum ButtonType { + SINGLE_PLAYER, + EDITOR, + OPTIONS, + EXIT, + OK, + DEFAULTS, + BACK, + CONTROLS_TAB, + MOUSE_TAB, + VIDEO_TAB, + AUDIO_TAB, + MULTIPLAYER_TAB, + NEW_MAP, + NEW_MAP_OK, + LOAD_MAP, + LOAD_MAP_OK, + EXPORT, + PLAY, + RESUME, + CONSOLE_SCREEN, + MAIN_MENU, + CONSOLE_COMMAND_OK, + BUILD, + TRAIN, + STATISTICS, + RESEARCH, + BUY_REFINEDS, + SELL_REFINEDS, + BUY_RESEARCH, + SELL_RESEARCH, + ACTIVE_STATE_BUTTON, + ACTIVE_STATE_BACK, + PLAYER_TRADE, + TRADING_SCREEN, + TRADE_OFFER, + RESOURCE_AMMOUNT, + UNIT_STATE, + ORDER, + MINIMAP, + }; + enum ListboxType { + CONTROLS, + RESOLUTION, + MAPS, + VEHICLES, + STRUCTURES, + RESOURCE_DEPOSITS, + SKYBOX_TEXTURES, + LAND_TEXTURES, + CPU_DIFFICULTIES, + FACTIONS, + COLORS, + TEAMS, + CONSOLE, + TRADE_OFFERS + }; + + class ConcreteGuiManager : public vb01Gui::AbstractGuiManager{ + public: + static ConcreteGuiManager* getSingleton(); + void readLuaScreenScript( + std::string, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::string = "" + ); + void readLuaScreenScriptDel( + std::string, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{}, + std::vector = std::vector{} + ); + void parseLuaScript(std::string, std::string = ""); + private: + ConcreteGuiManager(); + vb01Gui::Button* parseButton(int); + vb01Gui::Listbox* parseGameObjectListbox(); + vb01Gui::Listbox* parseListbox(int); + vb01Gui::Checkbox* parseCheckbox(int); + vb01Gui::Slider* parseSlider(int); + vb01Gui::Textbox* parseTextbox(int); + vb01::Node* parseGuiRectangle(int); + vb01::Text* parseText(int); + void parseMusic(); + + std::vector> guiElements; + std::string texBasePath, fontBasePath; + }; +} + +#endif diff --git a/source/gui/listboxes/gameObjectListbox.cpp b/source/gui/listboxes/gameObjectListbox.cpp new file mode 100644 index 0000000..f82fede --- /dev/null +++ b/source/gui/listboxes/gameObjectListbox.cpp @@ -0,0 +1,24 @@ +#include + +#include "gameObject.h" +#include "gameObjectListbox.h" +#include "gameObjectFrameController.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + GameObjectListbox::GameObjectListbox(bool ul, Vector3 pos, Vector2 size, vector lines, vector objIds, int maxDisplay, string fontPath) : + Listbox(pos, size, lines, maxDisplay, fontPath), + unitListbox(ul), + gameObjIds(objIds) {} + + void GameObjectListbox::onClose(){ + GameObjectFrameController *ufCtr = GameObjectFrameController::getSingleton(); + GameObject::Type type = (unitListbox ? GameObject::Type::UNIT : GameObject::Type::RESOURCE_DEPOSIT); + ufCtr->addGameObjectFrame(GameObjectFrame(gameObjIds[selectedOption], type, nullptr)); + ufCtr->setPlacingOnSurface(true); + } +} diff --git a/source/gui/listboxes/gameObjectListbox.h b/source/gui/listboxes/gameObjectListbox.h new file mode 100644 index 0000000..1e15ec1 --- /dev/null +++ b/source/gui/listboxes/gameObjectListbox.h @@ -0,0 +1,17 @@ +#ifndef UNIT_LISTBOX_H +#define UNIT_LISTBOX_H + +#include + +namespace battleship{ + class GameObjectListbox : public vb01Gui::Listbox{ + public: + GameObjectListbox(bool, vb01::Vector3, vb01::Vector2, std::vector, std::vector, int, std::string); + void onClose(); + private: + bool unitListbox; + std::vector gameObjIds; + }; +} + +#endif diff --git a/source/gui/listboxes/landTextureListbox.cpp b/source/gui/listboxes/landTextureListbox.cpp new file mode 100644 index 0000000..13891eb --- /dev/null +++ b/source/gui/listboxes/landTextureListbox.cpp @@ -0,0 +1,37 @@ +#include "landTextureListbox.h" +#include "mapEditorAppState.h" +#include "gameManager.h" + +#include +#include +#include +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + LandTextureListbox::LandTextureListbox(Vector3 pos, Vector2 size, vector lines, int maxDisplay, std::string fontPath) : Listbox(pos, size, lines, maxDisplay, fontPath){} + + void LandTextureListbox::onClose(){ + StateManager *sm = GameManager::getSingleton()->getStateManager(); + MapEditorAppState::MapEditor *mapEditor = ((MapEditorAppState*)sm->getAppStateByType((int)AppStateType::MAP_EDITOR))->getMapEditor(); + + Map *map = Map::getSingleton(); + Material *mat = map->getNodeParent()->getChild(0)->getMesh(0)->getMaterial(); + Material::BoolUniform *texturingUniform = (Material::BoolUniform*)mat->getUniform("texturingEnabled"); + Texture *landTex = mapEditor->getLandmassTexture(selectedOption); + string texUni = "textures[0]"; + + if(!texturingUniform->value){ + texturingUniform->value = true; + mat->addTexUniform(texUni, landTex, false); + } + else + mat->setTexUniform(texUni, landTex, false); + } +} diff --git a/source/gui/listboxes/landTextureListbox.h b/source/gui/listboxes/landTextureListbox.h new file mode 100644 index 0000000..867824b --- /dev/null +++ b/source/gui/listboxes/landTextureListbox.h @@ -0,0 +1,16 @@ +#ifndef LAND_TEXTURE_LISTBOX_H +#define LAND_TEXTURE_LISTBOX_H + +#include + +namespace battleship{ + class LandTextureListbox : public vb01Gui::Listbox{ + public: + LandTextureListbox(vb01::Vector3, vb01::Vector2, std::vector, int, std::string); + void onClose(); + private: + }; +} + +#endif + diff --git a/source/gui/listboxes/mapListbox.cpp b/source/gui/listboxes/mapListbox.cpp new file mode 100644 index 0000000..19338a2 --- /dev/null +++ b/source/gui/listboxes/mapListbox.cpp @@ -0,0 +1,52 @@ +#include "mapListbox.h" +#include "map.h" +#include "concreteGuiManager.h" +#include "gameManager.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace battleship{ + using namespace vb01; + using namespace vb01Gui; + using namespace std; + using namespace gameBase; + + MapListbox::MapListbox(Vector3 pos, Vector2 size, std::vector &lines, int maxDisplay, bool adg, string fontPath, bool closeable) : + Listbox(pos, size, lines, maxDisplay, fontPath, closeable), + addPlayerGui(adg) {} + + void MapListbox::onClose(){ + if(addPlayerGui){ + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + + for(Listbox *listbox : cpuPlayerListboxes) + guiManager->removeListbox(listbox); + + cpuPlayerListboxes.clear(); + + string mapName = wstringToString(getContents()[getSelectedOption()]); + int numSpawnPoints = Map::getSingleton()->getNumMapSpawnPoints(mapName); + sol::state_view SOL_LUA_VIEW = generateView(); + + for(int i = 0; i < numSpawnPoints; i++){ + int prevNumListboxes = guiManager->getListboxes().size(); + + SOL_LUA_VIEW.script("lineId = " + to_string(i)); + guiManager->parseLuaScript("playerSelection.lua"); + + std::vector listboxes = guiManager->getListboxes(); + int diffNumListboxes = listboxes.size() - prevNumListboxes; + + for(int j = 0; j < diffNumListboxes; j++) + cpuPlayerListboxes.push_back(listboxes[listboxes.size() - 1 - j]); + } + } + } +} diff --git a/source/gui/listboxes/mapListbox.h b/source/gui/listboxes/mapListbox.h new file mode 100644 index 0000000..3464735 --- /dev/null +++ b/source/gui/listboxes/mapListbox.h @@ -0,0 +1,22 @@ +#ifndef MAP_LISTBOX_H +#define MAP_LISTBOX_H + +#include + +namespace vb01Gui{ + class Listbox; +} + +namespace battleship{ + class MapListbox : public vb01Gui::Listbox{ + public: + MapListbox(vb01::Vector3, vb01::Vector2, std::vector&, int, bool, std::string, bool); + private: + void onClose(); + + bool addPlayerGui; + std::vector cpuPlayerListboxes; + }; +} + +#endif diff --git a/source/gui/listboxes/skyboxTextureListbox.cpp b/source/gui/listboxes/skyboxTextureListbox.cpp new file mode 100644 index 0000000..4d729dd --- /dev/null +++ b/source/gui/listboxes/skyboxTextureListbox.cpp @@ -0,0 +1,31 @@ +#include "skyboxTextureListbox.h" +#include "gameManager.h" +#include "mapEditorAppState.h" + +#include + +#include +#include +#include +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace vb01Gui; + using namespace gameBase; + + SkyboxTextureListbox::SkyboxTextureListbox(Vector3 pos, Vector2 size, vector lines, int maxDisplay, string fontPath) : Listbox(pos, size, lines, maxDisplay, fontPath){} + + void SkyboxTextureListbox::onClose(){ + Root *root = Root::getSingleton(); + StateManager *sm = GameManager::getSingleton()->getStateManager(); + MapEditorAppState::MapEditor *mapEditor = ((MapEditorAppState*)sm->getAppStateByType((int)AppStateType::MAP_EDITOR))->getMapEditor(); + Texture *skyTexture = mapEditor->getSkyTexture(selectedOption); + + if(!root->getSkybox()) + root->createSkybox(skyTexture); + else + root->getSkybox()->getMaterial()->setTexUniform("tex", skyTexture, true); + } +} diff --git a/source/gui/listboxes/skyboxTextureListbox.h b/source/gui/listboxes/skyboxTextureListbox.h new file mode 100644 index 0000000..8465753 --- /dev/null +++ b/source/gui/listboxes/skyboxTextureListbox.h @@ -0,0 +1,16 @@ +#ifndef SKYBOX_TEXTURE_LISTBOX_H +#define SKYBOX_TEXTURE_LISTBOX_H + +#include + +namespace battleship{ + class SkyboxTextureListbox : public vb01Gui::Listbox{ + public: + SkyboxTextureListbox(vb01::Vector3, vb01::Vector2, std::vector, int, std::string); + void onClose(); + private: + }; +} + +#endif + diff --git a/source/gui/tooltip.cpp b/source/gui/tooltip.cpp new file mode 100644 index 0000000..a4d2681 --- /dev/null +++ b/source/gui/tooltip.cpp @@ -0,0 +1,16 @@ +#include "tooltip.h" + +using namespace std; +using namespace vb01; + +namespace battleship{ + Tooltip::Tooltip(Vector2 pos, string entry){ + this->pos = pos; + } + + Tooltip::~Tooltip(){ + } + + void Tooltip::update(){ + } +} diff --git a/source/gui/tooltip.h b/source/gui/tooltip.h new file mode 100644 index 0000000..68b8020 --- /dev/null +++ b/source/gui/tooltip.h @@ -0,0 +1,17 @@ +#ifndef TOOLTIP_H +#define TOOLTIP_H + +#include + +namespace battleship{ + class Tooltip{ + public: + Tooltip(vb01::Vector2, std::string); + ~Tooltip(); + void update(); + private: + vb01::Vector2 pos; + }; +} + +#endif diff --git a/source/tests/mapTest.cpp b/source/tests/mapTest.cpp new file mode 100644 index 0000000..71387d8 --- /dev/null +++ b/source/tests/mapTest.cpp @@ -0,0 +1,30 @@ +#include "mapTest.h" +#include "map.h" + +namespace battleship{ + using namespace vb01; + + void MapTest::setUp(){ + } + + void MapTest::tearDown(){ + } + + void MapTest::testGenerateWeights(){ + Map *map = Map::getSingleton(); + Vector3 mapSize = map->getSize(), cellSize = Vector3(6, 1, 6); + int cellsByDim[3] = { + int(mapSize.x / cellSize.x), + int(mapSize.y / cellSize.y), + int(mapSize.z / cellSize.z) + }; + int numCells = cellsByDim[0] * cellsByDim[1] * cellsByDim[2]; + + u32 **weights = new u32*[numCells]; + + for(int i = 0; i < numCells; i++) + weights[i] = new u32[numCells]; + + map->generateWeights(weights, cellSize, UnitType::UNDERWATER, Vector3::VEC_ZERO, 60000); + } +} diff --git a/source/tests/mapTest.h b/source/tests/mapTest.h new file mode 100644 index 0000000..302b8d1 --- /dev/null +++ b/source/tests/mapTest.h @@ -0,0 +1,21 @@ +#ifndef MAP_TEST_H +#define MAP_TEST_H + +#include +#include + +namespace battleship{ + class MapTest : public CppUnit::TestFixture{ + CPPUNIT_TEST_SUITE(MapTest); + CPPUNIT_TEST(testGenerateWeights); + CPPUNIT_TEST_SUITE_END(); + + public: + MapTest(){} + void testGenerateWeights(); + void setUp(); + void tearDown(); + }; +} + +#endif diff --git a/source/tests/pathfinderTest.cpp b/source/tests/pathfinderTest.cpp new file mode 100644 index 0000000..362fc75 --- /dev/null +++ b/source/tests/pathfinderTest.cpp @@ -0,0 +1,118 @@ +#include "pathfinderTest.h" +#include "pathfinder.h" +#include "unit.h" + +#include +#include + +#include + +namespace battleship{ + using namespace std; + using namespace vb01; + + vector PathfinderTest::generateCellGraph(int numCellsOnSide){ + vector cells; + float cellSize = 2; + + for(int i = 0; i < numCellsOnSide; i++) + for(int j = 0; j < numCellsOnSide; j++){ + vector edges = Map::generateAdjacentNodeEdges(numCellsOnSide, i, numCellsOnSide, j, 1000); + cells.push_back(Map::Cell(Vector3(j * cellSize, 0, i * cellSize), Map::Cell::Type::LAND, edges)); + } + + return cells; + } + + int PathfinderTest::calcPathLength(vector &path){ + int sumPathWeights = 0; + + for(int i = 0; i < path.size() - 1; i++) + for(int j = 0; j < cells[path[i]].edges.size(); j++) + if(cells[path[i]].edges[j].destCellId == path[i + 1]) + sumPathWeights += cells[path[i]].edges[j].weight; + + return sumPathWeights; + } + + vector PathfinderTest::generateHeuristics(vector &cells, int dest, int type){ + vector heur; + int numSideCells = sqrt(cells.size()); + + if(type == 0) + for(Map::Cell &cell : cells) + heur.push_back(145 * cells[dest].pos.getDistanceFrom(cell.pos)); + else if(type == 1) + for(int i = 0; i < cells.size(); i++){ + int x = i % numSideCells, y = i / numSideCells; + heur.push_back(2 * (numSideCells - 1) - (x + y)); + } + + return heur; + } + + void PathfinderTest::testFindPath(){ + cells = vector{ + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 0, 0), Map::Edge(2, 0, 1), Map::Edge(4, 0, 2)}), + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 1, 1), Map::Edge(1, 1, 2), Map::Edge(9, 1, 3), Map::Edge(13, 1, 4)}), + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 2, 2), Map::Edge(2, 2, 1), Map::Edge(4, 2, 4), Map::Edge(5, 2, 5)}), + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 3, 3), Map::Edge(1, 3, 6)}), + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 4, 4), Map::Edge(1, 4, 1), Map::Edge(1, 4, 3), Map::Edge(2, 4, 5), Map::Edge(3, 4, 6)}), + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 5, 5), Map::Edge(9, 5, 4), Map::Edge(2, 5, 6)}), + Map::Cell(Vector3::VEC_ZERO, Map::Cell::Type::LAND, vector{Map::Edge(0, 6, 6)}) + }; + + int src = 0, dest = cells.size() - 1; + vector heur; + vector path = pathfinder->findPath(cells, heur, src, dest); + CPPUNIT_ASSERT(path == vector({0, 1, 2, 4, 3, 6})); + + int sumPathWeights = calcPathLength(path); + CPPUNIT_ASSERT(sumPathWeights == 9); + } + + void PathfinderTest::testFindBigPath(){ + cells = generateCellGraph(250); + int numIterations = 1, src = 0, dest = cells.size() - 1; + vector heur; + heur = generateHeuristics(cells, dest, 0); + s64 sumTime = 0; + + for(int i = 0; i < numIterations; i++){ + s64 t0 = getTime(); + pathfinder->findPath(cells, heur, src, dest); + s64 t1 = getTime(); + sumTime += t1 - t0; + } + + int threshold = 100; + double avg = (double)sumTime / numIterations, eps = .1; + cout << "Total time: " << sumTime << endl; + CPPUNIT_ASSERT(sumTime <= threshold); + } + + void PathfinderTest::testFindShorePath(){ + int numSideCells = 25; + cells = generateCellGraph(numSideCells); + + for(int i = 0; i < numSideCells; i++) + cells[numSideCells * i + int(.5 * numSideCells)].type = Map::Cell::WATER; + + vector heur = vector{}; + vector p1 = pathfinder->findPath(cells, heur, 0, 4, (int)UnitType::LAND); + + int numEdges = cells[p1[p1.size() - 1]].edges.size(); + //for(int i = 0; i < numEdges; i++){} + + vector p2 = pathfinder->findPath(cells, heur, 2, 4, (int)UnitType::SEA_LEVEL); + CPPUNIT_ASSERT(cells[p2[p2.size() - 1]].type == Map::Cell::WATER); + } + + void PathfinderTest::setUp(){ + pathfinder = Pathfinder::getSingleton(); + const u16 INF = u16(0 - 1); + pathfinder->setImpassibleNodeVal(INF); + } + + void PathfinderTest::tearDown(){} +} diff --git a/source/tests/pathfinderTest.h b/source/tests/pathfinderTest.h new file mode 100644 index 0000000..b1c4d27 --- /dev/null +++ b/source/tests/pathfinderTest.h @@ -0,0 +1,36 @@ +#ifndef PATHFINDER_TEST_H +#define PATHFINDER_TEST_H + +#include +#include + +#include "map.h" + +namespace battleship{ + class Pathfinder; + + class PathfinderTest : public CppUnit::TestFixture{ + CPPUNIT_TEST_SUITE(PathfinderTest); + CPPUNIT_TEST(testFindPath); + CPPUNIT_TEST(testFindBigPath); + CPPUNIT_TEST(testFindShorePath); + CPPUNIT_TEST_SUITE_END(); + + public: + PathfinderTest(){} + void testFindPath(); + void testFindBigPath(); + void testFindShorePath(); + void setUp(); + void tearDown(); + private: + Pathfinder *pathfinder = nullptr; + std::vector cells; + + std::vector generateCellGraph(int); + std::vector generateHeuristics(std::vector&, int, int = 0); + int calcPathLength(std::vector&); + }; +} + +#endif diff --git a/source/tests/testMain.cpp b/source/tests/testMain.cpp new file mode 100644 index 0000000..1e7755e --- /dev/null +++ b/source/tests/testMain.cpp @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +#include "pathfinderTest.h" + +using namespace CppUnit; +using namespace battleship; + +int main(){ + TextUi::TestRunner runner; + runner.addTest(PathfinderTest::suite()); + runner.run(); + return 0; +} diff --git a/source/utils/util.cpp b/source/utils/util.cpp new file mode 100644 index 0000000..65dcb21 --- /dev/null +++ b/source/utils/util.cpp @@ -0,0 +1,145 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include "util.h" +#include "defConfigs.h" +#include "gameManager.h" +#include "concreteGuiManager.h" +#include "guiAppState.h" +#include "inGameAppState.h" +#include "loadingAppState.h" +#include "mapEditorButton.h" + +using namespace std; +using namespace std::this_thread; +using namespace std::chrono; +using namespace glm; +using namespace vb01; +using namespace vb01Gui; + +namespace battleship{ + using namespace configData; + + void handleLoadingGui(LoadingAppState *loadState){ + ConcreteGuiManager *guiManager = ConcreteGuiManager::getSingleton(); + guiManager->readLuaScreenScript("loadingScreen.lua"); + + sol::state_view SOL_LUA_VIEW = generateView(); + string vfxPrefix = SOL_LUA_VIEW["vfxPrefix"], gameObjPrefix = SOL_LUA_VIEW["gameObjPrefix"]; + + AssetManager *assetManager = AssetManager::getSingleton(); + string path = GameManager::getSingleton()->getPath(); + vector assets = vector{path + DEFAULT_TEXTURE}; + + assetManager->readDir(path + vfxPrefix, assets, true); + assetManager->readDir(path + gameObjPrefix, assets, true); + + loadState->setLoadableAssets(assets); + + StateManager *stateManager = GameManager::getSingleton()->getStateManager(); + stateManager->attachAppState(loadState); + } + + Vector3 getVecToPlane(Vector3 pos, Vector3 dirVec, Vector3 upVec){ + float baseAngle = upVec.getAngleBetween(dirVec.norm()); + + if(fabs(baseAngle - PI / 2) > .001){ + bool acuteAngle = (baseAngle < PI / 2); + + if(acuteAngle) baseAngle = PI / 2 - baseAngle; + else baseAngle -= PI / 2; + + float pointToPlane = dirVec.getLength() * sin(baseAngle); + return (dirVec + upVec * pointToPlane * (acuteAngle ? -1 : 1)).norm(); + } + else return dirVec.norm(); + } + + vector readDir(string path, bool findFolders){ + tinydir_dir dir; + tinydir_open_sorted(&dir, path.c_str()); + vector files; + + for (int i = 0; i < dir.n_files; i++) { + tinydir_file file; + tinydir_readfile_n(&dir, &file, i); + + if (file.is_dir == findFolders && file.name[0] != '.') + files.push_back(file.name); + } + + tinydir_close(&dir); + + return files; + } + + Vector3 spaceToScreen3d(Vector3 pos){ + Root *root = Root::getSingleton(); + Camera *cam = root->getCamera(); + Vector3 dir = cam->getDirection(), up = cam->getUp(); + Vector3 camPos = cam->getPosition(); + mat4 view = lookAt(vec3(camPos.x, camPos.y, camPos.z), vec3(camPos.x + dir.x, camPos.y + dir.y, camPos.z + dir.z), vec3(up.x, up.y, up.z)); + + float fov = cam->getFov(), width = root->getWidth(), height = root->getHeight(), nearPlane = cam->getNearPlane(), farPlane = cam->getFarPlane(); + mat4 proj = perspective(radians(fov), width / height, nearPlane, farPlane); + + vec4 ndcPos = proj * view * vec4(pos.x, pos.y, pos.z, 1); + ndcPos.x /= ndcPos.w; + ndcPos.y /= ndcPos.w; + ndcPos.z /= ndcPos.w; + return Vector3(0.5 * width * (1 + ndcPos.x), 0.5 * height * (1 - ndcPos.y), ndcPos.z); + } + + Vector2 spaceToScreen(Vector3 pos){ + Vector3 pos3d = spaceToScreen3d(pos); + return Vector2(pos3d.x, pos3d.y); + } + + Vector3 screenToSpace(Vector2 pos){ + GameManager *gm = GameManager::getSingleton(); + float midWidth = .5 * gm->getWidth(), midHeight = .5 * gm->getHeight(), horOffset, vertOffset; + bool left, up; + + if(pos.x < midWidth){ + left = true; + horOffset = pos.x / midWidth - 1; + } + else{ + left = false; + horOffset = (pos.x - midWidth) / midWidth; + } + + if(pos.y < midHeight){ + up = true; + vertOffset = 1 - pos.y / midHeight; + } + else{ + up = false; + vertOffset = -(pos.y - midHeight) / midHeight; + } + + Camera *cam = Root::getSingleton()->getCamera(); + float camNorm = cam->getNearPlane(); + float tg = tan(radians(cam->getFov()) / 2), ar = midWidth / midHeight; + float camHeight = camNorm * tg, camWidth = camHeight * ar; + Vector3 posOffset = (cam->getLeft() * camWidth * horOffset + cam->getUp() * camHeight * vertOffset + cam->getDirection() * camNorm); + + return cam->getPosition() + posOffset; + } +} diff --git a/source/utils/util.h b/source/utils/util.h new file mode 100644 index 0000000..75b6700 --- /dev/null +++ b/source/utils/util.h @@ -0,0 +1,36 @@ +#pragma once +#ifndef UTIL_BATTLESHIP_H +#define UTIL_BATTLESHIP_H + +#include +#include +#include +#include +#include + +#include + +namespace battleship{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef unsigned long long u64; + typedef char s8; + typedef short s16; + typedef int s32; + typedef long long s64; + + enum AppStateType{GUI_STATE, IN_GAME_STATE, ACTIVE_STATE, MAP_EDITOR, LOADING_STATE}; + + class GuiAppState; + class LoadingAppState; + + void handleLoadingGui(LoadingAppState*); + vb01::Vector3 getVecToPlane(vb01::Vector3, vb01::Vector3, vb01::Vector3); + std::vector readDir(std::string, bool); + vb01::Vector2 spaceToScreen(vb01::Vector3); + vb01::Vector3 spaceToScreen3d(vb01::Vector3); + vb01::Vector3 screenToSpace(vb01::Vector2); +} + +#endif