Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 206: Weapon Specialist #4510

Merged
merged 5 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions megamek/src/megamek/client/ui/swing/CustomMechDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import megamek.common.util.fileUtils.MegaMekFile;
import megamek.common.verifier.*;
import megamek.common.weapons.bayweapons.ArtilleryBayWeapon;
import megamek.common.weapons.bayweapons.BayWeapon;
import megamek.common.weapons.bayweapons.CapitalMissileBayWeapon;

import javax.swing.*;
Expand All @@ -34,6 +35,7 @@
import java.awt.event.*;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
* A dialog that a player can use to customize his mech before battle.
Expand Down Expand Up @@ -326,27 +328,13 @@ private void addOption(IOption option, GridBagLayout gridbag, GridBagConstraints

if ((OptionsConstants.GUNNERY_WEAPON_SPECIALIST).equals(option.getName())) {
optionComp.addValue(Messages.getString("CustomMechDialog.None"));
TreeSet<String> uniqueWeapons = new TreeSet<>();
for (int i = 0; i < entity.getWeaponList().size(); i++) {
Mounted m = entity.getWeaponList().get(i);
uniqueWeapons.add(m.getName());
}
for (String name : uniqueWeapons) {
optionComp.addValue(name);
}
PilotSPAHelper.weaponSpecialistValidWeaponNames(entity).forEach(optionComp::addValue);
optionComp.setSelected(option.stringValue());
}

if ((OptionsConstants.GUNNERY_SANDBLASTER).equals(option.getName())) {
optionComp.addValue(Messages.getString("CustomMechDialog.None"));
TreeSet<String> uniqueWeapons = new TreeSet<>();
for (int i = 0; i < entity.getWeaponList().size(); i++) {
Mounted m = entity.getWeaponList().get(i);
uniqueWeapons.add(m.getName());
}
for (String name : uniqueWeapons) {
optionComp.addValue(name);
}
PilotSPAHelper.sandblasterValidWeaponNames(entity, gameOptions()).forEach(optionComp::addValue);
optionComp.setSelected(option.stringValue());
}

Expand Down
135 changes: 135 additions & 0 deletions megamek/src/megamek/common/PilotSPAHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2023 - 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 <http://www.gnu.org/licenses/>.
*/
package megamek.common;
SJuliez marked this conversation as resolved.
Show resolved Hide resolved

import megamek.common.annotations.Nullable;
import megamek.common.options.GameOptions;
import megamek.common.options.OptionsConstants;
import megamek.common.weapons.autocannons.ACWeapon;
import megamek.common.weapons.autocannons.LBXACWeapon;
import megamek.common.weapons.autocannons.UACWeapon;
import megamek.common.weapons.bayweapons.BayWeapon;

import java.util.List;
import java.util.stream.Collectors;

/**
* This class contains helper methods for Special Pilot Abilities.
*/
public final class PilotSPAHelper {

/** @return True when the given Mounted equipment is a valid choice for the Weapons Specialist SPA. */
public static boolean isWeaponSpecialistValid(Mounted mounted) {
return isWeaponSpecialistValid(mounted.getType());
}

/** @return True when the given EquipmentType is a valid choice for the Weapons Specialist SPA. */
public static boolean isWeaponSpecialistValid(EquipmentType equipmentType) {
return (equipmentType instanceof WeaponType) && !(equipmentType instanceof BayWeapon)
&& !equipmentType.hasFlag(WeaponType.F_AMS) && !equipmentType.is("Screen Launcher")
&& !equipmentType.hasFlag(WeaponType.F_C3M) && !equipmentType.hasFlag(WeaponType.F_C3MBS)
&& !equipmentType.hasFlag(WeaponType.F_INFANTRY_ATTACK);
}

/**
* Returns a List of distinct (each occuring only once) weapon names of weapons present on the given
* Entity that are valid choices for the Weapon Specialist SPA.
*
* @return A list of weapon names from the given Entity that are valid choices for the Weapon Specialist SPA
*/
public static List<String> weaponSpecialistValidWeaponNames(Entity entity) {
return entity.getTotalWeaponList().stream()
.map(Mounted::getType)
.filter(PilotSPAHelper::isWeaponSpecialistValid)
.map(EquipmentType::getName)
.distinct()
.collect(Collectors.toList());
}

/**
* Returns a List of weapons from those present on the given Entity that are valid choices for the
* Weapon Specialist SPA. Unlike {@link #weaponSpecialistValidWeaponNames(Entity)}, weapons
* appear in this list as often as they are present on the given Entity.
*
* @return A list of weapons from the given Entity that are valid choices for the Weapon Specialist SPA
*/
public static List<Mounted> weaponSpecialistValidWeapons(Entity entity) {
return entity.getTotalWeaponList().stream()
.filter(PilotSPAHelper::isWeaponSpecialistValid)
.collect(Collectors.toList());
}

/**
* Returns true when the given Mounted equipment is a valid choice for the Sandblaster SPA, taking into account
* the given GameOptions, particularly, if TacOps RapidFire Autocannons is in use. When the given GameOptions
* is null, TacOps RapidFire Autocannons is assumed off. When TacOps RapidFire Autocannons is off,
* standard ACs are considered invalid.
*
* @return True when the given EquipmentType is a valid choice for the Sandblaster SPA.
*/
public static boolean isSandblasterValid(Mounted mounted, @Nullable GameOptions options) {
return isSandblasterValid(mounted.getType(), options);
}

/**
* Returns true when the given EquipmentType is a valid choice for the Sandblaster SPA, taking into account
* the given GameOptions, particularly, if TacOps RapidFire Autocannons is in use. When the given GameOptions
* is null, TacOps RapidFire Autocannons is assumed off. When TacOps RapidFire Autocannons is off,
* standard ACs are considered invalid.
*
* @return True when the given EquipmentType is a valid choice for the Sandblaster SPA.
*/
public static boolean isSandblasterValid(EquipmentType equipmentType, @Nullable GameOptions options) {
boolean rapidFireAC = (options != null) && options.booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_RAPID_AC)
&& (equipmentType instanceof ACWeapon);

return (equipmentType instanceof WeaponType)
&& ((equipmentType instanceof UACWeapon) || (equipmentType instanceof LBXACWeapon) || rapidFireAC
|| ((WeaponType) equipmentType).damage == WeaponType.DAMAGE_BY_CLUSTERTABLE);
}

/**
* Returns a List of distinct (each occuring only once) weapon names of weapons present on the given
* Entity that are valid choices for the Sandblaster SPA.
*
* @return A list of weapon names from the given Entity that are valid choices for the Sandblaster SPA
*/
public static List<String> sandblasterValidWeaponNames(Entity entity, @Nullable GameOptions options) {
return entity.getTotalWeaponList().stream()
.filter(mounted -> isSandblasterValid(mounted, options))
.map(Mounted::getName)
.distinct()
.collect(Collectors.toList());
}

/**
* Returns a List of weapons from those present on the given Entity that are valid choices for the
* Sandblaster SPA. Unlike {@link #sandblasterValidWeaponNames(Entity, GameOptions)}, weapons
* appear in this list as often as they are present on the given Entity.
*
* @return A list of weapons from the given Entity that are valid choices for the Sandblaster SPA
*/
public static List<Mounted> sandblasterValidWeapons(Entity entity, @Nullable GameOptions options) {
return entity.getTotalWeaponList().stream()
.filter(mounted -> isSandblasterValid(mounted, options))
.collect(Collectors.toList());
}

private PilotSPAHelper() { }
}
30 changes: 15 additions & 15 deletions megamek/src/megamek/common/actions/WeaponAttackAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@
import megamek.common.weapons.Weapon;
import megamek.common.weapons.artillery.ArtilleryCannonWeapon;
import megamek.common.weapons.artillery.ArtilleryWeapon;
import megamek.common.weapons.bayweapons.LaserBayWeapon;
import megamek.common.weapons.bayweapons.PPCBayWeapon;
import megamek.common.weapons.bayweapons.PulseLaserBayWeapon;
import megamek.common.weapons.bayweapons.ScreenLauncherBayWeapon;
import megamek.common.weapons.bayweapons.*;
import megamek.common.weapons.capitalweapons.CapitalMissileWeapon;
import megamek.common.weapons.gaussrifles.GaussWeapon;
import megamek.common.weapons.gaussrifles.ISHGaussRifle;
Expand Down Expand Up @@ -696,7 +693,7 @@ private static ToHitData toHit(Game game, int attackerId, Targetable target, int
toHit = compileEnvironmentalToHitMods(game, ae, target, wtype, atype, toHit, isArtilleryIndirect);

// Collect the modifiers for the crew/pilot
toHit = compileCrewToHitMods(game, ae, te, toHit, wtype);
toHit = compileCrewToHitMods(game, ae, te, toHit, weapon);

// Collect the modifiers for the attacker's condition/actions
if (ae != null) {
Expand Down Expand Up @@ -3922,11 +3919,10 @@ else if (wtype.getAtClass() == WeaponType.CLASS_LBX_AC) {
* @param ae The Entity making this attack
* @param te The target Entity
* @param toHit The running total ToHitData for this WeaponAttackAction
*
* @param wtype The WeaponType of the weapon being used
* @param weapon The weapon being used (it's type should be WeaponType!)
*
*/
private static ToHitData compileCrewToHitMods(Game game, Entity ae, Entity te, ToHitData toHit, WeaponType wtype) {
private static ToHitData compileCrewToHitMods(Game game, Entity ae, Entity te, ToHitData toHit, Mounted weapon) {

if (ae == null) {
// These checks won't work without a valid attacker
Expand Down Expand Up @@ -3991,6 +3987,8 @@ private static ToHitData compileCrewToHitMods(Game game, Entity ae, Entity te, T
toHit.addModifier(-1, Messages.getString("WeaponAttackAction.Vdni"));
}

WeaponType wtype = ((weapon != null) && (weapon.getType() instanceof WeaponType)) ? (WeaponType) weapon.getType() : null;

if (ae.isConventionalInfantry()) {
// check for cyber eye laser sighting on ranged attacks
if (ae.hasAbility(OptionsConstants.MD_CYBER_IMP_LASER)
Expand Down Expand Up @@ -4018,15 +4016,17 @@ private static ToHitData compileCrewToHitMods(Game game, Entity ae, Entity te, T
}

// Is the pilot a weapon specialist?
if (wtype != null && ae.hasAbility(OptionsConstants.GUNNERY_WEAPON_SPECIALIST, wtype.getName())) {
if (wtype instanceof BayWeapon
&& weapon.getBayWeapons().stream().map(ae::getEquipment)
.allMatch(w -> ae.hasAbility(OptionsConstants.GUNNERY_WEAPON_SPECIALIST, w.getName()))) {
// All weapons in a bay must match the specialization
toHit.addModifier(-2, Messages.getString("WeaponAttackAction.WeaponSpec"));
} else if (wtype != null && ae.hasAbility(OptionsConstants.GUNNERY_WEAPON_SPECIALIST, wtype.getName())) {
toHit.addModifier(-2, Messages.getString("WeaponAttackAction.WeaponSpec"));
} else if (ae.hasAbility(OptionsConstants.GUNNERY_SPECIALIST)) {
// aToW style gunnery specialist: -1 to specialized weapon and +1 to
// all other weapons
// Note that weapon specialist supersedes gunnery specialization, so
// if you have
// a specialization in Medium Lasers and a Laser specialization, you
// only get the -2 specialization mod
// aToW style gunnery specialist: -1 to specialized weapon and +1 to all other weapons
// Note that weapon specialist supersedes gunnery specialization, so if you have
// a specialization in Medium Lasers and a Laser specialization, you only get the -2 specialization mod
if (wtype != null && wtype.hasFlag(WeaponType.F_ENERGY)) {
if (ae.hasAbility(OptionsConstants.GUNNERY_SPECIALIST, Crew.SPECIAL_ENERGY)) {
toHit.addModifier(-1, Messages.getString("WeaponAttackAction.EnergySpec"));
Expand Down
2 changes: 1 addition & 1 deletion megamek/src/megamek/common/options/Option.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public String getName() {
public String getDisplayableNameWithValue() {
updateInfo();
return info.getDisplayableName()
+ (type == IOption.INTEGER ? " " + value : "");
+ ((type == IOption.INTEGER) || (type == IOption.CHOICE) ? " [" + value + "]" : "");
}

@Override
Expand Down