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

Feat/acar formations reintroduced #6407

Merged
merged 7 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
17 changes: 9 additions & 8 deletions megamek/i18n/megamek/client/acs-report-messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
################################################################################
acar.header=<h1>Abstract Combat Auto Resolution</h1>
acar.header.startingScenario=<h2>\u25ce Starting Scenario Phase</h2>
acar.header.teamFormations=Team {0} Formations:
acar.startingScenario.numberOfUnits={0} has {1} units.
acar.header.teamFormationsHeader=Team {0} Formations
acar.header.teamFormations={0} has {1} formations
acar.startingScenario.formation.numberOfUnits={0} has {1} units.
acar.startingScenario.unit.numberOfElements={0} has {1} elements.
acar.startingScenario.unitStats={0}, Armor: {1}, Structure: {2}, Crew: {3}, Hits: {4}
acar.shortBlurb=short blurb

Expand Down Expand Up @@ -99,14 +101,13 @@ acar.firingPhase.noInternalDamage=Took no internal damage.
## End Phase
################################################################################
acar.endPhase.header=<h2>\u25cf End Phase</h2>
acar.endPhase.destroyedHeader=<h3>Destroyed Units</h3>
acar.endPhase.withdrawAnnouncement=[Withdraw] {0} attempts to withdraw under {1} conditions.
acar.endPhase.withdrawThreshold=[Withdraw] Needs {0}+ to successfully withdraw <i>\u27e8{1}\u27e9</i>.
acar.endPhase.withdrawRoll=[Withdraw] {0} rolled {1} for withdrawal.
acar.endPhase.withdrawAnnouncement=[Withdraw] {0} will withdraw. Reason: {1}
acar.endPhase.withdrawSuccess=[Withdraw] The {0} has withdrawn from the battlefield.
acar.endPhase.withdrawFail=[Withdraw] The withdrawal attempt fails.
acar.endPhase.withdrawMotive.crippled=crippled
acar.endPhase.withdrawMotive.metOrderCondition=met order condition
acar.endPhase.crewDeath=[Crew] {0} has succumbed from his wounds \u2620
acar.endPhase.crewAlive=[Crew] {0} is still alive ({1} hits) \u263a
acar.endPhase.unitDestroyed=[Destroyed] {0} was destroyed, lost elements: {1}.
acar.endPhase.devastated=[Devastated] {0} has been devastated, there is nothing left of it.
acar.endPhase.destroyedPilot=[Destroyed] {0} was destroyed by the pilot ejection.
acar.endPhase.destroyedOffBoard=[Destroyed] {0} was destroyed after being pushed off the combat envelope.
Expand All @@ -132,7 +133,7 @@ acar.morale.checkFail=[Morale] {0} fails its morale check! Morale worsens from {
## End of Combat
################################################################################
acar.endOfCombat.header=<h2>\u25d9 End of Combat</h2>
acar.endOfCombat.teamReportHeader=<h3>Team {0} Report:</h3>
acar.endOfCombat.teamReportHeader=<h2>Team {0} Report:</h2>
acar.endOfCombat.teamRemainingUnits=<h3>{0} has {1} units remaining.</h3>
acar.endOfCombat.teamUnitStats={0}, Armor remaining: {1}, Structure remaining: {2}, Crew: {3}, Hits: {4}
acar.endOfCombat.teamUnitStatsShort={0} - Armor remaining: {1}, Structure remaining: {2}
Expand Down
2 changes: 2 additions & 0 deletions megamek/i18n/megamek/common/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,8 @@ AutoResolveDialog.messageScenarioPlayer=Player {0} won the scenario.
AutoResolveDialog.messageScenarioDraw=The scenario ended in a draw.
AutoResolveDialog.message.victory=Your forces won the scenario. Did your side control the battlefield at the end of the scenario?
AutoResolveDialog.message.defeat=Your forces lost the scenario. Do you want to declare your side as controlling the battlefield at the end of the scenario?
AutoResolveDialog.messageScenarioError.text=There was an error during the execution of the simulation. If sentry isn't enabled open an issue with the developers.
AutoResolveDialog.messageScenarioError.title=Error during the simulation
AutoResolveDialog.victory=Victory!
AutoResolveDialog.defeat=Defeat!
ResolveDialog.control.title=Control of Battlefield?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,19 @@ public Integer doInBackground() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
var result = simulateScenario();
if (result == null) {
JOptionPane.showMessageDialog(
getFrame(),
Internationalization.getText("AutoResolveDialog.messageScenarioError.text"),
Internationalization.getText("AutoResolveDialog.messageScenarioError.title"),
JOptionPane.INFORMATION_MESSAGE);
return -1;
}
dialog.setEvent(result);
stopWatch.stop();

var messageKey = (result.getVictoryResult().getWinningTeam() != Entity.NONE) ? "AutoResolveDialog.messageScenarioTeam" : "AutoResolveDialog.messageScenarioPlayer";
messageKey = (result.getVictoryResult().getWinningTeam() == 0 && result.getVictoryResult().getWinningPlayer() == 0) ? "AutoResolveDialog.messageScenarioDraw" : messageKey;
messageKey = (result.getVictoryResult().getWinningTeam() == 0 && result.getVictoryResult().getWinningPlayer() == -1) ? "AutoResolveDialog.messageScenarioDraw" : messageKey;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should probably be a constant.

var message = Internationalization.getFormattedText(messageKey,
result.getVictoryResult().getWinningTeam(),
result.getVictoryResult().getWinningPlayer());
Expand Down Expand Up @@ -231,11 +239,17 @@ private AutoResolveConcludedEvent simulateScenario() {
return null;
}));
futures.add(executor.submit(() -> {
var result = Resolver.simulationRun(
setupForces, SimulationOptions.empty(), new Board(board.getWidth(), board.getHeight()))
.resolveSimulation();
countDownLatch.countDown();
return result;
try {
return Resolver.simulationRun(
setupForces, SimulationOptions.empty(), new Board(board.getWidth(), board.getHeight()))
.resolveSimulation();
} catch (Exception e) {
logger.error(e, e);
} finally {
countDownLatch.countDown();
}

return null;
}));

