Skip to content

Commit f6289a2

Browse files
authored
Adventure: QOL improvements for "Clear All" quests (#10244)
* Add enemy counter feature to GameHUD and localization support for remaining enemies when in a dungeon with an active "clear all" quest. * Add setting to draw chevrons for hidden enemies in clear all quests. Gate this behavior behind a new "Draw chevrons pointing to hidden enemies in a clear all quest" flag in settings. * Fix casing of "Clear All" quests
1 parent f6ec1c9 commit f6289a2

File tree

14 files changed

+172
-0
lines changed

14 files changed

+172
-0
lines changed

forge-gui-mobile/src/forge/adventure/data/SettingData.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ public class SettingData {
2727
public boolean excludeAlchemyVariants;
2828
public boolean generateLDADecks;
2929
public boolean bindEquipmentLoadoutsToDecks;
30+
public boolean drawChevronsToHiddenEnemiesInClearQuest;
3031
}

forge-gui-mobile/src/forge/adventure/scene/SettingsScene.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,13 @@ public void changed(ChangeEvent event, Actor actor) {
267267
Config.instance().saveSettings();
268268
}
269269
});
270+
addSettingField(Forge.getLocalizer().getMessage("lblDrawChevronsToHiddenEnemiesInClearQuest"), Config.instance().getSettingData().drawChevronsToHiddenEnemiesInClearQuest, new ChangeListener() {
271+
@Override
272+
public void changed(ChangeEvent event, Actor actor) {
273+
Config.instance().getSettingData().drawChevronsToHiddenEnemiesInClearQuest = ((CheckBox) actor).isChecked();
274+
Config.instance().saveSettings();
275+
}
276+
});
270277
CheckBox cbAnte = addCheckBox(Forge.getLocalizer().getMessage("cbAnte"), ForgePreferences.FPref.UI_ANTE);
271278
CheckBox cbAnteMatchRarity = addCheckBox(Forge.getLocalizer().getMessage("cbAnteMatchRarity"), ForgePreferences.FPref.UI_ANTE_MATCH_RARITY);
272279
CheckBox cbAnteIncludeBasicLands = addCheckBox(Forge.getLocalizer().getMessage("cbAnteIncludeBasicLands"), ForgePreferences.FPref.UI_ANTE_INCLUDE_BASIC_LANDS);

forge-gui-mobile/src/forge/adventure/stage/GameHUD.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.badlogic.gdx.graphics.Pixmap;
77
import com.badlogic.gdx.graphics.Texture;
88
import com.badlogic.gdx.math.Vector2;
9+
import com.badlogic.gdx.math.Vector3;
910
import com.badlogic.gdx.scenes.scene2d.Action;
1011
import com.badlogic.gdx.scenes.scene2d.Actor;
1112
import com.badlogic.gdx.scenes.scene2d.Group;
@@ -18,6 +19,7 @@
1819
import com.badlogic.gdx.scenes.scene2d.ui.Dialog;
1920
import com.badlogic.gdx.scenes.scene2d.ui.Image;
2021
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
22+
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane.ScrollPaneStyle;
2123
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad;
2224
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
2325
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
@@ -34,6 +36,7 @@
3436
import java.util.EnumSet;
3537

