diff --git a/bin/common/Language/BOXCE/en-US.yml b/bin/common/Language/BOXCE/en-US.yml index 8916396a6c..65342597ce 100644 --- a/bin/common/Language/BOXCE/en-US.yml +++ b/bin/common/Language/BOXCE/en-US.yml @@ -113,3 +113,6 @@ en-US: WRT_VANILLA: "vanilla formula" hangarType: "Hangar Type" spaceOccupied: "Space Occupied" + # Diagonal Terrain melee + STR_DIAG_TERRAIN_MELEE: "Diagonal Terrain Melee" + STR_DIAG_TERRAIN_MELEE_DESC: "Hitting terrain objects at diagonal directions with melee.{NEWLINE}Requires activated \"TerrainMeleeExtended\" feature.{NEWLINE}{ALT}Alt+click{ALT} - to hit objects at current and adjacent tiles." \ No newline at end of file diff --git a/bin/common/Language/BOXCE/ru.yml b/bin/common/Language/BOXCE/ru.yml index e9a6a266ed..2053474080 100644 --- a/bin/common/Language/BOXCE/ru.yml +++ b/bin/common/Language/BOXCE/ru.yml @@ -111,3 +111,6 @@ ru: WRT_VANILLA: "Формула из оригинальной игры" hangarType: "Тип ангара" spaceOccupied: "Место занято" + # Diagonal Terrain melee + STR_DIAG_TERRAIN_MELEE: "Рукопашно местность по диагонали " + STR_DIAG_TERRAIN_MELEE_DESC: "Атака объектов ландшафта по диагонали в ближнем бою.{NEWLINE}Требует включения опции \"terrainMeleeExtended\".{NEWLINE}{ALT}Alt+клик{ALT} - бить объекты на текущей и соседних клетках." diff --git a/src/Battlescape/TileEngine.cpp b/src/Battlescape/TileEngine.cpp index e983ab2040..a1d321b178 100644 --- a/src/Battlescape/TileEngine.cpp +++ b/src/Battlescape/TileEngine.cpp @@ -3231,6 +3231,7 @@ TileEngine::ReactionScore TileEngine::determineReactionType(BattleUnit *unit, Ba return reaction; } } + if (_save->canUseWeapon(weapon, unit, false, BA_SNAPSHOT)) { // has a gun capable of snap shot with ammo @@ -5947,7 +5948,7 @@ bool TileEngine::validTerrainMeleeRange(BattleAction* action) action->terrainMeleeTilePart = 0; if (action->weapon) - { + { // is melee weapon able to hit terrain ? auto wRule = action->weapon->getRules(); if (wRule->getBattleType() == BT_MELEE) { @@ -5961,79 +5962,115 @@ bool TileEngine::validTerrainMeleeRange(BattleAction* action) } } - Position pos = action->actor->getPosition(); int direction = action->actor->getDirection(); - BattleUnit* attacker = action->actor; if (direction < 0 || direction > 7) - { + { // only horisontal direction are allowed return false; - } - if (direction % 2 != 0) + } + if (!Options::diagTerrainMelee && direction % 2 != 0) { - // diagonal directions are not supported + // diagonal directions are not supported. pWWWa: if proper option is not activated return false; } + + Position pos = action->actor->getPosition(); Position p; Pathfinding::directionToVector(direction, &p); - Tile* originTile = _save->getTile(pos); - Tile* originTile2 = originTile; - if (originTile && originTile->getTerrainLevel() <= -16) + + if (originTile && originTile->getTerrainLevel() <= -16 && !_save->isAltPressed(true)) { - // if we are on the upper part of stairs, target one tile above + // if we are on the upper part of stairs, target one tile above. pWWWa: let leave at the same tile altitude with pressed Alt (if there are need to hit stair under feet). pos += Position(0, 0, 1); originTile = _save->getTile(pos); } - Tile* neighbouringTile = _save->getTile(pos + p); - Tile* neighbouringTile2 = nullptr; - int size = attacker->getArmor()->getSize(); - if (size > 1) + + Tile* originTile2 = originTile; + Tile* neighbouringTile = _save->getTile(pos + p); + Tile* neighbouringTile2 = nullptr; + Tile* neighbouringTile3 = nullptr; + int size = action->actor->getArmor()->getSize() - 1; // pWWWa: attacker variable removed, cause it used only once + + switch (direction) { - if (direction == 0) - { - // North - originTile2 = _save->getTile(pos + Position(1, 0, 0)); - neighbouringTile2 = _save->getTile(pos + p + Position(1, 0, 0)); - } - else if (direction == 2) - { - // East - neighbouringTile = _save->getTile(pos + p + Position(1, 0, 0)); - neighbouringTile2 = _save->getTile(pos + p + Position(1, 1, 0)); - } - else if (direction == 4) - { - // South - neighbouringTile = _save->getTile(pos + p + Position(0, 1, 0)); - neighbouringTile2 = _save->getTile(pos + p + Position(1, 1, 0)); - } - else if (direction == 6) - { - // West - originTile2 = _save->getTile(pos + Position(0, 1, 0)); - neighbouringTile2 = _save->getTile(pos + p + Position(0, 1, 0)); - } - if (!neighbouringTile2 || !originTile2) - { - return false; - } + case 0: // North + originTile2 = _save->getTile(pos + Position(size, 0, 0)); + neighbouringTile2 = _save->getTile(pos + p + Position(size, 0, 0)); + break; + + case 2: // East + originTile = _save->getTile(pos + Position(size, 0, 0)); + originTile2 = _save->getTile(pos + Position(size, size, 0)); + neighbouringTile = _save->getTile(pos + p + Position(size, 0, 0)); + neighbouringTile2 = _save->getTile(pos + p + Position(size, size, 0)); + break; + + case 4: // South + originTile = _save->getTile(pos + Position(0, size, 0)); + originTile2 = _save->getTile(pos + Position(size, size, 0)); + neighbouringTile = _save->getTile(pos + p + Position(0, size, 0)); + neighbouringTile2 = _save->getTile(pos + p + Position(size, size, 0)); + break; + + case 6: // West + originTile2 = _save->getTile(pos + Position(0, size, 0)); + neighbouringTile2 = _save->getTile(pos + p + Position(0, size, 0)); + break; + + case 1: // North-East + originTile = _save->getTile(pos + Position(size, 0, 0)); + neighbouringTile = _save->getTile(pos + p + Position(size, 0, 0)); + neighbouringTile2 = _save->getTile(pos + Position(size, -1, 0)); + neighbouringTile3 = _save->getTile(pos + Position(size + 1, 0, 0)); + break; + + case 3: // South-East + originTile = _save->getTile(pos + Position(size, size, 0)); + neighbouringTile = _save->getTile(pos + p + Position(size, size, 0)); + neighbouringTile2 = _save->getTile(pos + p + Position(size, size - 1, 0)); + neighbouringTile3 = _save->getTile(pos + p + Position(size - 1, size, 0)); + break; + + case 5: // South-West + originTile = _save->getTile(pos + Position(0, size, 0)); + neighbouringTile = _save->getTile(pos + p + Position(0, size, 0)); + neighbouringTile2 = _save->getTile(pos + Position(0, size + 1, 0)); + neighbouringTile3 = _save->getTile(pos + Position(- 1, size, 0)); + break; + + case 7: //North-West + neighbouringTile2 = _save->getTile(pos + Position(-1, 0, 0)); + neighbouringTile3 = _save->getTile(pos + Position(0, -1, 0)); } + if (originTile && neighbouringTile) { auto setTarget = [](Tile* tt, TilePart tp, BattleAction* aa, int dir = -1) -> bool { + Position origin = tt->getSavedGame()->getTileEngine()->getSightOriginVoxel(aa->actor, tt) + Position(0, 0, -4); + Position target; + + if (!tt->getSavedGame()->getTileEngine()->canTargetTile(&origin, tt, tp, &target, aa->actor, false)) + { // is tile reachable (more predictable targeting queue at diagonals) + return false; + } + MapData* obj = tt->getMapData(tp); if (obj) { - if (dir > -1 && tp == O_OBJECT) - { + if (dir > -1 && tp == O_OBJECT && !(Options::diagTerrainMelee && tt->getSavedGame()->isAltPressed(true))) + { auto bigWall = obj->getBigWall(); if (dir == 0 /*north*/ && bigWall != Pathfinding::BIGWALLNORTH && bigWall != Pathfinding::BIGWALLWESTANDNORTH) return false; if (dir == 2 /*east */ && bigWall != Pathfinding::BIGWALLEAST && bigWall != Pathfinding::BIGWALLEASTANDSOUTH) return false; if (dir == 4 /*south*/ && bigWall != Pathfinding::BIGWALLSOUTH && bigWall != Pathfinding::BIGWALLEASTANDSOUTH) return false; if (dir == 6 /*west */ && bigWall != Pathfinding::BIGWALLWEST && bigWall != Pathfinding::BIGWALLWESTANDNORTH) return false; - } + if (dir == 1 /*NW */ && bigWall != Pathfinding::BIGWALLNWSE) return false; + if (dir == 3 /*NE */ && bigWall != Pathfinding::BIGWALLNESW && bigWall != Pathfinding::BIGWALLEASTANDSOUTH) return false; + if (dir == 5 /*SE */ && bigWall != Pathfinding::BIGWALLNWSE) return false; + if (dir == 7 /*SW */ && bigWall != Pathfinding::BIGWALLNESW && bigWall != Pathfinding::BIGWALLWESTANDNORTH) return false; + } if (tp != O_OBJECT && !obj->isDoor() && !obj->isUFODoor() && tt->getTUCost(tp, MT_WALK) != Pathfinding::INVALID_MOVE_COST) { // it is possible to walk through this (rubble) wall... no need to attack it @@ -6058,76 +6095,88 @@ bool TileEngine::validTerrainMeleeRange(BattleAction* action) return false; }; - if (setTarget(originTile, O_OBJECT, action, direction)) + if (setTarget(originTile, O_OBJECT, action, direction) || + setTarget(originTile2, O_OBJECT, action, direction)) { // All directions: target the object (marked as big wall) on the current tile return true; } - if (size > 1) + + switch (direction) { - if (setTarget(originTile2, O_OBJECT, action, direction)) - { - // All directions + case 0: // North + if (setTarget(originTile, O_NORTHWALL, action) || + setTarget(originTile2, O_NORTHWALL, action) || + setTarget(neighbouringTile2, O_WESTWALL, action) && size) return true; - } - } + break; - if (direction == 0 && setTarget(originTile, O_NORTHWALL, action)) - { - // North: target the north wall of the same tile - return true; - } - else if (direction == 2 && setTarget(neighbouringTile, O_WESTWALL, action)) - { - // East: target the west wall of the neighbouring tile - return true; - } - else if (direction == 4 && setTarget(neighbouringTile, O_NORTHWALL, action)) - { - // South: target the north wall of the neighbouring tile - return true; - } - else if (direction == 6 && setTarget(originTile, O_WESTWALL, action)) - { - // West: target the west wall of the same tile - return true; - } - if (size > 1) - { - if (direction == 0 && setTarget(originTile2, O_NORTHWALL, action)) - { - // North + case 2: // East + if (setTarget(neighbouringTile, O_WESTWALL, action) || + setTarget(neighbouringTile2, O_WESTWALL, action) || + setTarget(neighbouringTile2, O_NORTHWALL, action) && size) return true; - } - else if (direction == 2 && setTarget(neighbouringTile2, O_WESTWALL, action)) - { - // East + break; + + case 4: // South + if (setTarget(neighbouringTile, O_NORTHWALL, action) || + setTarget(neighbouringTile2, O_NORTHWALL, action) || + setTarget(neighbouringTile2, O_WESTWALL, action) && size) return true; - } - else if (direction == 4 && setTarget(neighbouringTile2, O_NORTHWALL, action)) - { - // South + break; + + case 6: // West + if (setTarget(originTile, O_WESTWALL, action) || + setTarget(originTile2, O_WESTWALL, action) || + setTarget(neighbouringTile2, O_NORTHWALL, action) && size) return true; - } - else if (direction == 6 && setTarget(originTile2, O_WESTWALL, action)) - { - // West + break; + + case 1: // North-East + if (setTarget(neighbouringTile, O_WESTWALL, action) || // 3 + setTarget(neighbouringTile3, O_NORTHWALL, action) || // 4 + setTarget(originTile, O_NORTHWALL, action) || // 1 + setTarget(neighbouringTile3, O_WESTWALL, action)) // 2 + return true; + break; + + case 3: // South-East + if (setTarget(neighbouringTile, O_WESTWALL, action) || // 3 + setTarget(neighbouringTile, O_NORTHWALL, action) || // 4 + setTarget(neighbouringTile3, O_NORTHWALL, action) || // 1 + setTarget(neighbouringTile2, O_WESTWALL, action)) // 2 + return true; + break; + + case 5: // North-East + if (setTarget(neighbouringTile, O_NORTHWALL, action) || // 4 + setTarget(neighbouringTile2, O_NORTHWALL, action) || // 1 + setTarget(neighbouringTile2, O_WESTWALL, action) || // 3 + setTarget(originTile, O_WESTWALL, action)) // 2 + return true; + break; + + case 7: // North-West + if (setTarget(neighbouringTile3, O_WESTWALL, action) || // 4 + setTarget(neighbouringTile2, O_NORTHWALL, action) || // 3 + setTarget(originTile, O_NORTHWALL, action) || // 2 + setTarget(originTile, O_WESTWALL, action)) // 1 return true; - } } - if (setTarget(neighbouringTile, O_OBJECT, action)) - { - // All directions: target the object on the neighbouring tile + if (setTarget(neighbouringTile, O_OBJECT, action) || + setTarget(neighbouringTile2, O_OBJECT, action) && size && !(direction % 2)) + { + // All directions: target the object on the neighbouring tile + big unit's second part at non-diagonal direction included return true; } - if (size > 1) + + if ( _save->isAltPressed(true) && direction % 2 && + ( setTarget(neighbouringTile2, O_OBJECT, action) || + setTarget(neighbouringTile3, O_OBJECT, action) ) ) { - if (setTarget(neighbouringTile2, O_OBJECT, action)) - { - // All directions - return true; - } + // Diagonal terrain object targeting helper. Suitable for hitting big walls and terrain stuff at adjacent tiles. + return true; } } diff --git a/src/Engine/Options.cpp b/src/Engine/Options.cpp index 0b484caef6..b00cce5678 100644 --- a/src/Engine/Options.cpp +++ b/src/Engine/Options.cpp @@ -569,16 +569,21 @@ void createOptionsOTHER() void createAdvancedOptionsOTHER() { + // OTHER options General _info.push_back(OptionInfo(OPTION_OTHER, "showCraftHangar", &showCraftHangar, false, "STR_SHOW_CRAFT_HANGAR", "STR_GENERAL")); - _info.push_back(OptionInfo(OPTION_OTHER, "forcedAbsoluteCosts", &forcedAbsoluteCosts, false, "STR_FORCED_ABSOLUTE_COSTS", "STR_BATTLESCAPE")); + // OTHER options Geoscape _info.push_back(OptionInfo(OPTION_OTHER, "dogfightAI", &dogfightAI, true, "STR_DOGFIGHTAI", "STR_GEOSCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "aggressiveRetaliation", &aggressiveRetaliation, true, "STR_AGGRESSIVERETALIATION", "STR_GEOSCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "displayHiddenAlienActivity", &displayHiddenAlienActivity, 0, "STR_DISPLAY_HIDDEN_ALIEN_ACTIVITY", "STR_GEOSCAPE")); + // OTHER options Basescape _info.push_back(OptionInfo(OPTION_OTHER, "baseDefenseProbability", &baseDefenseProbability, false, "STR_DISPLAY_BASE_DEFENSE_PROBABILITY", "STR_BASESCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "baseDetectionProbability", &baseDetectionProbability, false, "STR_DISPLAY_BASE_DETECTION_PROBABILITY", "STR_BASESCAPE")); + // OTHER options Battlescape + _info.push_back(OptionInfo(OPTION_OTHER, "diagTerrainMelee", &diagTerrainMelee, false, "STR_DIAG_TERRAIN_MELEE", "STR_BATTLESCAPE")); + _info.push_back(OptionInfo(OPTION_OTHER, "forcedAbsoluteCosts", &forcedAbsoluteCosts, false, "STR_FORCED_ABSOLUTE_COSTS", "STR_BATTLESCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "shootingSpreadMode", &shootingSpreadMode, 1, "STR_SHOOTING_SPREAD_MODE", "STR_BATTLESCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "useChanceToHit", &useChanceToHit, true, "STR_BATTLECHANCETOHIT", "STR_BATTLESCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "battleRealisticAccuracy", &battleRealisticAccuracy, false, "STR_BATTLEREALISTICACCURACY", "STR_BATTLESCAPE")); @@ -599,6 +604,7 @@ void createAdvancedOptionsOTHER() _info.push_back(OptionInfo(OPTION_OTHER, "preprimeGrenades", &preprimeGrenades, 1, "STR_PREPRIMEGRENADES", "STR_BATTLESCAPE")); _info.push_back(OptionInfo(OPTION_OTHER, "updateTurnsSinceSeenByClue", &updateTurnsSinceSeenByClue, true, "STR_UPDATETURNSSINCESEENBYCLUE", "STR_BATTLESCAPE")); + // OTHER options AI _info.push_back(OptionInfo(OPTION_OTHER, "brutalAI", &brutalAI, 1, "STR_BRUTALAI", "STR_AI")); _info.push_back(OptionInfo(OPTION_OTHER, "brutalCivilians", &brutalCivilians, 0, "STR_BRUTALCIVILIANS", "STR_AI")); _info.push_back(OptionInfo(OPTION_OTHER, "ignoreDelay", &ignoreDelay, true, "STR_IGNOREDELAY", "STR_AI")); @@ -606,6 +612,8 @@ void createAdvancedOptionsOTHER() _info.push_back(OptionInfo(OPTION_OTHER, "avoidMines", &avoidMines, true, "STR_AVOIDMINES", "STR_AI")); _info.push_back(OptionInfo(OPTION_OTHER, "aiPeformance", &aiPerformanceOptimization, false, "STR_AI_PERFORMANCE", "STR_AI")); _info.push_back(OptionInfo(OPTION_OTHER, "aiCheatMode", &aiCheatMode, 0, "STR_AICHEATMODE", "STR_AI")); + + // OTHER options Autoplay _info.push_back(OptionInfo(OPTION_OTHER, "autoCombat", &autoCombat, false, "STR_AUTOCOMBAT", "STR_AUTO")); _info.push_back(OptionInfo(OPTION_OTHER, "autoCombatEachCombat", &autoCombatEachCombat, true, "STR_AUTOCOMBAT_EACH_COMBAT", "STR_AUTO")); _info.push_back(OptionInfo(OPTION_OTHER, "autoCombatEachTurn", &autoCombatEachTurn, true, "STR_AUTOCOMBAT_EACH_TURN", "STR_AUTO")); diff --git a/src/Engine/Options.inc.h b/src/Engine/Options.inc.h index a4a1e514be..35d9c66a0d 100644 --- a/src/Engine/Options.inc.h +++ b/src/Engine/Options.inc.h @@ -36,7 +36,7 @@ OPT PathPreview battleNewPreviewPath; OPT int shootingSpreadMode, battleRealisticShotDispersion, battleRealisticCoverEfficiency, battleScrollSpeed, battleDragScrollButton, battleFireSpeed, battleXcomSpeed, battleAlienSpeed, battleExplosionHeight, battlescapeScale, battleTerrainSquishyness, preprimeGrenades, battleThrownSpeed; OPT bool traceAI, battleInstantGrenade, battleNotifyDeath, battleTooltips, battleHairBleach, battleAutoEnd, strafe, forceFire, showMoreStatsInInventoryView, allowPsionicCapture, skipNextTurnScreen, disableAutoEquip, battleDragScrollInvert, - battleUFOExtenderAccuracy, useChanceToHit, battleRealisticAccuracy, battleRealisticImprovedAimed, battleRealisticImprovedLof, battleRealisticDisplayRolls, battleRealisticDisplayOthersRolls, battleAltGrenades, battleConfirmFireMode, battleSmoothCamera, noAlienPanicMessages, alienBleeding, instantPrime, strictBlockedChecking, updateTurnsSinceSeenByClue; + battleUFOExtenderAccuracy, useChanceToHit, battleRealisticAccuracy, battleRealisticImprovedAimed, battleRealisticImprovedLof, battleRealisticDisplayRolls, battleRealisticDisplayOthersRolls, battleAltGrenades, battleConfirmFireMode, battleSmoothCamera, noAlienPanicMessages, alienBleeding, instantPrime, strictBlockedChecking, updateTurnsSinceSeenByClue, diagTerrainMelee; OPT SDLKey keyBattleLeft, keyBattleRight, keyBattleUp, keyBattleDown, keyBattleLevelUp, keyBattleLevelDown, keyBattleCenterUnit, keyBattlePrevUnit, keyBattleNextUnit, keyBattleDeselectUnit, keyBattleUseLeftHand, keyBattleUseRightHand, keyBattleInventory, keyBattleMap, keyBattleOptions, keyBattleEndTurn, keyBattleAbort, keyBattleStats, keyBattleKneel, keyBattleReserveKneel, keyBattleReload, keyBattlePersonalLighting, keyBattleReserveNone, keyBattleReserveSnap, keyBattleReserveAimed, keyBattleReserveAuto,