// Wait for all tasks to complete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@
package megamek.client.ui.dialogs.helpDialogs;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;

import megamek.client.ui.Messages;
import megamek.client.ui.baseComponents.AbstractDialog;
Expand Down Expand Up @@ -77,6 +82,35 @@ protected Container createCenterPane() {
}
}
});


// Add mouse motion listener to show tooltips for links.
pane.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
int pos = pane.viewToModel2D(e.getPoint());
if (pos >= 0 && pane.getDocument() instanceof HTMLDocument doc) {
var elem = doc.getCharacterElement(pos);
if (elem != null) {
// The Element’s attributes may point us to a <SPAN> tag
var attrs = elem.getAttributes();
Object attrsAttribute = attrs.getAttribute(HTML.Tag.A);

if (attrsAttribute instanceof AttributeSet nAttrs) {
// Try retrieving your custom data-value attribute.
// "data-value" isn’t part of the standard HTML.Attribute enum,
// so we can use HTML.getAttributeKey("data-value").
String dataValue = (String) nAttrs.getAttribute(HTML.getAttributeKey("data-value"));

if (dataValue != null) {
// We found our custom attribute, so show it in the tooltip
pane.setToolTipText(dataValue);
}
}
}
}
}
});
scrollPane.getVerticalScrollBar().setUnitIncrement(16);

