diff --git a/megamek/data/scenarios/Test Setups/MekCowl.mms b/megamek/data/scenarios/Test Setups/MekCowl.mms new file mode 100644 index 00000000000..c458986bc9d --- /dev/null +++ b/megamek/data/scenarios/Test Setups/MekCowl.mms @@ -0,0 +1,39 @@ +MMSVersion: 2 +name: Test Setup for Mek Cowls +planet: None +description: A few units on a small map to test mek cowl (quirk) functions +map: Beginner Box/16x17 Grassland 1.board + +factions: +- name: Test Player + + units: + - fullname: Cyclops CP-10-Z + at: [ 9, 12 ] + facing: 0 + crew: + gunnery: 0 + + - fullname: Cyclops CP-10-Z + at: [ 9, 8 ] + facing: 3 + crew: + gunnery: 0 + + - fullname: Cyclops CP-10-Z + at: [ 10, 8 ] + facing: 3 + crew: + gunnery: 0 + + - fullname: Cyclops CP-10-Z + at: [ 9, 15 ] + facing: 0 + crew: + gunnery: 0 + + - fullname: Cyclops CP-10-Z + at: [ 6, 9 ] + facing: 3 + crew: + gunnery: 0 diff --git a/megamek/src/megamek/common/Coords.java b/megamek/src/megamek/common/Coords.java index b01c8e4b5ce..0e10fea0c3a 100644 --- a/megamek/src/megamek/common/Coords.java +++ b/megamek/src/megamek/common/Coords.java @@ -456,6 +456,34 @@ public static Coords nextHex(Coords current, Coords destination) { new IdealHex(destination), directions); } + /** + * Returns true when the given other Coords are exactly on the hex row (line) from this Coords in the given + * direction. For example, if the direction is 0 (north), returns true only for Coords that are above + * this Coords at the same x. Returns false when the other Coords are null, the other Coords are equal + * to this or the direction is outside of 0 to 5. + * + * @param direction The direction, 0 = N, 2 = SE ... + * @param other The Coords to test + * @return True when the other Coords are on the hex row from this Coords in the given direction + */ + public boolean isOnHexRow(int direction, @Nullable Coords other) { + if ((other == null) || this.equals(other)) { + return false; + } + HexLine line = new HexLine(this, direction); + if (line.judgePoint(other) != 0) { + return false; + } else { + return switch (direction) { + case 0 -> other.y < y; + case 1, 2 -> other.x > x; + case 3 -> other.y > y; + case 4, 5 -> other.x < x; + default -> false; + }; + } + } + /** * Returns a list of all adjacent coordinates (distance = 1), * regardless of whether they're on the board or not. diff --git a/megamek/src/megamek/common/Mech.java b/megamek/src/megamek/common/Mech.java index 83df3acb569..01da9cf942a 100644 --- a/megamek/src/megamek/common/Mech.java +++ b/megamek/src/megamek/common/Mech.java @@ -256,6 +256,9 @@ public abstract class Mech extends Entity { protected int cockpitType = COCKPIT_STANDARD; + /** + * Head armor provided by the Cowl quirk. Ignored when the unit doesn't have Cowl or quirks aren't used. + */ private int cowlArmor = 3; private int hasLaserHeatSinks = HAS_UNKNOWN; @@ -389,34 +392,29 @@ public CrewType defaultCrewType() { */ public abstract boolean cannotStandUpFromHullDown(); - public int getCowlArmor() { - if (hasCowl()) { - return cowlArmor; - } - return 0; - } - + /** + * @return True if this Mek has the Cowl quirk and quirks are used in the present game. + */ public boolean hasCowl() { return hasQuirk(OptionsConstants.QUIRK_POS_COWL); } /** - * Damage the cowl. Returns amount of excess damage + * Damages the remaining cowl armor, if any, by the given amount. Returns the amount of excess damage + * that is left after deducting cowl armor. This method tests if the unit has Cowl and quirks are + * being used. * - * @param amount - * @return + * @param amount The incoming damage + * @return The damage left after deducting the cowl's remaining armor, if any */ public int damageCowl(int amount) { if (hasCowl()) { - if (amount < cowlArmor) { - cowlArmor -= amount; - return 0; - } - amount -= cowlArmor; - cowlArmor = 0; + int excessDamage = Math.max(amount - cowlArmor, 0); + cowlArmor = Math.max(cowlArmor - amount, 0); + return excessDamage; + } else { return amount; } - return amount; // No cowl - return full damage } /** diff --git a/megamek/src/megamek/common/MiscType.java b/megamek/src/megamek/common/MiscType.java index 1dece8cbb6c..43ef75b839b 100644 --- a/megamek/src/megamek/common/MiscType.java +++ b/megamek/src/megamek/common/MiscType.java @@ -1537,7 +1537,6 @@ public static void initializeTypes() { EquipmentType.addType(MiscType.createCombine()); EquipmentType.addType(MiscType.createBackhoe()); EquipmentType.addType(MiscType.createPileDriver()); - EquipmentType.addType(MiscType.createArmoredCowl()); EquipmentType.addType(MiscType.createNullSignatureSystem()); EquipmentType.addType(MiscType.createVoidSignatureSystem()); EquipmentType.addType(MiscType.createChameleonLightPolarizationShield()); @@ -8641,27 +8640,6 @@ public static MiscType createPintleTurret() { return misc; } - // Battle Armor Tech - - public static MiscType createArmoredCowl() { - MiscType misc = new MiscType(); - - misc.name = "Armored Cowl"; - misc.setInternalName(misc.name); - misc.tonnage = 1; - misc.criticals = 1; - misc.cost = 10000; - misc.flags = misc.flags.or(F_COWL).or(F_MECH_EQUIPMENT); - misc.bv = 10; - // Making this up based on the Strat Ops Quirk - - misc.techAdvancement.setTechBase(TECH_BASE_IS); - misc.techAdvancement.setISAdvancement(DATE_NONE, DATE_NONE, 2439); - misc.techAdvancement.setTechRating(RATING_C); - misc.techAdvancement.setAvailability(new int[] { RATING_E, RATING_E, RATING_E, RATING_X }); - return misc; - } - // Start BattleArmor equipment public static MiscType createISBALightActiveProbe() { diff --git a/megamek/src/megamek/server/totalwarfare/TWGameManager.java b/megamek/src/megamek/server/totalwarfare/TWGameManager.java index 0688d687076..658fed7e45b 100644 --- a/megamek/src/megamek/server/totalwarfare/TWGameManager.java +++ b/megamek/src/megamek/server/totalwarfare/TWGameManager.java @@ -18242,21 +18242,15 @@ public Vector damageEntity(Entity te, HitData hit, int damage, } // Armored Cowl may absorb some damage from hit - if (te instanceof Mech) { - Mech me = (Mech) te; - if (me.hasCowl() && (hit.getLocation() == Mech.LOC_HEAD) - && !throughFront) { - int damageNew = me.damageCowl(damage); - int damageDiff = damage - damageNew; - me.damageThisPhase += damageDiff; - damage = damageNew; - - r = new Report(3520); - r.subject = te_n; - r.indent(3); - r.add(damageDiff); - vDesc.addElement(r); - } + if ((te instanceof Mech targetMek) && targetMek.hasCowl() && (hit.getLocation() == Mech.LOC_HEAD) + && ((targetMek.getPosition() == null) || (ae == null) + || !targetMek.getPosition().isOnHexRow(targetMek.getSecondaryFacing(), ae.getPosition()))) { + int excessDamage = targetMek.damageCowl(damage); + int blockedByCowl = damage - excessDamage; + r = new Report(3520).subject(te_n).indent(3).add(blockedByCowl); + vDesc.addElement(r); + targetMek.damageThisPhase += blockedByCowl; + damage = excessDamage; } // So might modular armor, if the location mounts any. diff --git a/megamek/src/megamek/test/TestCoords.java b/megamek/src/megamek/test/TestCoords.java deleted file mode 100644 index eebec56077b..00000000000 --- a/megamek/src/megamek/test/TestCoords.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * MegaMek - Copyright (C) 2003 Ben Mazur (bmazur@sev.org) - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ -package megamek.test; - -import java.util.ArrayList; -import java.util.List; -import megamek.common.Coords; -import org.apache.logging.log4j.LogManager; - -/** - * This class will display various constants and the output of some methods in - * the Coords class. This will allow a knowledgeable programmer - * to determine that the Coords class is operating correctly. - * TODO: integrate JUnit into this class. - */ -public class TestCoords { - private static final String OUTFORMAT = "The hash of %s is 0x%08X"; - private static final String NBFORMAT = "%s translated in dir %s should be %s and is %s"; - private static final String FARFORMAT = "%s translated by %s in dir %s should be %s and is %s"; - private static final String DISTFORMAT = "Distance between %s and %s should be %s and is %s"; - - public static void main(String... args) { - for (int x = 1; x < 10; x++) { - Coords coords = new Coords(x, 2); - LogManager.getLogger().info(String.format(OUTFORMAT, coords, coords.hashCode())); - } - - for (int y = 10; y < 19; ++ y) { - Coords coords = new Coords(1, y); - LogManager.getLogger().info(String.format(OUTFORMAT, coords, coords.hashCode())); - } - - Coords neg_coords = new Coords(-11, -22); - LogManager.getLogger().info(String.format(OUTFORMAT, neg_coords, neg_coords.hashCode())); - - neg_coords = new Coords(42, -68); - LogManager.getLogger().info(String.format(OUTFORMAT, neg_coords, neg_coords.hashCode())); - - neg_coords = new Coords(-668, 42); - LogManager.getLogger().info(String.format(OUTFORMAT, neg_coords, neg_coords.hashCode())); - - Coords origin = new Coords(0, 0); - int dir = 2; - Coords neighbor = origin.translated(dir); - String correct = "(2,1)"; - LogManager.getLogger().info(String.format(NBFORMAT, origin.toFriendlyString(), dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(1, 0); - neighbor = origin.translated(dir); - correct = "(3,2)"; - LogManager.getLogger().info(String.format(NBFORMAT, origin.toFriendlyString(), dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(2, 1); - neighbor = origin.translated(dir); - correct = "(4,2)"; - LogManager.getLogger().info(String.format(NBFORMAT, origin.toFriendlyString(), dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(0, 0); - dir = 3; - neighbor = origin.translated(dir); - correct = "(1,2)"; - LogManager.getLogger().info(String.format(NBFORMAT, origin.toFriendlyString(), dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(7,-2); - dir = 5; - neighbor = origin.translated(dir); - correct = "(7,-1)"; - LogManager.getLogger().info(String.format(NBFORMAT, origin.toFriendlyString(), dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(0, 0); - dir = 2; - int dist = 3; - neighbor = origin.translated(dir, dist); - correct = "(4,2)"; - LogManager.getLogger().info(String.format(FARFORMAT, origin.toFriendlyString(), dist, dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(10, 4); - dir = 0; - dist = 2; - neighbor = origin.translated(dir, dist); - correct = "(11,3)"; - LogManager.getLogger().info(String.format(FARFORMAT, origin.toFriendlyString(), dist, dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(10, 4); - dir = 3; - dist = 5; - neighbor = origin.translated(dir, dist); - correct = "(11,10)"; - LogManager.getLogger().info(String.format(FARFORMAT, origin.toFriendlyString(), dist, dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(10, 4); - dir = 4; - dist = 3; - neighbor = origin.translated(dir, dist); - correct = "(8,6)"; - LogManager.getLogger().info(String.format(FARFORMAT, origin.toFriendlyString(), dist, dir, correct, neighbor.toFriendlyString())); - - origin = new Coords(10, 4); // displays as 11,5 - correct = "(11, 4); (12, 4); (12, 5); (11, 6); (10, 5); (10, 4)"; - LogManager.getLogger().info("All neighbors of " + origin.toFriendlyString() + ": "); - for (int di: Coords.ALL_DIRECTIONS) { - System.out.print(origin.translated(di).toFriendlyString()+"; "); - } - LogManager.getLogger().info("\nShould be: "); - LogManager.getLogger().info(correct); - - origin = new Coords(13, 6); // displays as 14, 7 - correct = "(14, 4); (17, 6); (17, 9); (14, 10); (11, 9); (11, 6)"; - LogManager.getLogger().info("All distance 3 translated hexes of " + origin.toFriendlyString() + ": "); - for (int di: Coords.ALL_DIRECTIONS) { - System.out.print(origin.translated(di, dist).toFriendlyString()+"; "); - } - LogManager.getLogger().info("\nShould be: " + correct); - - origin = new Coords(13, 6); - Coords dest = new Coords(15, 1); - correct = "6"; - LogManager.getLogger().info(String.format(DISTFORMAT, origin.toFriendlyString(), dest.toFriendlyString(), correct, dest.distance(origin))); - - origin = new Coords(12, 2); - dest = new Coords(9, 2); - correct = "3"; - LogManager.getLogger().info(String.format(DISTFORMAT, origin.toFriendlyString(), dest.toFriendlyString(), correct, dest.distance(origin))); - - Coords testCoords = new Coords(0, 0); - - List resultingCoords = testCoords.allAtDistance(0); - if ((resultingCoords.size() == 1) && resultingCoords.contains(testCoords)) { - LogManager.getLogger().info("AllAtDistance(0) working correctly."); - } else { - LogManager.getLogger().info("AllAtDistance(0) not working correctly."); - } - - // for a radius 1 donut, we expect to see 6 hexes. - resultingCoords = testCoords.allAtDistance(1); - - List expectedCoords = new ArrayList<>(); - expectedCoords.add(new Coords(1, -1)); - expectedCoords.add(new Coords(1, 0)); - expectedCoords.add(new Coords(0, -1)); - expectedCoords.add(new Coords(0, 1)); - expectedCoords.add(new Coords(-1, 0)); - expectedCoords.add(new Coords(-1, -1)); - - boolean iscorrect = resultingCoords.size() == 6; - for (Coords expectedCoord : expectedCoords) { - if (!resultingCoords.contains(expectedCoord)) { - iscorrect = false; - break; - } - } - - if (iscorrect) { - LogManager.getLogger().info("AllAtDistance(1) working correctly."); - } else { - LogManager.getLogger().info("AllAtDistance(1) not working correctly."); - } - - // for a radius 2 donut we expect to see 12 hexes. - resultingCoords = testCoords.allAtDistance(2); - - expectedCoords = new ArrayList<>(); - expectedCoords.add(new Coords(-2, 0)); - expectedCoords.add(new Coords(0, -2)); - expectedCoords.add(new Coords(1, 1)); - expectedCoords.add(new Coords(-2, 1)); - expectedCoords.add(new Coords(1, -2)); - expectedCoords.add(new Coords(-2, -1)); - expectedCoords.add(new Coords(2, 1)); - expectedCoords.add(new Coords(-1, -2)); - expectedCoords.add(new Coords(2, -1)); - expectedCoords.add(new Coords(2, 0)); - expectedCoords.add(new Coords(0, 2)); - expectedCoords.add(new Coords(-1, 1)); - - iscorrect = resultingCoords.size() == 12; - for (Coords expectedCoord : expectedCoords) { - if (!resultingCoords.contains(expectedCoord)) { - iscorrect = false; - break; - } - } - - if (iscorrect) { - LogManager.getLogger().info("AllAtDistance(2) working correctly."); - } else { - LogManager.getLogger().info("AllAtDistance(2) not working correctly."); - } - } -} diff --git a/megamek/unittests/megamek/common/CoordsTest.java b/megamek/unittests/megamek/common/CoordsTest.java new file mode 100644 index 00000000000..60237570e8b --- /dev/null +++ b/megamek/unittests/megamek/common/CoordsTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MegaMek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MegaMek. If not, see . + */ +package megamek.common; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class CoordsTest { + + @Test + public void testTranslated() { + assertEquals(new Coords(0, 0).translated(2), new Coords(1, 0)); + assertEquals(new Coords(1, 0).translated(2), new Coords(2, 1)); + assertEquals(new Coords(2, 1).translated(2), new Coords(3, 1)); + + assertEquals(new Coords(0, 0).translated(3), new Coords(0, 1)); + assertEquals(new Coords(7, -2).translated(5), new Coords(6, -2)); + + assertEquals(new Coords(0, 0).translated(2, 3), new Coords(3, 1)); + assertEquals(new Coords(10, 4).translated(0, 2), new Coords(10, 2)); + assertEquals(new Coords(10, 4).translated(3, 5), new Coords(10, 9)); + assertEquals(new Coords(10, 4).translated(4, 3), new Coords(7, 5)); + } + + @Test + public void testDistance() { + assertEquals(new Coords(13, 6).distance(new Coords(15, 1)), 6); + assertEquals(new Coords(12, 2).distance(new Coords(9, 2)), 3); + } + + @Test + public void testAdjacent() { + assertEquals(new Coords(5, -5).allAtDistance(0).size(), 1); + + final List expectedAdjacent = new ArrayList<>(); + expectedAdjacent.add(new Coords(1, -1)); + expectedAdjacent.add(new Coords(1, 0)); + expectedAdjacent.add(new Coords(0, -1)); + expectedAdjacent.add(new Coords(0, 1)); + expectedAdjacent.add(new Coords(-1, 0)); + expectedAdjacent.add(new Coords(-1, -1)); + assertEquals(new Coords(0, 0).allAdjacent().size(), 6); + new Coords(0, 0).allAdjacent().forEach(coords -> assertTrue(expectedAdjacent.contains(coords))); + + // for a radius 2 donut we expect to see 12 hexes. + final List expectedAtDistance2 = new ArrayList<>(); + expectedAtDistance2.add(new Coords(-2, 0)); + expectedAtDistance2.add(new Coords(0, -2)); + expectedAtDistance2.add(new Coords(1, 1)); + expectedAtDistance2.add(new Coords(-2, 1)); + expectedAtDistance2.add(new Coords(1, -2)); + expectedAtDistance2.add(new Coords(-2, -1)); + expectedAtDistance2.add(new Coords(2, 1)); + expectedAtDistance2.add(new Coords(-1, -2)); + expectedAtDistance2.add(new Coords(2, -1)); + expectedAtDistance2.add(new Coords(2, 0)); + expectedAtDistance2.add(new Coords(0, 2)); + expectedAtDistance2.add(new Coords(-1, 1)); + + assertEquals(new Coords(0, 0).allAtDistance(2).size(), 12); + new Coords(0, 0).allAtDistance(2).forEach(coords -> assertTrue(expectedAtDistance2.contains(coords))); + } + + @Test + public void testHexRow() { + Coords center = new Coords(3, 7); + assertFalse(center.isOnHexRow(-1, new Coords(0, 0))); + assertFalse(center.isOnHexRow(6, new Coords(0, 0))); + assertFalse(center.isOnHexRow(2, center)); + assertFalse(center.isOnHexRow(5, center)); + assertFalse(center.isOnHexRow(2, null)); + + Coords other = new Coords(3, 5); + assertTrue(center.isOnHexRow(0, other)); + assertFalse(center.isOnHexRow(1, other)); + assertFalse(center.isOnHexRow(2, other)); + assertFalse(center.isOnHexRow(3, other)); + assertFalse(center.isOnHexRow(4, other)); + assertFalse(center.isOnHexRow(5, other)); + + other = new Coords(3, -1); + assertTrue(center.isOnHexRow(0, other)); + assertFalse(center.isOnHexRow(1, other)); + assertFalse(center.isOnHexRow(2, other)); + assertFalse(center.isOnHexRow(3, other)); + assertFalse(center.isOnHexRow(4, other)); + assertFalse(center.isOnHexRow(5, other)); + + other = new Coords(4, -1); + assertFalse(center.isOnHexRow(0, other)); + assertFalse(center.isOnHexRow(1, other)); + assertFalse(center.isOnHexRow(2, other)); + assertFalse(center.isOnHexRow(3, other)); + assertFalse(center.isOnHexRow(4, other)); + assertFalse(center.isOnHexRow(5, other)); + + center = new Coords(8, 2); + other = new Coords(8, 8); + assertFalse(center.isOnHexRow(0, other)); + assertFalse(center.isOnHexRow(1, other)); + assertFalse(center.isOnHexRow(2, other)); + assertTrue(center.isOnHexRow(3, other)); + assertFalse(center.isOnHexRow(4, other)); + assertFalse(center.isOnHexRow(5, other)); + + center = new Coords(4, 4); + other = new Coords(8, 2); + assertFalse(center.isOnHexRow(0, other)); + assertTrue(center.isOnHexRow(1, other)); + assertFalse(center.isOnHexRow(2, other)); + assertFalse(center.isOnHexRow(3, other)); + assertFalse(center.isOnHexRow(4, other)); + assertFalse(center.isOnHexRow(5, other)); + + other = new Coords(8, 3); + assertFalse(center.isOnHexRow(0, other)); + assertFalse(center.isOnHexRow(1, other)); + assertFalse(center.isOnHexRow(2, other)); + assertFalse(center.isOnHexRow(3, other)); + assertFalse(center.isOnHexRow(4, other)); + assertFalse(center.isOnHexRow(5, other)); + } +}