Skip to content

Commit

Permalink
Merge pull request #4984 from HoneySkull/building_warning
Browse files Browse the repository at this point in the history
Fixes #1031 - Show structure collapse warning when deploying and moving.
  • Loading branch information
neoancient authored Dec 30, 2023
2 parents 3449f53 + d1c42ae commit ff1719b
Show file tree
Hide file tree
Showing 10 changed files with 809 additions and 2 deletions.
2 changes: 2 additions & 0 deletions megamek/i18n/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,8 @@ CommonMenuBar.viewToggleSensorRange=Visual & Sensor Ranges
CommonMenuBar.viewToggleSensorRangeToolTip=Outline Visual & Sensor Ranges
CommonMenuBar.viewToggleFiringSolutions=Firing Solutions
CommonMenuBar.viewToggleFiringSolutionsToolTip=During the firing phase, to-hit modifiers for each range bracket will be drawn on units
CommonMenuBar.viewToggleCFWarning=Structure Collapse Warning
CommonMenuBar.viewToggleCFWarningToolTip=Show buildings or bridges that could collapse if entered by selected unit
CommonMenuBar.viewChangeTheme=Change Tileset Theme
CommonMenuBar.viewResetWindowPos=Reset Window Positions
CommonMenuBar.moveLAMmechMode=Transform to mek mode
Expand Down
4 changes: 4 additions & 0 deletions megamek/src/megamek/client/ui/swing/ClientGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public class ClientGUI extends JPanel implements BoardViewListener,
public static final String VIEW_TOGGLE_FOV_DARKEN = "viewToggleFovDarken";
public static final String VIEW_TOGGLE_FOV_HIGHLIGHT = "viewToggleFovHighlight";
public static final String VIEW_TOGGLE_FIRING_SOLUTIONS = "viewToggleFiringSolutions";
public static final String VIEW_TOGGLE_CF_WARNING = "viewToggleCFWarnings";
public static final String VIEW_MOVE_ENV = "viewMovementEnvelope";
public static final String VIEW_TURN_DETAILS_OVERLAY = "viewTurnDetailsOverlay";
public static final String VIEW_MOVE_MOD_ENV = "viewMovModEnvelope";
Expand Down Expand Up @@ -934,6 +935,9 @@ public void actionPerformed(ActionEvent event) {
}
bv.refreshDisplayables();
break;
case VIEW_TOGGLE_CF_WARNING:
ConstructionFactorWarning.handleActionPerformed();
break;
case VIEW_MOVE_ENV:
if (curPanel instanceof MovementDisplay) {
GUIP.setMoveEnvelope(!GUIP.getMoveEnvelope());
Expand Down
6 changes: 6 additions & 0 deletions megamek/src/megamek/client/ui/swing/CommonMenuBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public class CommonMenuBar extends JMenuBar implements ActionListener, IPreferen
private JCheckBoxMenuItem toggleFovHighlight = new JCheckBoxMenuItem(getString("CommonMenuBar.viewToggleFovHighlight"));
private JCheckBoxMenuItem toggleFovDarken = new JCheckBoxMenuItem(getString("CommonMenuBar.viewToggleFovDarken"));
private JCheckBoxMenuItem toggleFiringSolutions = new JCheckBoxMenuItem(getString("CommonMenuBar.viewToggleFiringSolutions"));
private JCheckBoxMenuItem toggleCFWarning = new JCheckBoxMenuItem(getString("CommonMenuBar.viewToggleCFWarning"));
private JCheckBoxMenuItem viewMovementEnvelope = new JCheckBoxMenuItem(getString("CommonMenuBar.movementEnvelope"));
private JCheckBoxMenuItem viewTurnDetailsOverlay = new JCheckBoxMenuItem(getString("CommonMenuBar.turnDetailsOverlay"));
private JMenuItem viewMovModEnvelope = new JMenuItem(getString("CommonMenuBar.movementModEnvelope"));
Expand Down Expand Up @@ -305,6 +306,10 @@ public CommonMenuBar() {
toggleFiringSolutions.setToolTipText(Messages.getString("CommonMenuBar.viewToggleFiringSolutionsToolTip"));
toggleFiringSolutions.setSelected(GUIP.getFiringSolutions());

initMenuItem(toggleCFWarning, menu, VIEW_TOGGLE_CF_WARNING);
toggleCFWarning.setToolTipText(Messages.getString("CommonMenuBar.viewToggleCFWarningToolTip"));
toggleCFWarning.setSelected(GUIP.getShowCFWarnings());

/* TODO: moveTraitor = createMenuItem(menu, getString("CommonMenuBar.moveTraitor"), MovementDisplay.MOVE_TRAITOR); */

// Create the Help menu
Expand Down Expand Up @@ -479,6 +484,7 @@ private synchronized void updateEnabledStates() {
toggleFovHighlight.setEnabled(isInGameBoardView);
toggleFovDarken.setEnabled(isInGameBoardView);
toggleFiringSolutions.setEnabled(isInGameBoardView);
toggleCFWarning.setEnabled(isInGameBoardView);
viewMovementEnvelope.setEnabled(isInGameBoardView);
viewTurnDetailsOverlay.setEnabled(isInGameBoardView);
viewMovModEnvelope.setEnabled(isInGameBoardView);
Expand Down
203 changes: 203 additions & 0 deletions megamek/src/megamek/client/ui/swing/ConstructionFactorWarning.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* 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.client.ui.swing;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.apache.logging.log4j.LogManager;

import megamek.common.Board;
import megamek.common.Building;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.Game;
import megamek.common.enums.GamePhase;

/**
* Construction Factor Warning Logic. Handles events, help
* methods and logic related to CF Warning in a way that
* can be unit tested and encapsulated from BoardView and
* ClientGUI and other actors.
*/
public class ConstructionFactorWarning {
/*
* Handler for ClientGUI actionPerformed event. Encapsulates
* as much Construction Factory Warning logic possible.
*/
public static void handleActionPerformed() {
toggleCFWarning();
}

/*
* Return true if the passed in phase is a phase that should allow
* Construction Factor Warnings such as Deploy and Movement.
*/
public static boolean isCFWarningPhase(GamePhase gp) {
return (gp == GamePhase.DEPLOYMENT || gp == GamePhase.MOVEMENT);
}

public static boolean shouldShow(GamePhase gp) {
return shouldShow(gp, true);
}

/*
* Returns true if the show construction factor warning preference
* is enabled and in a phase that should show warnings.
*/
public static boolean shouldShow(GamePhase gp, boolean isEnabled) {
return (isEnabled && isCFWarningPhase(gp));
}

private static boolean toggleCFWarning() {
//Toggle the GUI Preference setting for CF Warning setting.
GUIPreferences GUIP = GUIPreferences.getInstance();
GUIP.setShowCFWarnings(!GUIP.getShowCFWarnings());
return (GUIP.getShowCFWarnings());
}

/**
* For the provided entity, find all hexes within movement range with
* buildings that would collapse if entered or landed upon. This is
* used by the {@link MovementDisplay} class.
*
* @param g {@link Game} provided by the phase display class
* @param e {@link Entity} currently selected in the movement phase.
* @param b {@link Board} board object with building data.
*
* @return returns a list of {@link Coords} that where warning flags
* should be placed.
*/
public static List<Coords> findCFWarningsMovement(Game g, Entity e, Board b) {
List<Coords> warnList = new ArrayList<Coords>();

try {
//Only calculate CF Warnings for entity types in the whitelist.
if (!entityTypeIsInWhiteList(e)) {
return warnList;
}

Coords pos = e.getPosition();
int range = Math.max(e.getJumpMP(), e.getRunMP());

List<Coords> hexesToCheck = new ArrayList<Coords>();
if (pos != null) {
hexesToCheck = pos.allAtDistanceOrLess(range + 1);
} else {
return hexesToCheck;
}

// For each hex in jumping range, look for buildings, if found check for collapse.
for (Coords c : hexesToCheck) {
//is there a building at this location? If so add it to hexes with buildings.
Building bld = b.getBuildingAt(c);

// If a building, compare total weight and add to warning list.
if (null != bld) {
if (calculateTotalTonnage(g, e, c) > bld.getCurrentCF(c)) {
warnList.add(c);
}
}
}
} catch (Exception exc) {
// Something bad is going to happen. This is a passive feature return an empty list.
LogManager.getLogger().error("Unable to calculate construction factor collapse candidates. (Movement)");
return new ArrayList<Coords>();
}

return warnList;
}

/*
* Returns true if the selected entity should have CF warnings calculated when selected.
*/
protected static boolean entityTypeIsInWhiteList(Entity e) {
// Include entities that are ground units and onboard only. Flying units need not apply.
return (e.isGround() && !e.isOffBoard());
}

/**
* Looks for all building locations in a legal deploy zone that would collapse
* if the currently selected entity would deploy there. This is used by
* {@link DeploymentDisplay} to render a warning sprite on danger hexes.
*
* @param g {@link Game} provided by the phase display class
* @param e {@link Entity} currently selected in the movement phase.
* @param b {@link Board} board object with building data.
*
* @return returns a list of {@link Coords} that where warning flags
* should be placed.
*/
public static List<Coords> findCFWarningsDeployment(Game g, Entity e, Board b) {
List<Coords> warnList = new ArrayList<Coords>();

try {
//Only calculate CF Warnings for entity types in the whitelist.
if (!entityTypeIsInWhiteList(e)) {
return warnList;
}

Enumeration<Building> buildings = b.getBuildings();

// Enumerate through all the buildings
while (buildings.hasMoreElements()) {
Building bld = buildings.nextElement();
List<Coords> buildingList = bld.getCoordsList();

// For each hex occupied by the building, check if it's a legal deploy hex.
for (Coords c : buildingList) {
if (b.isLegalDeployment(c, e)) {
// Check for weight limits for collapse and add a warning sprite.
if (calculateTotalTonnage(g, e, c) > bld.getCurrentCF(c)) {
warnList.add(c);
}
}
}
}
} catch (Exception exc) {
// Something bad is going to happen. This is a passive feature return an empty list.
LogManager.getLogger().error("Unable to calculate construction factor collapse candidates. (Deployment)");
return new ArrayList<Coords>();
}

return warnList;
}

/*
* Determine the total weight burden for a building hex at a location.
* This includes the entity current weight summed with any unit weights
* at the hex location that could cause a building to collapse.
*/
protected static double calculateTotalTonnage(Game g, Entity selected, Coords c) {
// Calculate total weight of entity and all entities at the location.
double totalWeight = selected.getWeight();
List<Entity> units = g.getEntitiesVector(c, true);
for (Entity ent : units) {
if (ConstructionFactorWarning.isEntityPartOfWeight(selected, ent)) {
totalWeight += ent.getWeight();
}
}
return totalWeight;
}

protected static boolean isEntityPartOfWeight(Entity selected, Entity inHex) {
return ((selected != inHex) && inHex.isGround() && !inHex.isAirborneVTOLorWIGE());
}
}
12 changes: 12 additions & 0 deletions megamek/src/megamek/client/ui/swing/DeploymentDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ public void selectEntity(int en) {
clientgui.getUnitDisplay().showPanel("movement");
clientgui.getBoardView().setWeaponFieldOfFire(ce().getFacing(), ce().getPosition());
clientgui.getBoardView().setSensorRange(ce(), ce().getPosition());
computeCFWarningHexes(ce());
} else {
disableButtons();
setNextEnabled(true);
Expand All @@ -239,6 +240,17 @@ public void selectEntity(int en) {
}
}

private void computeCFWarningHexes(Entity ce) {
List<Coords> warnList =
ConstructionFactorWarning.findCFWarningsDeployment(
clientgui.getBoardView().game,
ce,
clientgui.getBoardView().game.getBoard());

clientgui.getBoardView().setCFWarningSprites(warnList);

}

/** Enables relevant buttons and sets up for your turn. */
private void beginMyTurn() {
clientgui.maybeShowUnitDisplay();
Expand Down
12 changes: 11 additions & 1 deletion megamek/src/megamek/client/ui/swing/GUIPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ public class GUIPreferences extends PreferenceStoreProxy {
public static final String TEAM_COLORING = "EntityTeamLabelColor";
public static final String FOCUS = "Focus";
public static final String FIRING_SOLUTIONS = "FiringSolutions";
public static final String CONSTRUCTOR_FACTOR_WARNING = "ConstructionFactorWarning";
public static final String MOVE_ENVELOPE = "MoveEnvelope";
public static final String TURN_DETAILS_OVERLAY = "TurnDetailsOverlay";
public static final String FOV_HIGHLIGHT = "FovHighlight";
Expand Down Expand Up @@ -601,6 +602,7 @@ protected GUIPreferences() {
store.setDefault(UNIT_LABEL_BORDER, true);
store.setDefault(UNIT_LABEL_STYLE, LabelDisplayStyle.NICKNAME.name());
store.setDefault(FIRING_SOLUTIONS, true);
store.setDefault(CONSTRUCTOR_FACTOR_WARNING, true);
store.setDefault(GUI_SCALE, 1);
store.setDefault(LOBBY_MEKTABLE_UNIT_WIDTH, 170);
store.setDefault(LOBBY_MEKTABLE_PILOT_WIDTH, 80);
Expand Down Expand Up @@ -1002,6 +1004,10 @@ public boolean getFiringSolutions() {
return store.getBoolean(FIRING_SOLUTIONS);
}

public boolean getShowCFWarnings() {
return store.getBoolean(CONSTRUCTOR_FACTOR_WARNING);
}

public boolean getMoveEnvelope() {
return store.getBoolean(MOVE_ENVELOPE);
}
Expand Down Expand Up @@ -1784,6 +1790,10 @@ public void setFiringSolutions(boolean state) {
store.setValue(FIRING_SOLUTIONS, state);
}

public void setShowCFWarnings(boolean b) {
store.setValue(CONSTRUCTOR_FACTOR_WARNING, b);
}

public void setMoveEnvelope(boolean state) {
store.setValue(MOVE_ENVELOPE, state);
}
Expand Down Expand Up @@ -3142,4 +3152,4 @@ protected Color parseRGB(String text) {
return Color.BLUE;
}
//endregion Colours
}
}
11 changes: 11 additions & 0 deletions megamek/src/megamek/client/ui/swing/MovementDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@ public synchronized void selectEntity(int en) {
}
clientgui.getBoardView().clearFieldOfFire();
computeMovementEnvelope(ce);
computeCFWarningHexes(ce);
}

private MegamekButton getBtn(MoveCommand c) {
Expand Down Expand Up @@ -4544,6 +4545,16 @@ && ce().getMovementMode() == EntityMovementMode.TRACKED) {
clientgui.getBoardView().setMovementModifierEnvelope(lpf.getLongestComputedPaths());
}

private void computeCFWarningHexes(Entity ce) {
List<Coords> warnList =
ConstructionFactorWarning.findCFWarningsMovement(
clientgui.getBoardView().game,
ce,
clientgui.getBoardView().game.getBoard());

clientgui.getBoardView().setCFWarningSprites(warnList);
}

//
// ActionListener
//
Expand Down
Loading

0 comments on commit ff1719b

Please sign in to comment.