final File helpFile = new File(getHelpFilePath());
Expand Down
8 changes: 6 additions & 2 deletions megamek/src/megamek/common/BattleArmor.java
Original file line number Diff line number Diff line change
Expand Up @@ -1250,8 +1250,12 @@ public int getRandomTrooper() {
activeTroops.add(loop);
}
}
int locInt = Compute.randomInt(activeTroops.size());
return activeTroops.elementAt(locInt);
if (!activeTroops.isEmpty()) {
int locInt = Compute.randomInt(activeTroops.size());
return activeTroops.elementAt(locInt);
Scoppio marked this conversation as resolved.
Show resolved Hide resolved
} else {
return -1;
}
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion megamek/src/megamek/common/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2661,7 +2661,7 @@ private String createDisplayName(int duplicateMarker) {
StringBuilder builder = new StringBuilder();
builder.append(createShortName(duplicateMarker));

if (getOwner() != null) {
if (getOwner() != null && getOwner().getName() != null && !getOwner().getName().isBlank()) {
builder.append(" (").append(getOwner().getName()).append(")");
}

Expand Down
3 changes: 1 addition & 2 deletions megamek/src/megamek/common/autoresolve/Resolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ public AutoResolveConcludedEvent resolveSimulation() {
SimulationContext context = new SimulationContext(options, setupForces, board);
SimulationManager simulationManager = new SimulationManager(context, suppressLog);
initializeGameManager(simulationManager);
simulationManager.execute();
return simulationManager.getConclusionEvent();
return simulationManager.execute();
}

}
19 changes: 10 additions & 9 deletions megamek/src/megamek/common/autoresolve/acar/SimulationContext.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
/*
* Copyright (c) 2025 - The MegaMek Team. All Rights Reserved.
*
* 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 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.
*
* 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.common.autoresolve.acar;

Expand Down Expand Up @@ -375,7 +376,7 @@ public List<TriggeredEvent> scriptedEvents() {
}

public boolean gameTimerIsExpired() {
return getRoundCount() >= 1000;
return getRoundCount() >= 20;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should definitely be a constant, or a member, or something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I forgot about that

}

private int getRoundCount() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public class SimulationManager extends AbstractGameManager {
private final List<PhaseHandler> phaseHandlers = new ArrayList<>();
private final PhaseEndManager phaseEndManager;
private final PhasePreparationManager phasePreparationManager;
private final ActionsProcessor actionsProcessor;
private final InitiativeHelper initiativeHelper;
private final ActionsProcessor actionsProcessor;
private final InitiativeHelper initiativeHelper;
private final VictoryHelper victoryHelper;
private final SimulationContext simulationContext;
private final boolean suppressLog;
Expand All @@ -62,12 +62,12 @@ public SimulationManager(SimulationContext simulationContext, boolean suppressLo
this.victoryHelper = new VictoryHelper(this);
}

public void execute() {
public AutoResolveConcludedEvent execute() {
changePhase(GamePhase.STARTING_SCENARIO);
while (!simulationContext.getPhase().equals(GamePhase.VICTORY)) {
changePhase(GamePhase.INITIATIVE);
}

return getConclusionEvent();
}

public void addPhaseHandler(PhaseHandler phaseHandler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private void resolveAttack(Formation attacker, StandardUnitAttack attack, Format
}

// Start of attack report
reporter.reportAttackStart(attacker, attack.getUnitNumber(), target);
reporter.reportAttackStart(attacker, attack.getUnitNumber(), target, targetUnit);

if (toHit.cannotSucceed()) {
reporter.reportCannotSucceed(toHit.getDesc());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public void execute() {

var withdrawFormation = withdrawOpt.get();
if (!withdrawFormation.isWithdrawing()) {
if (withdrawFormation.isCrippled()) {
reporter.reportStartingWithdrawForCrippled(withdrawFormation);
} else {
reporter.reportStartingWithdrawForOrder(withdrawFormation);
}
withdrawFormation.setWithdrawing(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,19 @@ private void forgetEverything() {

private void destroyUnits(Formation formation, List<SBFUnit> destroyedUnits) {
for (var unit : destroyedUnits) {
if (!formation.isSingleEntity()) {
reporter.reportUnitDestroyed(formation, unit);
}
for (var element : unit.getElements()) {
var entityOpt = getContext().getEntity(element.getId());
if (entityOpt.isPresent()) {
var entity = entityOpt.get();
var removalConditionTable = entity.isEjectionPossible() ?
REMOVAL_CONDITIONS_TABLE : REMOVAL_CONDITIONS_TABLE_NO_EJECTION;
entity.setRemovalCondition(removalConditionTable.randomItem());

reporter.reportUnitDestroyed(entity);
if (formation.isSingleEntity()) {
reporter.reportElementDestroyed(formation, unit, entity);
}
getContext().addUnitToGraveyard(entity);
getContext().applyDamageToEntityFromUnit(
unit, entity, EntityFinalState.fromEntityRemovalState(entity.getRemovalCondition()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
package megamek.common.autoresolve.acar.phase;

import megamek.common.Compute;
import megamek.common.alphaStrike.ASRange;
import megamek.common.autoresolve.acar.SimulationManager;
import megamek.common.autoresolve.acar.action.Action;
Expand Down Expand Up @@ -78,6 +79,7 @@ private void standardUnitAttacks(AttackRecord attackRecord) {
var attack = new StandardUnitAttack(actingFormation.getId(), unitIndex, target.getId(), range);
attacks.add(attack);
}
target.addBeingTargetedBy(actingFormation);
getSimulationManager().addAttack(attacks, actingFormation);
}
}
Expand All @@ -97,6 +99,7 @@ private List<AttackRecord> attack(Formation actingFormation) {
}

var ret = new ArrayList<AttackRecord>();

ret.add(new AttackRecord(actingFormation, target.get(0), unitIds));
return ret;
}
Expand Down Expand Up @@ -183,9 +186,13 @@ private List<Formation> bestTargetOrPreviousTarget(Formation actingFormation, Se

Collections.shuffle(pickTarget);
priorityTarget.ifPresent(formation -> pickTarget.add(0, formation));
var iterator = targets.iterator();
if (iterator.hasNext()) {

var iterator = pickTarget.iterator();
while (iterator.hasNext()) {
var target = iterator.next();
if (target.beingTargetByHowMany() > 2 && iterator.hasNext()) {
continue;
}
return List.of(target);
}
return Collections.emptyList();
Expand Down
Loading
Loading