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 1 commit
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).forEach(optionComp::addValue);
optionComp.setSelected(option.stringValue());
}

Expand Down
95 changes: 95 additions & 0 deletions megamek/src/megamek/common/PilotSPAHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package megamek.common;
SJuliez marked this conversation as resolved.
Show resolved Hide resolved

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");
}

/**
* 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());
}

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

/** @return True when the given EquipmentType is a valid choice for the Sandblaster SPA. */
public static boolean isSandblasterValid(EquipmentType equipmentType) {
return (equipmentType instanceof WeaponType)
&& ((equipmentType instanceof UACWeapon) || (equipmentType instanceof LBXACWeapon)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment here, which is that the "rapid fire autocannons" weapon allows standard ACs to behave like UACs (at the risk of exploding), so I think standard autocannons should be included in this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a suggestion, I have added a GameOptions parameter here based on which all ACs may be allowed. Both MM and MHQ can supply GameOptions and so it'll reflect the state of the game/campaign. The GameOptions can be nulled if necessary but I have not added versions without the additional parameter as we'd really want the GameOpts to be given. I think MML won't need these methods.

Aside from that I've removed Streaks from Sandblaster as they don't use the cluster table and C3 from the weapon specialist (hehe).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NickAragua Any thoughts?

Copy link
Member

@NickAragua NickAragua Jun 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, lost track of this one.

I think there are some situations (mostly involving high powered ECM as I recall) where Streak launchers revert to cluster rolls, so there may be a case for Sandblaster applying to them.

Tying sandblaster availability to "rapid fire AC" rules is ok.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've re-allowed Streaks for those Angel ECM encounters and removed infantry attacks (leg attack etc) from Weapon Specialist (doesn't seem in the spirit of the rules)

|| ((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) {
return entity.getTotalWeaponList().stream()
.map(Mounted::getType)
.filter(PilotSPAHelper::isSandblasterValid)
.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
* Sandblaster SPA. Unlike {@link #sandblasterValidWeaponNames(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 Sandblaster SPA
*/
public static List<Mounted> sandblasterValidWeapons(Entity entity) {
return entity.getTotalWeaponList().stream()
.filter(PilotSPAHelper::isSandblasterValid)
.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