3638
import forge.Forge;
39+
import forge.adventure.character.EnemySprite;
3740
import forge.adventure.character.CharacterSprite;
3841
import forge.adventure.data.AdventureQuestData;
3942
import forge.adventure.data.ItemData;
@@ -50,6 +53,7 @@
5053
import forge.adventure.util.Controls;
5154
import forge.adventure.util.Current;
5255
import forge.adventure.util.KeyBinding;
56+
import forge.adventure.util.NavArrowActor;
5357
import forge.adventure.util.UIActor;
5458
import forge.adventure.world.WorldSave;
5559
import forge.deck.Deck;
@@ -69,6 +73,8 @@ public class GameHUD extends Stage {
6973
private final TypingLabel lifePoints;
7074
private final TypingLabel money;
7175
private final TypingLabel shards;
76+
private final TypingLabel enemyCounterText;
77+
private final Image enemyCounterBackground;
7278
private final TextraLabel notificationText = Controls.newTextraLabel("");
7379
private final Image miniMap, gamehud, mapborder, avatarborder, blank;
7480
private final InputEvent eventTouchDown, eventTouchUp;
@@ -84,6 +90,9 @@ public class GameHUD extends Stage {
8490
private boolean dialogOnlyInput;
8591
private final Array<TextraButton> dialogButtonMap = new Array<>();
8692
private final Array<TextraButton> abilityButtonMap = new Array<>();
93+
private final Array<NavArrowActor> hiddenEnemyChevrons = new Array<>();
94+
private final Vector2 chevronStageCoordinates = new Vector2();
95+
private final Vector3 playerProjectedCoordinates = new Vector3();
8796
private String lifepointsTextColor = "";
8897
private final ScrollPane notificationPane;
8998
private final Group mapGroup = new Group();
@@ -159,6 +168,14 @@ public void changed(ChangeEvent changeEvent, Actor actor) {
159168
shards.setText("[%95][+Shards]");
160169
money.setText("[%95][+Gold]");
161170
lifePoints.setText("[%95][+Life]");
171+
enemyCounterText = Controls.newTypingLabel(Forge.getLocalizer().getMessage("lblRemainingEnemies", String.valueOf(0)));
172+
enemyCounterText.setColor(Color.BLACK);
173+
enemyCounterText.skipToTheEnd();
174+
enemyCounterText.setVisible(false);
175+
176+
ScrollPaneStyle paperStyle = Controls.getSkin().get("paper", ScrollPaneStyle.class);
177+
enemyCounterBackground = new Image(paperStyle.background);
178+
enemyCounterBackground.setVisible(false);
162179
AdventurePlayer.current().onLifeChange(() -> {
163180
String effect = "{EMERGE}";
164181
String effectEnd = "{ENDEMERGE}";
@@ -183,6 +200,8 @@ public void changed(ChangeEvent changeEvent, Actor actor) {
183200
AdventurePlayer.current().onEquipmentChanged(this::updateAbility);
184201
addActor(ui);
185202
addActor(miniMapPlayer);
203+
addActor(enemyCounterBackground);
204+
addActor(enemyCounterText);
186205
console = new Console();
187206
console.setBounds(0, GuiBase.isAndroid() ? getHeight() : 0, getWidth(), getHeight() / 2);
188207
console.setVisible(false);
@@ -338,6 +357,7 @@ public void draw() {
338357
int yPos = (int) gameStage.player.getY();
339358
int xPos = (int) gameStage.player.getX();
340359
act(Gdx.graphics.getDeltaTime()); //act the Hud
360+
updateHiddenEnemyChevrons();
341361
super.draw(); //draw the Hud
342362
int xPosMini = (int) (((float) xPos / (float) WorldSave.getCurrentSave().getWorld().getTileSize() / (float) WorldSave.getCurrentSave().getWorld().getWidthInTiles()) * miniMap.getWidth());
343363
int yPosMini = (int) (((float) yPos / (float) WorldSave.getCurrentSave().getWorld().getTileSize() / (float) WorldSave.getCurrentSave().getWorld().getHeightInTiles()) * miniMap.getHeight());
@@ -416,6 +436,7 @@ public void enter() {
416436
}
417437
if (MapStage.getInstance().isInMap())
418438
updateBookmarkActor(MapStage.getInstance().getChanges().isBookmarked());
439+
updateEnemyCounter();
419440
avatarGroup.setZIndex(ui.getChildren().size);
420441
}
421442

@@ -430,6 +451,100 @@ void updateKeys() {
430451
keyCollection.setText(keys);
431452
}
432453

454+
public void updateEnemyCounter() {
455+
if (MapStage.getInstance().isInMap() && AdventureQuestController.instance().hasClearQuestActive()) {
456+
int remaining = MapStage.getInstance().getRemainingEnemyCount();
457+
enemyCounterText.setText(Forge.getLocalizer().getMessage("lblRemainingEnemies", String.valueOf(remaining)));
458+
enemyCounterText.setVisible(true);
459+
enemyCounterBackground.setVisible(true);
460+
461+
float paddingX = 4f;
462+
float paddingY = 1.5f;
463+
float margin = 2f;
464+
float textWidth = enemyCounterText.getPrefWidth();
465+
float textHeight = Math.max(enemyCounterText.getPrefHeight(), 12f);
466+
float bgWidth = textWidth + paddingX * 2f;
467+
float bgHeight = textHeight + paddingY * 2f;
468+
469+
float bgX = getWidth() - bgWidth - margin;
470+
float bgY = margin;
471+
472+
enemyCounterBackground.setBounds(bgX, bgY, bgWidth, bgHeight);
473+
enemyCounterText.setBounds(bgX + paddingX, bgY + paddingY, textWidth, textHeight);
474+
} else {
475+
enemyCounterText.setVisible(false);
476+
enemyCounterBackground.setVisible(false);
477+
}
478+
}
479+
480+
private void hideHiddenEnemyChevrons() {
481+
for (NavArrowActor chevron : hiddenEnemyChevrons) {
482+
chevron.setVisible(false);
483+
}
484+
}
485+
486+
private NavArrowActor getChevronActor(int index) {
487+
while (hiddenEnemyChevrons.size <= index) {
488+
NavArrowActor chevron = new NavArrowActor();
489+
chevron.setTouchable(Touchable.disabled);
490+
chevron.setVisible(false);
491+
hiddenEnemyChevrons.add(chevron);
492+
addActor(chevron);
493+
}
494+
return hiddenEnemyChevrons.get(index);
495+
}
496+
497+
private void positionChevron(NavArrowActor chevron, EnemySprite enemy, int index) {
498+
playerProjectedCoordinates.set(MapStage.getInstance().player.getX() + MapStage.getInstance().player.getWidth() * 0.5f,
499+
MapStage.getInstance().player.getY() + MapStage.getInstance().player.getHeight() * 0.5f, 0f);
500+
MapStage.getInstance().getCamera().project(playerProjectedCoordinates, 0f, 0f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
501+
chevronStageCoordinates.set(playerProjectedCoordinates.x, playerProjectedCoordinates.y);
502+
screenToStageCoordinates(chevronStageCoordinates);
503+
504+
Vector2 navDirection = new Vector2(enemy.pos()).sub(MapStage.getInstance().player.pos());
505+
if (navDirection.isZero(0.01f)) {
506+
navDirection.set(1f, 0f);
507+
} else {
508+
navDirection.nor();
509+
}
510+
511+
Vector2 side = new Vector2(-navDirection.y, navDirection.x);
512+
int lane = index / 2;
513+
float spread = (index % 2 == 0 ? 1f : -1f) * lane * 9f;
514+
float offsetFromPlayer = 18f;
515+
float pointerX = chevronStageCoordinates.x + navDirection.x * offsetFromPlayer + side.x * spread;
516+
float pointerY = chevronStageCoordinates.y + navDirection.y * offsetFromPlayer + side.y * spread;
517+
518+
chevron.navTargetAngle = navDirection.angleDeg();
519+
chevron.setPosition(pointerX, pointerY);
520+
chevron.setVisible(true);
521+
}
522+
523+
private void updateHiddenEnemyChevrons() {
524+
if (!Config.instance().getSettingData().drawChevronsToHiddenEnemiesInClearQuest
525+
|| hidden
526+
|| !MapStage.getInstance().isInMap()
527+
|| !AdventureQuestController.instance().hasClearQuestActive()
528+
|| MapStage.getInstance().getRemainingEnemyCount() <= 0) {
529+
hideHiddenEnemyChevrons();
530+
return;
531+
}
532+
533+
int chevronIndex = 0;
534+
for (EnemySprite enemy : MapStage.getInstance().enemies) {
535+
if (!enemy.hidden || enemy.getStage() == null || enemy.defeatDialog != null) {
536+
continue;
537+
}
538+
NavArrowActor chevron = getChevronActor(chevronIndex);
539+
positionChevron(chevron, enemy, chevronIndex);
540+
chevronIndex++;
541+
}
542+
543+
while (chevronIndex < hiddenEnemyChevrons.size) {
544+
hiddenEnemyChevrons.get(chevronIndex++).setVisible(false);
545+
}
546+
}
547+
433548
void clearAbility() {
434549
for (TextraButton button : abilityButtonMap) {
435550
button.remove();

forge-gui-mobile/src/forge/adventure/stage/MapStage.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,16 @@ public EnemySprite getEnemyByID(int id) { //Search actor by ID, enemies only.
916916
return null;
917917
}
918918

919+
public int getRemainingEnemyCount() {
920+
int count = 0;
921+
for (EnemySprite enemy : enemies) {
922+
if (enemy.getStage() != null && enemy.defeatDialog == null) {
923+
count++;
924+
}
925+
}
926+
return count;
927+
}
928+
919929
public Actor getByID(int id) { //Search actor by ID.
920930
for (MapActor A : new Array.ArrayIterator<>(actors)) {
921931
if (A.getId() == id)

forge-gui-mobile/src/forge/adventure/util/AdventureQuestController.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import forge.adventure.data.*;
1212
import forge.adventure.pointofintrest.PointOfInterest;
1313
import forge.adventure.pointofintrest.PointOfInterestChanges;
14+
import forge.adventure.scene.TileMapScene;
1415
import forge.adventure.stage.GameStage;
1516
import forge.adventure.stage.MapStage;
1617
import forge.adventure.world.WorldSave;
@@ -285,6 +286,22 @@ public static void clear(){
285286
object = null;
286287
}
287288

289+
public boolean hasClearQuestActive() {
290+
if (!MapStage.getInstance().isInMap() || TileMapScene.instance().rootPoint == null) {
291+
return false;
292+
}
293+
for (AdventureQuestData quest : Current.player().getQuests()) {
294+
for (AdventureQuestStage stage : quest.stages) {
295+
if (stage.getStatus() == ACTIVE
296+
&& stage.objective == ObjectiveTypes.Clear
297+
&& stage.checkIfTargetLocation(TileMapScene.instance().rootPoint)) {
298+
return true;
299+
}
300+
}
301+
}
302+
return false;
303+
}
304+
288305
private AdventureQuestController(){
289306

290307
}

forge-gui-mobile/src/forge/adventure/util/NavArrowActor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.badlogic.gdx.graphics.g2d.TextureRegion;
77
import com.badlogic.gdx.scenes.scene2d.Actor;
88
import com.badlogic.gdx.utils.Array;
9+
import com.badlogic.gdx.graphics.Color;
910

1011
public class NavArrowActor extends Actor {
1112

@@ -38,7 +39,12 @@ public void draw(Batch batch, float parentAlpha) {
3839
setHeight(currentFrame.getRegionHeight());
3940
setWidth(currentFrame.getRegionWidth());
4041

42+
Color oldColor = batch.getColor();
43+
Color actorColor = getColor();
44+
batch.setColor(actorColor.r, actorColor.g, actorColor.b, actorColor.a * parentAlpha);
45+
4146
//TODO: Simplify params somehow for readability? All this does is spin the image around the player.
4247
batch.draw(currentFrame, getX() - currentFrame.getRegionWidth() / 2, getY() - currentFrame.getRegionHeight() / 2, (currentFrame.getRegionWidth() * 0.5f), (currentFrame.getRegionHeight() * 0.5f), currentFrame.getRegionWidth(), currentFrame.getRegionHeight(), 1, 1, navTargetAngle);
48+
batch.setColor(oldColor);
4349
}
4450
}

forge-gui/res/languages/de-DE.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,7 +3097,9 @@ lblUseAllCardVariants=Use Card Variants from All Sets (Restart Required)
30973097
lblExcludeAlchemyVariants=Exclude variants rebalanced for Arena's Alchemy and Historic formats
30983098
lblGenerateLDADecks=Generate Archetype Decks instead of Genetic AI Decks
30993099
lblBindEquipmentLoadoutsToDecks=Ausrüstung an Decks binden
3100+
lblDrawChevronsToHiddenEnemiesInClearQuest=Draw chevrons pointing to hidden enemies in a Clear All quest
31003101
lblExitToWoldMap=Zurück zur Weltkarte?
3102+
lblRemainingEnemies=Remaining Enemies: {0}
31013103
lblStartArena=Willst du in die Arena gehen?
31023104
lblWouldYouLikeDestroy=Möchten Sie {0} zerstören?
31033105
lblAdventureDescription=Im Abenteuermodus erkunden Sie die sich ständig ändernde Landschaft von Shandalar und Duell-Kreaturen; um Gold, Scherben, Ausrüstung und neue Karten zu gewinnen, sie alle zu sammeln und der Beste zu werden!

forge-gui/res/languages/en-US.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3246,7 +3246,9 @@ lblUseAllCardVariants=Use Card Variants from All Sets (Restart Required)
32463246
lblExcludeAlchemyVariants=Exclude variants rebalanced for Arena's Alchemy and Historic formats
32473247
lblGenerateLDADecks=Generate Archetype Decks instead of Genetic AI Decks
32483248
lblBindEquipmentLoadoutsToDecks=Bind equipment loadouts to decks
3249+
lblDrawChevronsToHiddenEnemiesInClearQuest=Draw chevrons pointing to hidden enemies in a Clear All quest
32493250
lblExitToWoldMap=Exit to the World Map?
3251+
lblRemainingEnemies=Remaining Enemies: {0}
32503252
lblStartArena=Do you want to go into the Arena?
32513253
lblWouldYouLikeDestroy=Would you like to destroy {0}?
32523254
lblAdventureDescription=Adventure mode is where you explore the ever-changing landscape of Shandalar, and duel creatures to gain gold, shards, equipment and new cards to become the best and collect them all!

forge-gui/res/languages/es-ES.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3085,7 +3085,9 @@ lblUseAllCardVariants=Use Card Variants from All Sets (Restart Required)
30853085
lblExcludeAlchemyVariants=Exclude variants rebalanced for Arena's Alchemy and Historic formats
30863086
lblGenerateLDADecks=Generate Archetype Decks instead of Genetic AI Decks
30873087
lblBindEquipmentLoadoutsToDecks=Vincular equipamiento a mazos
3088+
lblDrawChevronsToHiddenEnemiesInClearQuest=Draw chevrons pointing to hidden enemies in a Clear All quest
30883089
lblExitToWoldMap=Salir al mapa del mundo?
3090+
lblRemainingEnemies=Remaining Enemies: {0}
30893091
lblStartArena=¿Quieres ir a la arena?
30903092
lblWouldYouLikeDestroy=¿Le gustaría destruir {0}?
30913093
lblAdventureDescription=¡El modo de aventura es donde exploras el paisaje siempre cambiante de Shandalar, y criaturas de duelo para ganar oro, fragmentos, equipos y nuevas tarjetas para convertirte en el mejor y recogerlas a todos!

forge-gui/res/languages/fr-FR.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3079,7 +3079,9 @@ lblUseAllCardVariants=Use Card Variants from All Sets (Restart Required)
30793079
lblExcludeAlchemyVariants=Exclude variants rebalanced for Arena's Alchemy and Historic formats
30803080
lblGenerateLDADecks=Generate Archetype Decks instead of Genetic AI Decks
30813081
lblBindEquipmentLoadoutsToDecks=Lier l'équipement aux decks
3082+
lblDrawChevronsToHiddenEnemiesInClearQuest=Draw chevrons pointing to hidden enemies in a Clear All quest
30823083
lblExitToWoldMap=Sortir sur la carte du monde?
3084+
lblRemainingEnemies=Remaining Enemies: {0}
30833085
lblStartArena=Voulez-vous entrer dans l'arène?
30843086
lblWouldYouLikeDestroy=Souhaitez-vous détruire {0}?
30853087
lblAdventureDescription=Le mode aventure est l'endroit où vous explorez le paysage en constante évolution de Shandalar, et des créatures duel pour gagner de l'or, des éclats, de l'équipement et de nouvelles cartes pour devenir les meilleurs et les récupérer tous!

0 commit comments

Comments
 (0)