[AFR] Implementing dungeon mechanic (ready for review) (#7937)

* added dungeon and dungeon room class

* [AFR] Implemented Tomb of Annihilation

* [AFR] Implemented Shortcut Seeker

* [AFR] Implemented Gloom Stalker

* [AFR] Implemented Nadaar, Selfless Paladin

* added room triggers

* added more venturing code, currently untested

* fixed error

* moved venture into dungeon from player class to game class

* removed unnecessary sourceobject from dungeon

* fixed npe error

* added dungeon completion

* fixed concurrent modification exception

* added logging

* added proper copy methods

* added views

* updated room text generation

* added some missing code

* finished implementing CompletedDungeonCondition

* [AFR] Implemented Ellywick Tumblestrum

* [AFR] Implemented Lost Mine of Phandelver

* added choice dialog for dungeons

* [AFR] Implemented Dungeon of the Mad Mage

* small text fix

* added initial dungeon test

* [AFR] Implemented Cloister Gargoyle

* [AFR] Implemented Dungeon Crawler

* small text change for dungeon rooms

* added more tests

* some simplification to dungeon props

* updated testing helper functions

* added currently failing test for venturing on separate steps and turns

* added tests for dungeon completion

* fixed missing trigger visual and dungeons not persisting through turns

* some text updates

* added rollback test

* added a test for multiple dungeons at once

* added one more condition test
This commit is contained in:
Evan Kranzler 2021-06-29 06:57:43 -04:00 committed by GitHub
parent c6d08ce344
commit bb591dd038
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 2481 additions and 144 deletions

View file

@ -38,10 +38,7 @@ import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.combat.Combat;
import mage.game.command.CommandObject;
import mage.game.command.Commander;
import mage.game.command.Emblem;
import mage.game.command.Plane;
import mage.game.command.*;
import mage.game.events.*;
import mage.game.events.TableEvent.EventType;
import mage.game.mulligan.Mulligan;
@ -355,7 +352,7 @@ public abstract class GameImpl implements Game, Serializable {
if (item.getId().equals(objectId)) {
return item;
}
if (item.getSourceId().equals(objectId) && item instanceof Spell) {
if (item instanceof Spell && item.getSourceId().equals(objectId)) {
return item;
}
}
@ -431,6 +428,59 @@ public abstract class GameImpl implements Game, Serializable {
return null;
}
@Override
public Dungeon getDungeon(UUID objectId) {
return state
.getCommand()
.stream()
.filter(commandObject -> commandObject.getId().equals(objectId))
.filter(Dungeon.class::isInstance)
.map(Dungeon.class::cast)
.findFirst()
.orElse(null);
}
@Override
public Dungeon getPlayerDungeon(UUID playerId) {
return state
.getCommand()
.stream()
.filter(commandObject -> commandObject.isControlledBy(playerId))
.filter(Dungeon.class::isInstance)
.map(Dungeon.class::cast)
.findFirst()
.orElse(null);
}
private void removeDungeon(Dungeon dungeon) {
if (dungeon == null) {
return;
}
Player player = getPlayer(dungeon.getControllerId());
if (player != null) {
informPlayers(player.getLogName() + " has completed " + dungeon.getLogName());
}
state.getCommand().remove(dungeon);
fireEvent(GameEvent.getEvent(
GameEvent.EventType.DUNGEON_COMPLETED, dungeon.getId(), null,
dungeon.getControllerId(), dungeon.getName(), 0
));
}
private Dungeon getOrCreateDungeon(UUID playerId) {
Dungeon dungeon = this.getPlayerDungeon(playerId);
if (dungeon != null && dungeon.hasNextRoom()) {
return dungeon;
}
removeDungeon(dungeon);
return this.addDungeon(Dungeon.selectDungeon(playerId, this), playerId);
}
@Override
public void ventureIntoDungeon(UUID playerId) {
this.getOrCreateDungeon(playerId).moveToNextRoom(playerId, this);
}
@Override
public UUID getOwnerId(UUID objectId) {
return getOwnerId(getObject(objectId));
@ -1658,6 +1708,13 @@ public abstract class GameImpl implements Game, Serializable {
state.addCommandObject(commander);
}
@Override
public Dungeon addDungeon(Dungeon dungeon, UUID playerId) {
dungeon.setControllerId(playerId);
state.addCommandObject(dungeon);
return dungeon;
}
@Override
public void addPermanent(Permanent permanent, int createOrder) {
if (createOrder == 0) {
@ -1902,6 +1959,34 @@ public abstract class GameImpl implements Game, Serializable {
}
}
// If a Dungeon is on its last room and is not the source of any triggered abilities, it is removed
Set<Dungeon> dungeonsToRemove = new HashSet<>();
for (CommandObject commandObject : state.getCommand()) {
if (!(commandObject instanceof Dungeon)) {
continue;
}
Dungeon dungeon = (Dungeon) commandObject;
boolean removeDungeon = !dungeon.hasNextRoom()
&& this.getStack()
.stream()
.filter(DungeonRoom::isRoomTrigger)
.map(StackObject::getSourceId)
.noneMatch(dungeon.getId()::equals)
&& this.state
.getTriggered(dungeon.getControllerId())
.stream()
.filter(DungeonRoom::isRoomTrigger)
.map(Ability::getSourceId)
.noneMatch(dungeon.getId()::equals);
if (removeDungeon) {
dungeonsToRemove.add(dungeon);
}
}
for (Dungeon dungeon : dungeonsToRemove) {
this.removeDungeon(dungeon);
somethingHappened = true;
}
// If a commander is in a graveyard or in exile and that card was put into that zone
// since the last time state-based actions were checked, its owner may put it into the command zone.
// signature spells goes to command zone all the time