diff --git a/Assets/Scripts/GameObjects/Units/unitData.lua b/Assets/Scripts/GameObjects/Units/unitData.lua index e420bf1..e75829e 100644 --- a/Assets/Scripts/GameObjects/Units/unitData.lua +++ b/Assets/Scripts/GameObjects/Units/unitData.lua @@ -10,7 +10,8 @@ OrderType = { SUPPLY = 7, LOAD = 8, UNLOAD = 9, - HACK = 10 + HACK = 10, + FREEZE = 11 } UnitId = { WAR_MECH = 0, @@ -29,17 +30,18 @@ UnitId = { MISSILE_SUBMARINE = 13, STEALTH_SUBMARINE = 14, ICEBREAKER = 15, - LAND_FACTORY = 16, - NAVAL_FACTORY = 17, - TRADE_CENTER = 18, - LAB = 19, - POINT_DEFENSE = 20, - EXTRACTOR = 21, - REFINERY = 22, - FORT = 23, - ICE_SHEET = 24 + FREEZER = 16, + LAND_FACTORY = 17, + NAVAL_FACTORY = 18, + TRADE_CENTER = 19, + LAB = 20, + POINT_DEFENSE = 21, + EXTRACTOR = 22, + REFINERY = 23, + FORT = 24, + ICE_SHEET = 25 } -WeaponClass = {HITSCAN = 0, SHELL = 1, TORPEDO = 2, CRUISE_MISSILE = 3, HACK = 4} +WeaponClass = {FREEZE = 1} UnitClass = { WAR_MECH = 0, TANK = 1, @@ -51,15 +53,16 @@ UnitClass = { CARRIER = 7, SUBMARINE = 8, ICEBREAKER = 9, - LAND_FACTORY = 10, - NAVAL_FACTORY = 11, - TRADE_CENTER = 12, - LAB = 13, - POINT_DEFENSE = 14, - EXTRACTOR = 15, - REFINERY = 16, - FORT = 17, - ICE_SHEET = 18 + FREEZER = 10, + LAND_FACTORY = 11, + NAVAL_FACTORY = 12, + TRADE_CENTER = 13, + LAB = 14, + POINT_DEFENSE = 15, + EXTRACTOR = 16, + REFINERY = 17, + FORT = 18, + ICE_SHEET = 19 } UnitType = {UNDERWATER = 0, SEA_LEVEL = 1, HOVER = 2, LAND = 3, AIR = 4} @@ -93,7 +96,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 100, maxRange = 10, @@ -165,7 +167,6 @@ units = { { weapons = { { - type = WeaponClass.SHELL, orderType = OrderType.ATTACK, rateOfFire = 1000, damage = 200, @@ -221,7 +222,6 @@ units = { { weapons = { { - type = WeaponClass.SHELL, orderType = OrderType.ATTACK, rateOfFire = 2000, damage = 5000, @@ -275,7 +275,7 @@ units = { }, { weapons = { - {type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 2000, fireSfx = PATH .. 'Sounds/Units/WarMechs/fire.ogg', damage = 20, maxRange = 8}, + {orderType = OrderType.ATTACK, rateOfFire = 2000, fireSfx = PATH .. 'Sounds/Units/WarMechs/fire.ogg', damage = 20, maxRange = 8}, }, buildableUnits = { {id = UnitId.LAND_FACTORY, buildable = true, trigger = 76}, @@ -314,7 +314,7 @@ units = { garrisonCategory = 3 }, { - weapons = {{type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 2000, fireSfx = PATH .. 'Sounds/Units/WarMechs/fire.ogg', damage = 50, maxRange = 20}}, + weapons = {{orderType = OrderType.ATTACK, rateOfFire = 2000, fireSfx = PATH .. 'Sounds/Units/WarMechs/fire.ogg', damage = 50, maxRange = 20}}, unitClass = UnitClass.TRANSPORT, unitType = UnitType.HOVER, isVehicle = true, @@ -339,7 +339,7 @@ units = { garrisonCategory = 3 }, { - weapons = {{type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 2000, fireSfx = PATH .. 'Sounds/Units/Tanks/attack.ogg', damage = 100, maxRange = 25}}, + weapons = {{orderType = OrderType.ATTACK, rateOfFire = 2000, fireSfx = PATH .. 'Sounds/Units/Tanks/attack.ogg', damage = 100, maxRange = 25}}, unitClass = UnitClass.TRANSPORT, unitType = UnitType.HOVER, armor = {ArmorType.MECHANIC}, @@ -400,11 +400,10 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 200, damage = 300, - maxRange = 15, + maxRange = 50, fireFx = { { vfx = true, @@ -452,7 +451,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 200, fireFx = { @@ -504,7 +502,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 200, damage = 300, @@ -555,7 +552,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 200, fireSfx = PATH .. 'Sounds/Units/Cruisers/fire.ogg', @@ -607,7 +603,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 100, damage = 80, @@ -657,7 +652,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 450, damage = 500, @@ -684,7 +678,6 @@ units = { waterHitFx = {explosionVfx, explosionSfx} }, { - type = WeaponClass.CRUISE_MISSILE, orderType = OrderType.LAUNCH, projectile = {id = ProjectileId.CRUISE_MISSILE, pos = {x = 0, y = 15, z = 0}, rot = {w = .707, x = -.707, y = 0, z = 0}}, rateOfFire = 500, @@ -723,7 +716,6 @@ units = { { weapons = { { - type = WeaponClass.TORPEDO, orderType = OrderType.ATTACK, rateOfFire = 1000, fireFx = { @@ -738,7 +730,6 @@ units = { projectile = {id = ProjectileId.TORPEDO, pos = {x = 0, y = -.27, z = 4.6}, rot = {w = 1, x = 0, y = 0, z = 0}} }, { - type = WeaponClass.CRUISE_MISSILE, orderType = OrderType.LAUNCH, rateOfFire = 500, maxRange = 30, @@ -777,7 +768,6 @@ units = { { weapons = { { - type = WeaponClass.TORPEDO, orderType = OrderType.ATTACK, projectile = {id = ProjectileId.TORPEDO, pos = {x = 0, y = -.27, z = 4.6}, rot = {w = 1, x = 0, y = 0, z = 0}}, damage = 200, @@ -837,6 +827,49 @@ units = { maxTurnAngle = .1, garrisonCategory = 3 }, + { + weapons = { + { + type = WeaponClass.FREEZE, + orderType = OrderType.ATTACK, + rateOfFire = 100, + maxRange = 10, + damage = 0, + fireFx = { + { + vfx = false, + duration = 500, + path = PATH .. 'Sounds/SFX/Freezing/freeze.ogg' + }, + }, + unitHitFx = {}, + landHitFx = {}, + waterHitFx = {}, + } + }, + --buildableUnits = {{id = UnitId.ICE_SHEET, buildable = true, trigger = 83}}, + unitClass = UnitClass.FREEZER, + unitType = UnitType.SEA_LEVEL, + isVehicle = true, + health = 500, + buildTime = 1000, + cost = 500, + size = {x = 8.5, y = 6.3, z = 17.6}, + hitboxOffset = {x = 0, y = 1.6, z = 0}, + lineOfSight = 25, + name = 'Freezer', + --colorNodes = {'stealthSubmarine.001'}, + basePath = PATH .. vehiclePrefix .. 'Freezers/', + meshPath = 'freezer.xml', + albedoPath = 'freezer.jpg', + selectionSfx = PATH .. 'Sounds/Units/Submarines/selection.ogg', + deathSfx = PATH .. 'Sounds/SFX/Explosions/explosion01.ogg', + speed = .1, + destinationOffset = .1, + anglePrecision = .1, + maxTurnAngle = .1, + garrisonCategory = 3 + }, { buildableUnits = { {id = UnitId.WAR_MECH, buildable = true, trigger = 87}, @@ -927,7 +960,6 @@ units = { { weapons = { { - type = WeaponClass.HITSCAN, orderType = OrderType.ATTACK, rateOfFire = 100, damage = 50, diff --git a/CMakeLists.txt b/CMakeLists.txt index b0af216..481c7a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ set(CORE gameManager.cpp game.cpp fxManager.cpp defConfigs.cpp ${CONSOLE} ${CONT set(PROJECTILES projectile.cpp missile.cpp cruiseMissile.cpp shell.cpp) set(VEHICLES vehicle.cpp submarine.cpp engineer.cpp resourceRover.cpp) set(STRUCTURES structure.cpp factory.cpp pointDefense.cpp extractor.cpp researchStruct.cpp iceSheet.cpp) -set(UNITS gameObjectFactory.cpp unit.cpp ${STRUCTURES} ${VEHICLES}) +set(UNITS gameObjectFactory.cpp unit.cpp weapon.cpp ${STRUCTURES} ${VEHICLES}) set(CONTENT player.cpp trader.cpp pathfinder.cpp map.cpp gameObject.cpp gameObjectFrame.h resourceDeposit.cpp ${UNITS} ${PROJECTILES}) set(GAME_SRC ${STATES} ${GUI} ${CORE} ${CONTENT} ${UTIL}) diff --git a/unit.cpp b/unit.cpp index a71d761..18610de 100755 --- a/unit.cpp +++ b/unit.cpp @@ -13,6 +13,7 @@ #include #include "unit.h" +#include "weapon.h" #include "util.h" #include "game.h" #include "map.h" @@ -31,336 +32,6 @@ using namespace gameBase; using namespace std; namespace battleship{ - string Unit::Weapon::LASER_FLAG = "laser"; - - Unit::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"]; - orderType = (Order::TYPE)weaponTable["orderType"]; - - initProjectileData(weaponTable); - - sol::optional horNodeTblOpt = unitTable["weapons"][wid + 1]["horizontalNode"]; - sol::optional vertNodeTblOpt = unitTable["weapons"][wid + 1]["verticalNode"]; - - if(horNodeTblOpt != sol::nullopt) horNode = initNode(weaponTable, true); - if(vertNodeTblOpt != sol::nullopt) vertNode = initNode(weaponTable, false); - - fireFx = initFx(weaponTable, "fireFx", true); - if(fireFx) FxManager::getSingleton()->addFx(fireFx); - } - - - Node* Unit::Weapon::initNode(sol::table weaponTable, bool horizontal){ - sol::table nodeTbl = weaponTable[horizontal ? "horizontalNode" : "verticalNode"]; - maxFireAngle = nodeTbl["maxFireAngle"]; - rotSpeed = nodeTbl["rotationSpeed"]; - - string name = nodeTbl["name"]; - return unit->getModel()->findDescendant(name, true); - } - - void Unit::Weapon::initProjectileData(sol::table weaponTable){ - string projTableKey = "projectile"; - sol::optional proj = weaponTable[projTableKey]; - - if(proj != sol::nullopt){ - 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"]); - } - } - - FxManager::Fx* Unit::Weapon::initFx(sol::table weaponTable, string fxKey, bool attached){ - sol::optional fxOpt = weaponTable[fxKey]; - - if(fxOpt == sol::nullopt) return nullptr; - - sol::table fxTbl = weaponTable[fxKey]; - 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 = Vector3::VEC_ZERO; - 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, 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_FLAG); - 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 ? unit->getModel() : Root::getSingleton()->getRootNode()); - sol::optional parNameOpt = compTbl["parent"]; - - if(parNameOpt != sol::nullopt){ - string parName = compTbl["parent"]; - parNode = unit->getModel()->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); - } - - Unit::Weapon::~Weapon(){ - FxManager *fm = FxManager::getSingleton(); - - if(fireFx) fm->removeFx(fireFx); - } - - Vector3 Unit::Weapon::calcOrientVec(int id){ - Vector3 orientVec; - - switch(id){ - case 0: - orientVec = unit->getDirVec(); - break; - case 1: - orientVec = unit->getLeftVec(); - break; - case 2: - orientVec = unit->getUpVec(); - break; - } - - if(horNode) orientVec = horNode->getOrientation() * orientVec; - if(vertNode) orientVec = vertNode->getOrientation() * orientVec; - - return orientVec; - } - - //TODO replace unit pos with absolute weapon pos for withinAngle - void Unit::Weapon::update(){ - int numOrders = unit->getNumOrders(); - int ordTp = (numOrders > 0 ? (int)unit->getOrder(0).type : -1); - Unit *targUnit = (ordTp != -1 ? unit->getOrder(0).targets[0].unit : nullptr); - - if(ordTp == (int)orderType){ - Vector3 targPos = (targUnit ? targUnit->getPos() : unit->getOrder(0).targets[0].pos); - float targDist = unit->getPos().getDistanceFrom(targPos); - - bool withinRange = (minRange <= targDist && targDist <= maxRange); - - Vector3 dirVec = calcOrientVec(0); - bool withinAngle = (horNode ? Vector3(dirVec.x, 0, dirVec.z).norm().getAngleBetween((targPos - unit->getPos()).norm()) <= maxFireAngle : true); - - if((Order::TYPE)ordTp == Order::TYPE::ATTACK && withinRange && withinAngle) - fire(unit->getOrder(0)); - } - else - trackTarget(unit->getPos() + unit->getDirVec()); - } - - void Unit::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){ - if(fire && ((Node*)comp.comp)->getName() == LASER_FLAG){ - 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 Unit::Weapon::fire(Order order){ - if(!canFire()) return; - - Unit *targetUnit = order.targets[0].unit; - Vector3 targPos = (targetUnit ? targetUnit->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]; - - if(targetUnit){ - targetUnit->takeDamage(damage); - unit->updateGameStats(targetUnit); - useFx(initFx(weaponTbl, "unitHitFx", false), targPos, false); - } - else{ - Map *map = Map::getSingleton(); - Map::Cell::Type cellType = map->getCells()[map->getCellId(targPos)].type; - string fxKey = (cellType == Map::Cell::Type::LAND ? "landHitFx" : "waterHitFx"); - useFx(initFx(weaponTbl, fxKey, 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 Unit::Weapon::alignNode(Vector3 targPos, Node *node, bool horizontal){ - Vector3 targDir = targPos - unit->getPos(); - Vector3 targDirHor = Vector3(targDir.x, 0, targDir.z).norm(), targDirVert; - - Vector3 dirVec = calcOrientVec(0); - Vector3 dirVecHor = Vector3(dirVec.x, 0, dirVec.z).norm(), dirVecVert; - - Vector3 leftVec = calcOrientVec(1); - bool negate = (Vector3(leftVec.x, 0, leftVec.z).norm().getAngleBetween(targDirHor) < PI / 2); - - float angle = dirVecHor.getAngleBetween(targDirHor); - float rotAngle = (rotSpeed < angle ? rotSpeed : angle); - - Quaternion rot = Quaternion((negate ? 1 : -1) * rotAngle, unit->getUpVec()) * node->getOrientation(); - node->setOrientation(rot); - } - - void Unit::Weapon::trackTarget(Vector3 targPos){ - if(horNode) alignNode(targPos, horNode, true); - //if(vertNode) alignNode(targPos, vertNode, false); - } - Unit::Unit(Player *player, int id, Vector3 pos, Quaternion rot, State st) : GameObject(GameObject::Type::UNIT, id, player, pos, rot), state(st){ selectable = true; @@ -466,15 +137,22 @@ namespace battleship{ sol::table unitTable = SOL_STATE_VIEW[objType][id + 1]; string tblName = "weapons"; - sol::optional wt = unitTable[tblName]; + sol::optional weaponsTblOpt = unitTable[tblName]; - if(wt != sol::nullopt){ + 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++) - weapons.push_back(new Weapon(this, unitTable, i)); + 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"]; + + Weapon *weapon = ((Weapon::Type)wt == Weapon::Type::FREEZER ? new Freezer(this, unitTable, i) : new Weapon(this, unitTable, i)); + weapons.push_back(weapon); + } } } @@ -620,6 +298,8 @@ namespace battleship{ void Unit::update() { GameObject::update(); + if(freezeStatus >= 100) enabled = false; + ActiveGameState *activeState = (ActiveGameState*)GameManager::getSingleton()->getStateManager()->getAppStateByType(AppStateType::ACTIVE_STATE); Player *mainPlayer = (activeState ? activeState->getPlayer() : nullptr); @@ -669,6 +349,8 @@ namespace battleship{ //TODO remove order argument from action methods void Unit::executeOrders() { + if(!enabled) return; + if (orders.size() > 0) { Order order = orders[0]; @@ -783,7 +465,7 @@ namespace battleship{ } } - vector Unit::getWeaponsByOrder(Order::TYPE type){ + vector Unit::getWeaponsByOrder(Order::TYPE type){ vector weaps; for(Weapon *w : weapons) @@ -826,6 +508,8 @@ namespace battleship{ //TODO select only the closest cells based on unit size void Unit::placeAt(Vector3 p){ + //check twice in case the unit is warped over a long distance + Map::getSingleton()->blockCells(this); GameObject::placeAt(p); Map::getSingleton()->blockCells(this); } diff --git a/unit.h b/unit.h index ab3c099..3bf8fda 100755 --- a/unit.h +++ b/unit.h @@ -27,6 +27,7 @@ namespace vb01{ namespace battleship{ class Player; + class Weapon; class Unit; class Vehicle; class Structure; @@ -75,6 +76,7 @@ namespace battleship{ CARRIER, SUBMARINE, ICEBREAKER, + FREEZER, LAND_FACTORY, NAVAL_FACTORY, TRADE_CENTER, @@ -88,41 +90,6 @@ namespace battleship{ class Unit : public GameObject{ public: - class Weapon{ - public: - Weapon(Unit*, sol::table, int); - ~Weapon(); - virtual void update(); - virtual void fire(Order); - void trackTarget(vb01::Vector3); - 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 Order::TYPE getOrderType(){return orderType;} - private: - Unit *unit = nullptr; - Order::TYPE orderType; - int id, projId = -1, rateOfFire, damage = 0; - float minRange = 0, maxRange, rotSpeed, maxFireAngle; - vb01::s64 lastFireTime = 0; - FxManager::Fx *fireFx = nullptr; - vb01::Quaternion projRot; - vb01::Vector3 projPos; - float horConstraint = 1.57, vertConstraint = 1.57; - vb01::Node *horNode = nullptr, *vertNode = nullptr, *projPar = nullptr; - static std::string LASER_FLAG; - - vb01::Vector3 calcOrientVec(int); - void alignNode(vb01::Vector3, vb01::Node*, bool); - void initProjectileData(sol::table); - vb01::Node* initNode(sol::table, bool); - FxManager::Fx* initFx(sol::table, std::string, bool); - void useFx(FxManager::Fx*, vb01::Vector3, bool); - inline bool canFire(){return vb01::getTime() - lastFireTime > rateOfFire;} - }; struct GarrisonSlot{ Vehicle *vehicle = nullptr; @@ -160,7 +127,7 @@ namespace battleship{ inline float getLineOfSight() {return lineOfSight;} inline UnitType getType() {return type;} inline UnitClass getUnitClass() {return unitClass;} - inline void takeDamage(int damage) {health -= damage;} + inline void takeDamage(int damage) {health -= damage * (1 + (freezeDmgFactor - 1) * freezeStatus * .01f);} inline int getPlayerId() {return playerId;} inline int getHealth(){return health;} inline int getDeathHp(){return DEATH_HP;} @@ -171,6 +138,9 @@ namespace battleship{ inline std::string getGuiScreen(){return guiScreen;} inline BuildableUnit getBuildableUnit(int i){return buildableUnits[i];} inline vb01::Node* getLosLightNode(){return losLightNode;} + inline void setFreezeStatus(int fs){this->freezeStatus = std::clamp(fs, 0, 100);} + inline int getFreezeStatus(){return freezeStatus;} + inline bool isEnabled(){return enabled;} private: void renderOrderLine(bool); void updateScreenCoordinates(); @@ -183,13 +153,13 @@ namespace battleship{ sf::SoundBuffer *selectionSfxBuffer; sf::Sound *selectionSfx = nullptr; vb01::Node *hpBackgroundNode = nullptr, *hpForegroundNode = nullptr, *losLightNode = nullptr; - bool vehicle; + bool vehicle, enabled = true; protected: UnitClass unitClass; UnitType type; std::vector orders; std::string guiScreen = ""; - int health = 0, maxHealth, playerId, lenHpBar = 200; + int health = 0, maxHealth, playerId, lenHpBar = 200, freezeStatus = 0, freezeDmgFactor = 10; vb01::s64 orderLineDispTime = 0, lastFireTime = 0; float lineOfSight; std::vector armorTypes; @@ -219,6 +189,7 @@ namespace battleship{ virtual void launch(Order); virtual void handleResources(Order){} virtual void hack(Order){} + virtual void freeze(Order){} float calculateRotation(vb01::Vector3, float, float); void removeBar(vb01::Node*); vb01::Node* createBar(vb01::Vector2, vb01::Vector2, vb01::Vector4); diff --git a/vehicle.cpp b/vehicle.cpp index a535eaa..818542f 100644 --- a/vehicle.cpp +++ b/vehicle.cpp @@ -8,6 +8,7 @@ #include #include "vehicle.h" +#include "weapon.h" #include "pathfinder.h" #include "player.h" #include "game.h" diff --git a/weapon.cpp b/weapon.cpp new file mode 100644 index 0000000..ac04593 --- /dev/null +++ b/weapon.cpp @@ -0,0 +1,362 @@ +#include +#include +#include + +#include "map.h" +#include "weapon.h" +#include "player.h" +#include "fxManager.h" +#include "gameObjectFactory.h" +#include "projectile.h" + +namespace battleship{ + using namespace std; + using namespace vb01; + using namespace gameBase; + + string Weapon::LASER_FLAG = "laser"; + + 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"]; + orderType = (Order::TYPE)weaponTable["orderType"]; + + initProjectileData(weaponTable); + + sol::optional horNodeTblOpt = unitTable["weapons"][wid + 1]["horizontalNode"]; + sol::optional vertNodeTblOpt = unitTable["weapons"][wid + 1]["verticalNode"]; + + if(horNodeTblOpt != sol::nullopt) horNode = initNode(weaponTable, true); + if(vertNodeTblOpt != sol::nullopt) vertNode = initNode(weaponTable, false); + + fireFx = initFx(weaponTable, "fireFx", true); + if(fireFx) FxManager::getSingleton()->addFx(fireFx); + } + + + Node* Weapon::initNode(sol::table weaponTable, bool horizontal){ + sol::table nodeTbl = weaponTable[horizontal ? "horizontalNode" : "verticalNode"]; + maxFireAngle = nodeTbl["maxFireAngle"]; + rotSpeed = nodeTbl["rotationSpeed"]; + + string name = nodeTbl["name"]; + return unit->getModel()->findDescendant(name, true); + } + + void Weapon::initProjectileData(sol::table weaponTable){ + string projTableKey = "projectile"; + sol::optional proj = weaponTable[projTableKey]; + + if(proj != sol::nullopt){ + 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"]); + } + } + + FxManager::Fx* Weapon::initFx(sol::table weaponTable, string fxKey, bool attached){ + sol::optional fxOpt = weaponTable[fxKey]; + + if(fxOpt == sol::nullopt) return nullptr; + + sol::table fxTbl = weaponTable[fxKey]; + 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 = Vector3::VEC_ZERO; + 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, 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_FLAG); + 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 ? unit->getModel() : Root::getSingleton()->getRootNode()); + sol::optional parNameOpt = compTbl["parent"]; + + if(parNameOpt != sol::nullopt){ + string parName = compTbl["parent"]; + parNode = unit->getModel()->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); + } + + Weapon::~Weapon(){ + FxManager *fm = FxManager::getSingleton(); + + if(fireFx) fm->removeFx(fireFx); + } + + Vector3 Weapon::calcOrientVec(int id){ + Vector3 orientVec; + + switch(id){ + case 0: + orientVec = unit->getDirVec(); + break; + case 1: + orientVec = unit->getLeftVec(); + break; + case 2: + orientVec = unit->getUpVec(); + break; + } + + if(horNode) orientVec = horNode->getOrientation() * orientVec; + if(vertNode) orientVec = vertNode->getOrientation() * orientVec; + + return orientVec; + } + + //TODO replace unit pos with absolute weapon pos for withinAngle + void Weapon::update(){ + if(!unit->isEnabled()) return; + + int numOrders = unit->getNumOrders(); + int ordTp = (numOrders > 0 ? (int)unit->getOrder(0).type : -1); + Unit *targUnit = (ordTp != -1 ? unit->getOrder(0).targets[0].unit : nullptr); + + if(ordTp == (int)orderType){ + Vector3 targPos = (targUnit ? targUnit->getPos() : unit->getOrder(0).targets[0].pos); + float targDist = unit->getPos().getDistanceFrom(targPos); + + bool withinRange = (minRange <= targDist && targDist <= maxRange); + + Vector3 dirVec = calcOrientVec(0); + bool withinAngle = (horNode ? Vector3(dirVec.x, 0, dirVec.z).norm().getAngleBetween((targPos - unit->getPos()).norm()) <= maxFireAngle : true); + + if((Order::TYPE)ordTp == Order::TYPE::ATTACK && withinRange && withinAngle) + fire(unit->getOrder(0)); + } + else + trackTarget(unit->getPos() + unit->getDirVec()); + } + + 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){ + if(fire && ((Node*)comp.comp)->getName() == LASER_FLAG){ + 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::updateTargetUnit(Unit *targetUnit){ + targetUnit->takeDamage(damage); + unit->updateGameStats(targetUnit); + } + + + void Weapon::fire(Order order){ + if(!canFire()) return; + + Unit *targetUnit = order.targets[0].unit; + Vector3 targPos = (targetUnit ? targetUnit->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]; + + if(targetUnit){ + updateTargetUnit(targetUnit); + useFx(initFx(weaponTbl, "unitHitFx", false), targPos, false); + } + else{ + Map *map = Map::getSingleton(); + Map::Cell::Type cellType = map->getCells()[map->getCellId(targPos)].type; + string fxKey = (cellType == Map::Cell::Type::LAND ? "landHitFx" : "waterHitFx"); + useFx(initFx(weaponTbl, fxKey, 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::alignNode(Vector3 targPos, Node *node, bool horizontal){ + Vector3 targDir = targPos - unit->getPos(); + Vector3 targDirHor = Vector3(targDir.x, 0, targDir.z).norm(), targDirVert; + + Vector3 dirVec = calcOrientVec(0); + Vector3 dirVecHor = Vector3(dirVec.x, 0, dirVec.z).norm(), dirVecVert; + + Vector3 leftVec = calcOrientVec(1); + bool negate = (Vector3(leftVec.x, 0, leftVec.z).norm().getAngleBetween(targDirHor) < PI / 2); + + float angle = dirVecHor.getAngleBetween(targDirHor); + float rotAngle = (rotSpeed < angle ? rotSpeed : angle); + + Quaternion rot = Quaternion((negate ? 1 : -1) * rotAngle, unit->getUpVec()) * node->getOrientation(); + node->setOrientation(rot); + } + + void Weapon::trackTarget(Vector3 targPos){ + if(horNode) alignNode(targPos, horNode, true); + //if(vertNode) alignNode(targPos, vertNode, false); + } + + Freezer::Freezer(Unit *u, sol::table unitTable, int wid) : Weapon(u, unitTable, wid){} + + void Freezer::updateTargetUnit(Unit *targetUnit){ + if(canFreeze()){ + targetUnit->setFreezeStatus(targetUnit->getFreezeStatus() + 1); + lastFreezeTime = getTime(); + } + } +} diff --git a/weapon.h b/weapon.h new file mode 100644 index 0000000..614a699 --- /dev/null +++ b/weapon.h @@ -0,0 +1,72 @@ +#ifndef WEAPON_H +#define WEAPON_H + +#include +#include +#include + +#include + +#include "unit.h" + +namespace vb01{ + class Node; +} + +namespace battleship{ + class FxManager; + + class Weapon{ + public: + enum class Type{FREEZER = 1}; + + Weapon(Unit*, sol::table, int); + ~Weapon(); + virtual void update(); + virtual void fire(Order); + void trackTarget(vb01::Vector3); + 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 Order::TYPE getOrderType(){return orderType;} + private: + Unit *unit = nullptr; + Order::TYPE orderType; + int id, projId = -1, damage = 0; + float minRange = 0, maxRange, rotSpeed, maxFireAngle; + vb01::s64 lastFireTime = 0; + FxManager::Fx *fireFx = nullptr; + vb01::Quaternion projRot; + vb01::Vector3 projPos; + float horConstraint = 1.57, vertConstraint = 1.57; + vb01::Node *horNode = nullptr, *vertNode = nullptr, *projPar = nullptr; + static std::string LASER_FLAG; + + vb01::Vector3 calcOrientVec(int); + void alignNode(vb01::Vector3, vb01::Node*, bool); + void initProjectileData(sol::table); + vb01::Node* initNode(sol::table, bool); + FxManager::Fx* initFx(sol::table, std::string, bool); + void useFx(FxManager::Fx*, vb01::Vector3, bool); + inline bool canFire(){return vb01::getTime() - lastFireTime > rateOfFire;} + protected: + int rateOfFire; + + void updateTargetUnit(Unit*); + }; + + class Freezer : public Weapon{ + public: + Freezer(Unit*, sol::table, int); + private: + vb01::s64 lastFreezeTime = 0; + + void updateTargetUnit(Unit*); + inline bool canFreeze(){return vb01::getTime() - lastFreezeTime > rateOfFire;} + }; +} + +#endif