package mhfc.net.common.quests;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
import mhfc.net.MHFCMain;
import mhfc.net.common.core.registry.MHFCExplorationRegistry;
import mhfc.net.common.core.registry.MHFCQuestRegistry;
import mhfc.net.common.network.PacketPipeline;
import mhfc.net.common.network.message.quest.MessageMissionStatus;
import mhfc.net.common.network.message.quest.MessageMissionUpdate;
import mhfc.net.common.quests.api.QuestDefinition;
import mhfc.net.common.quests.api.QuestGoal;
import mhfc.net.common.quests.api.QuestGoalSocket;
import mhfc.net.common.quests.properties.GroupProperty;
import mhfc.net.common.quests.world.IQuestAreaSpawnController;
import mhfc.net.common.quests.world.QuestFlair;
import mhfc.net.common.util.PlayerMap;
import mhfc.net.common.util.StagedFuture;
import mhfc.net.common.world.area.IActiveArea;
import mhfc.net.common.world.area.IAreaType;
import mhfc.net.common.world.exploration.IExplorationManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.ChatComponentText;
public class Mission implements QuestGoalSocket, AutoCloseable {
public static final String KEY_TYPE_RUNNING = "running";
enum QuestState {
pending,
running,
finished,
resigned;
}
private static class QuestingPlayerState {
public EntityPlayerMP player;
public boolean vote;
@SuppressWarnings("unused")
public boolean restoreInventory;
@SuppressWarnings("unused")
public boolean reward;
public IExplorationManager previousManager;
public QuestingPlayerState(EntityPlayerMP p, boolean vote, boolean restoreInventory, boolean reward) {
this.player = p;
this.restoreInventory = restoreInventory;
this.vote = vote;
this.reward = reward;
this.previousManager = MHFCExplorationRegistry.getExplorationManagerFor(p);
}
}
private static QuestingPlayerState newAttribute(EntityPlayerMP player) {
return new QuestingPlayerState(player, false, true, false);
}
private final String missionID;
private QuestDefinition originalDescription;
private PlayerMap<QuestingPlayerState> playerAttributes;
private int maxPlayerCount;
protected QuestState state;
protected QuestGoal questGoal;
protected GroupProperty rootGoalProperties;
/**
* Not set before the {@link StagedFuture} from that the area is retrieved from is complete.
*/
protected IActiveArea questingArea;
protected QuestExplorationManager explorationManager;
protected int reward;
protected int fee;
private boolean closed;
public Mission(
String missionID,
QuestGoal goal,
GroupProperty goalProperties,
int maxPartySize,
int reward,
int fee,
CompletionStage<IActiveArea> activeArea,
QuestDefinition originalDescription) {
this.missionID = Objects.requireNonNull(missionID);
this.playerAttributes = new PlayerMap<>();
this.questGoal = Objects.requireNonNull(goal);
this.rootGoalProperties = Objects.requireNonNull(goalProperties);
activeArea.thenAccept(this::onAreaFinished);
goal.setSocket(this);
this.reward = reward;
this.fee = fee;
this.state = QuestState.pending;
this.originalDescription = originalDescription;
this.maxPlayerCount = maxPartySize;
this.closed = false;
}
public void updateCheck() {
updatePlayers();
}
public QuestState getState() {
return state;
}
public QuestGoal getQuestGoal() {
return questGoal;
}
public int getReward() {
return reward;
}
public int getFee() {
return fee;
}
@Override
public void questGoalStatusNotification(QuestGoal goal, EnumSet<QuestStatus> newStatus) {
if (newStatus.contains(QuestStatus.Fulfilled)) {
onSuccess();
}
if (newStatus.contains(QuestStatus.Failed)) {
onFail();
}
updatePlayers();
}
protected void onAreaFinished(IActiveArea area) {
this.questingArea = Objects.requireNonNull(area);
tryStart();
}
protected boolean canStart() {
return allVotes() && this.questingArea != null;
}
private void tryStart() {
if (canStart()) {
onStart();
resetVotes();
}
}
protected void onFail() {
for (EntityPlayerMP player : getPlayers()) {
player.addChatMessage(new ChatComponentText("You have failed a quest"));
}
// TODO do special stuff for fail
onEnd();
}
protected void onSuccess() {
for (QuestingPlayerState attribute : playerAttributes.values()) {
attribute.reward = true;
attribute.player.addExperienceLevel(10);
attribute.player.addChatMessage(new ChatComponentText("You have successfully completed a quest"));
}
this.state = QuestState.finished;
// TODO reward the players for finishing the quest with dynamic rewards
onEnd();
}
protected void onStart() {
explorationManager = new QuestExplorationManager(getQuestFlair(), getQuestingArea(), this);
questGoal.setActive(true);
this.state = QuestState.running;
for (EntityPlayerMP player : getPlayers()) {
MHFCExplorationRegistry.bindPlayer(explorationManager, player);
explorationManager.respawn(player);
// AreaTeleportation.movePlayerToArea(player, questingArea.getArea());
}
updatePlayers();
resetVotes();
}
private void resetVotes() {
for (QuestingPlayerState attribute : playerAttributes.values()) {
attribute.vote = false;
}
}
/**
* This method should be called whenever the quest ends, no matter how.
*/
protected void onEnd() {
for (EntityPlayerMP player : getPlayers()) {
removePlayer(player);
}
MHFCQuestRegistry.getRegistry().endMission(this);
MHFCMain.logger().info("Mission {} ended", getMission());
}
protected void updatePlayers() {
MessageMissionUpdate update = MessageMissionUpdate.createUpdate(missionID, rootGoalProperties);
if (update == null) {
return;
}
for (QuestingPlayerState attribute : playerAttributes.values()) {
EntityPlayerMP player = attribute.player;
PacketPipeline.networkPipe.sendTo(update, player);
}
// MHFCQuestRegistry.questUpdated(update);
}
protected void updatePlayerInitial(EntityPlayerMP player) {
// TODO: add player to the quest
PacketPipeline.networkPipe.sendTo(MessageMissionStatus.joining(missionID), player);
PacketPipeline.networkPipe.sendTo(createFullUpdateMessage(), player);
}
public boolean canJoin(EntityPlayer player) {
// TODO add more evaluation and/or move to another class?
boolean isPending = state == QuestState.pending;
boolean notFull = playerCount() < maxPlayerCount;
boolean playerHasNoQuest = MHFCQuestRegistry.getRegistry().getQuestForPlayer(player) == null;
return isPending && notFull && playerHasNoQuest;
}
private int playerCount() {
return playerAttributes.size();
}
@Override
public void reset() {
questGoal.reset();
}
@Override
public Mission getMission() {
return this;
}
private void addPlayer(EntityPlayerMP player) {
playerAttributes.putPlayer(player, Mission.newAttribute(player));
MHFCQuestRegistry.getRegistry().setQuestForPlayer(player, this);
updatePlayerInitial(player);
updatePlayers();
}
private boolean removePlayer(EntityPlayerMP player) {
QuestingPlayerState att = playerAttributes.removePlayer(player);
if (att != null) {
PacketPipeline.networkPipe.sendTo(MessageMissionStatus.departing(missionID), att.player);
MHFCQuestRegistry.getRegistry().setQuestForPlayer(att.player, null);
MHFCExplorationRegistry.bindPlayer(att.previousManager, player);
MHFCExplorationRegistry.respawnPlayer(player);
return true;
}
return false;
}
public boolean joinPlayer(EntityPlayerMP player) {
if (!canJoin(player)) {
return false;
}
addPlayer(player);
return true;
}
public boolean quitPlayer(EntityPlayerMP player) {
boolean found = removePlayer(player);
if (playerCount() == 0) {
onEnd();
}
return found;
}
public Set<EntityPlayerMP> getPlayers() {
return playerAttributes.values().stream().map(t -> t.player).collect(Collectors.toSet());
}
/**
* Utility method to provide the spawn controller. Might also introduce an indirection if the publicly available
* controller should behave differently.
*/
public IQuestAreaSpawnController getSpawnController() {
return questingArea.getArea().getSpawnController();
}
public QuestDefinition getOriginalDescription() {
return originalDescription;
}
private QuestingPlayerState getPlayerAttributes(EntityPlayerMP player) {
return playerAttributes.getPlayer(player);
}
public void voteStart(EntityPlayerMP player) {
QuestingPlayerState attributes = getPlayerAttributes(player);
attributes.vote = true;
tryStart();
}
private boolean allVotes() {
return playerAttributes.values().stream().map((x) -> x.vote).reduce(Boolean::logicalAnd).orElse(true);
}
public void voteEnd(EntityPlayerMP player) {
QuestingPlayerState attributes = getPlayerAttributes(player);
attributes.vote = true;
boolean end = allVotes();
if (end && state == QuestState.running) {
onEnd();
resetVotes();
}
}
public int getMaxPartySize() {
return maxPlayerCount;
}
public IAreaType getAreaType() {
return questingArea.getType();
}
public IActiveArea getQuestingArea() {
return questingArea;
}
public QuestFlair getQuestFlair() {
return originalDescription.getQuestFlair();
}
@Override
public void close() {
if (closed) {
MHFCMain.logger().debug("Tried to close already closed instance of mission {}", missionID);
return;
}
questGoal.questGoalFinalize();
questingArea.close();
closed = true;
}
@Override
protected void finalize() throws Throwable {
if (!closed) {
close();
}
}
/**
* Searches for the player with a matching entity id and updates all references
*/
public boolean updatePlayerEntity(EntityPlayerMP player) {
return playerAttributes.computeIfPresent(PlayerMap.playerToKey(player), (k, a) -> {
a.player = player;
return a;
}) != null;
}
/**
* Creates a packet suitable to initialize the properties of a mission based on the current state.
*
* @return
*/
public MessageMissionUpdate createFullUpdateMessage() {
return MessageMissionUpdate.createFullDump(missionID, rootGoalProperties);
}
}