#include #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); // Initiate the properties from the Lua table 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(); removeFromUnitGroup(); } /// @brief Sets all the unit properties from the Lua table containing the units void Unit::initProperties(){ GameObject::initProperties(); sol::state_view SOL_LUA_VIEW = generateView(); // Returns the table name as "units" 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"])); } } } /// @brief Initialize the weapons for this unit void Unit::initWeapons(){ // The name, number, and type of weapons are read (in that order) from a table containing the weapons info in a Lua script sol::state_view SOL_STATE_VIEW = generateView(); string objType = GameObject::getGameObjTableName(); sol::table unitTable = SOL_STATE_VIEW[objType][id + 1]; // The table containing the weapons info 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"]; // Create a new weapon from the unitTable and push it to the weapons vector for this unit weapons.push_back(new Weapon(this, unitTable, i)); } } } void Unit::destroyWeapons(){ // Deallocate all the memory declared for the weapons for this unit for(Weapon *weapon : weapons) delete weapon; // Clear the weapons vector 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; } /// @brief Check if unit is in a unit group and if so remove it void Unit::removeFromUnitGroup(){ ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); std::unordered_map>& unitGroups = activeState->getUnitGroups(); // Iterate through all the individual unit groups and see if this unit is in any of them for(auto& [key, value]: unitGroups) { if(std::find(value.begin(), value.end(), this) != value.end()) { //std::erase(value, this); value.erase(std::remove(value.begin(), value.end(), this), value.end()); } } } 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); } // Re-instantiates a unit 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() { // Since a unit is a game object and a destructable, run their update functions GameObject::update(); destructable->update(); // If the unit is EM jammed then see if enough time has passed to make the unit able to fight again if(condition == Condition::EM_JAMMED && getTime() - lastJamTime > restartTime) condition = Condition::ABLE; // Freeze the unit if its freeze status is greater than 100 if(destructable->getFreezeStatus() >= 100) condition = Condition::FROZEN; // Attack targets if the unit is not in the hold fire state 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() { // Return if there are no orders or if the unit is not able to execute orders 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(); // At the end of the targets vector insert all the destructables targets.insert(targets.end(), targs.begin(), targs.end()); } vector attackWeapons = getWeaponsByOrder(Order::TYPE::ATTACK); for(Order::Target &target : orders[0].targets) // If there is a target unit then find it if(target.unit){ if(find(targets.begin(), targets.end(), target.unit) != targets.end()){ bool canAttack = false; // Go through the weapons and determine which ones can attack 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; } } /// @brief Returns the weapons that are of the passed order type /// @param type /// @return vector Unit::getWeaponsByOrder(Order::TYPE type){ vector weaps; //getOrderType gets the order type from the Lua table containing the weapon data for(Weapon *w : weapons) if(w->getOrderType() == type) weaps.push_back(w); return weaps; } /// @brief Returns the weapons of the passed type. Currently, the type can either be damage or freezer /// @param type /// @return vector Unit::getWeaponsByType(int type){ vector weaps; //getType gets the type from the Lua table containing the weapon data 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){ ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); Map *map = Map::getSingleton(); //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(); } } /// @brief /// @return 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(); for(auto i: weapons) { cout << "Current ammo of selected unit's weapons " << i->getAmmo() << endl; } } }