package joshie.progression.player; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import joshie.progression.Progression; import joshie.progression.api.criteria.*; import joshie.progression.api.special.IStoreTriggerData; import joshie.progression.handlers.APICache; import joshie.progression.handlers.RemappingHandler; import joshie.progression.helpers.CollectionHelper; import joshie.progression.helpers.NBTHelper; import joshie.progression.helpers.PlayerHelper; import joshie.progression.json.Options; import joshie.progression.network.*; import joshie.progression.player.nbt.*; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraftforge.fml.common.eventhandler.Event.Result; import org.apache.logging.log4j.Level; import java.util.*; import java.util.concurrent.Callable; import static joshie.progression.Progression.data; public class CriteriaMappings { private PlayerDataServer master; private UUID uuid; protected HashMap<ICriteria, Integer> completedCritera = new HashMap(); //Criteria -> Counter protected Set<ITriggerProvider> completedTriggers = new HashSet(); //Completed Triggers protected HashMultimap<UUID, IRewardProvider> unclaimedRewards = HashMultimap.create(); //User -> Unclaimed Rewards protected HashMap<UUID, HashMap<ICriteria, Integer>> numberRewards = new HashMap(); //Users -> Reward Counter protected HashMap<UUID, IStoreTriggerData> triggerDataMap = new HashMap(); // Trigger ID -> Trigger Data protected Set<ICriteria> impossible = new HashSet(); // List of Impossible Criteria protected Set<Callable> todo = new HashSet<Callable>(); //List of Stuff to do //Generated by the remapping protected Multimap<String, ITriggerProvider> activeTriggers; //List of all the active triggers, based on their trigger type //Sets the uuid associated with this class public void setMaster(PlayerDataServer master) { this.master = master; this.uuid = master.getUUID(); } public void syncToClient(EntityPlayerMP player) { //remap(); //Remap the data, before the client gets sent the data PacketHandler.sendToClient(new PacketSyncTeam(master.getTeam()), player); PacketHandler.sendToClient(new PacketSyncAbilities(master.getAbilities()), player); PacketHandler.sendToClient(new PacketSyncCustomData(master.getCustomStats()), player); PacketHandler.sendToClient(new PacketSyncPoints(master.getPoints()), player); PacketHandler.sendToClient(new PacketSyncImpossible(impossible.toArray(new ICriteria[impossible.size()])), player); PacketHandler.sendToClient(new PacketSyncTriggers(completedTriggers), player); //Sync all trigger that are completed to the client PacketHandler.sendToClient(new PacketSyncTriggerData(triggerDataMap), player); //Sync all trigger data PacketHandler.sendToClient(new PacketSyncUnclaimed(unclaimedRewards), player); //Sync all unclaimed PacketHandler.sendToClient(new PacketSyncCriteria(true, completedCritera.values().toArray(new Integer[completedCritera.size()]), completedCritera.keySet().toArray(new ICriteria[completedCritera.size()])), player); //Sync all conditions to the client PacketHandler.sendToClient(new PacketLockUnlockSaving(false), player); //Allow players to edit the book } private void readTriggerData(NBTTagCompound nbt) { if (Options.debugMode) Progression.logger.log(Level.INFO, "Reading the nbt data for uuid " + uuid); NBTTagList data = nbt.getTagList("ActiveTriggerData", 10); for (int i = 0; i < data.tagCount(); i++) { NBTTagCompound tag = data.getCompoundTagAt(i); UUID uuid = UUID.fromString(tag.getString("UUID")); NBTTagCompound triggerNBT = tag.getCompoundTag("Data"); if (triggerDataMap.get(uuid) == null) { ITriggerProvider trigger = APICache.getCache(false).getTriggerFromUUID(uuid); if (trigger != null) { IStoreTriggerData triggerData = (IStoreTriggerData)trigger.copy().getProvided(); triggerData.readDataFromNBT(triggerNBT); triggerDataMap.put(uuid, triggerData); } } else triggerDataMap.get(uuid).readDataFromNBT(tag); } } //Reads the completed criteria public void readFromNBT(NBTTagCompound nbt) { NBTHelper.readTagCollection(nbt, "ImpossibleCriteria", CriteriaSet.INSTANCE.setCollection(impossible)); NBTHelper.readTagCollection(nbt, "CompletedTriggers", TriggerNBT.INSTANCE.setCollection(completedTriggers)); NBTHelper.readMultimap(nbt, "UnclaimedRewards", UnclaimedNBT.INSTANCE.setMap(unclaimedRewards)); NBTHelper.readMap(nbt, "CompletedCriteria", CriteriaNBT.INSTANCE.setMap(completedCritera)); NBTHelper.readMap(nbt, "MemberRewardCounter", RewardCountNBT.INSTANCE.setMap(numberRewards)); readTriggerData(nbt); } private void writeTriggerData(NBTTagCompound nbt) { //Save the extra data for the existing triggers NBTTagList data = new NBTTagList(); for (UUID uuid : triggerDataMap.keySet()) { NBTTagCompound tag = new NBTTagCompound(); tag.setString("UUID", uuid.toString()); NBTTagCompound triggerNBT = new NBTTagCompound(); triggerDataMap.get(uuid).writeDataToNBT(triggerNBT); tag.setTag("Data", triggerNBT); data.appendTag(tag); } nbt.setTag("ActiveTriggerData", data); } public NBTTagCompound writeToNBT(NBTTagCompound nbt) { NBTHelper.writeCollection(nbt, "ImpossibleCriteria", CriteriaSet.INSTANCE.setCollection(impossible)); NBTHelper.writeCollection(nbt, "CompletedTriggers", TriggerNBT.INSTANCE.setCollection(completedTriggers)); NBTHelper.writeMultimap(nbt, "UnclaimedRewards", UnclaimedNBT.INSTANCE.setMap(unclaimedRewards)); NBTHelper.writeMap(nbt, "CompletedCriteria", CriteriaNBT.INSTANCE.setMap(completedCritera)); NBTHelper.writeMap(nbt, "MemberRewardCounter", RewardCountNBT.INSTANCE.setMap(numberRewards)); writeTriggerData(nbt); return nbt; } public HashMap<ICriteria, Integer> getCompletedCriteria() { return completedCritera; } public Set<ITriggerProvider> getCompletedTriggers() { return completedTriggers; } public void markCriteriaAsCompleted(boolean overwrite, Integer[] values, ICriteria... conditions) { if (overwrite) completedCritera = new HashMap(); for (int i = 0; i < values.length; i++) { if (values[i] == 0) { completedCritera.remove(conditions[i]); } else completedCritera.put(conditions[i], values[i]); } } public void markTriggerAsCompleted(boolean overwrite, Set<ITriggerProvider> triggers) { if (overwrite) completedTriggers = new HashSet(); completedTriggers.addAll(triggers); } public void setTriggerData(boolean overwrite, PacketSyncTriggerData.DataPair[] pairs) { if (overwrite) triggerDataMap = new HashMap(); for (PacketSyncTriggerData.DataPair pair: pairs) { ITriggerProvider trigger = APICache.getCache(true).getTriggerFromUUID(pair.uuid); if (trigger != null) { ((IStoreTriggerData)trigger.getProvided()).readDataFromNBT(pair.data); //Fuck with local cache } } } public void setUnclaimedRewards(boolean remove, boolean overwrite, PacketSyncUnclaimed.UnclaimedPair[] pairs) { if (overwrite) unclaimedRewards = HashMultimap.create(); for (PacketSyncUnclaimed.UnclaimedPair pair: pairs) { if (pair == null) continue; for (UUID uuid: pair.rewardUUIDs) { IRewardProvider reward = APICache.getCache(true).getRewardFromUUID(uuid); if (reward != null) { if (!remove) unclaimedRewards.get(pair.uuid).add(reward); else unclaimedRewards.get(pair.uuid).remove(reward); } } } } private boolean containsAny(List<ICriteria> list) { for (ICriteria criteria : list) { if (completedCritera.keySet().contains(criteria)) return true; } return false; } public void sendTriggerDataToClient(ITriggerProvider trigger) { if (trigger.getProvided() instanceof IStoreTriggerData) { sendTriggerDataToClient(trigger.getUniqueID(), (IStoreTriggerData) trigger.getProvided()); } } public void sendTriggerDataToClient(UUID uuid, IStoreTriggerData trigger) { NBTTagCompound nbt = new NBTTagCompound(); trigger.writeDataToNBT(nbt); PacketHandler.sendToTeam(new PacketSyncTriggerData(uuid, nbt), master.team); } public boolean isImpossible(ICriteria criteria) { return impossible.contains(criteria); } public void setImpossibles(ICriteria... criteria) { for (ICriteria c : criteria) { impossible.add(c); } } public void switchPossibility(ICriteria criteria) { boolean isPossible = !isImpossible(criteria); if (isPossible) impossible.add(criteria); else CollectionHelper.remove(impossible, criteria); data.markDirty(); PacketHandler.sendToTeam(new PacketSyncImpossible(impossible.toArray(new ICriteria[impossible.size()])), master.team); } public Result forceRemoval(ICriteria criteria) { if (criteria == null || !completedCritera.keySet().contains(criteria)) return Result.DEFAULT; else removeCriteria(criteria); remap(); //Remap everything data.markDirty(); return Result.ALLOW; } public Result forceComplete(ICriteria criteria) { HashSet<ICriteria> toComplete = new HashSet<ICriteria>(); boolean repeat = criteria.canRepeatInfinite(); if (!repeat) { int max = criteria.getRepeatAmount(); int last = getCriteriaCount(criteria); repeat = last < max; } if (repeat) { //If we're allowed to fire again, do so toComplete.add(criteria); } return claimRewardsAndCompleteCriteria(toComplete, true); } /** Called to fire a trigger type, Triggers are only ever called on criteria that is activated **/ public Result fireAllTriggers(String type, Object... triggerData) { if (activeTriggers == null) return Result.DEFAULT; //If the remapping hasn't occured yet, say goodbye! boolean completedAnyCriteria = false; Collection<ITriggerProvider> triggers = activeTriggers.get(type); HashSet<ITriggerProvider> cantContinue = new HashSet(); List<ITriggerProvider> toTrigger = new ArrayList(); for (ITriggerProvider trigger : triggers) { Collection<IConditionProvider> conditions = trigger.getConditions(); for (IConditionProvider condition : conditions) { //If we're bypassing everything, ignore conditions if (condition.getProvided().isSatisfied(master.team) == condition.isInverted()) { cantContinue.add(trigger); break; } } if (cantContinue.contains(trigger)) continue; //Grab the old data toTrigger.add(trigger); //Add triggers for firing } //Fire the trigger Collections.shuffle(toTrigger); for (ITriggerProvider trigger : toTrigger) { if (trigger.isCancelable()) { boolean isCancelingEnabled = trigger.isCanceling(); if ((trigger.getProvided().onFired(uuid, triggerData))) { if (isCancelingEnabled) { sendTriggerDataToClient(trigger); //Send updated trigger before returning return Result.DENY; } } } else if (!trigger.getProvided().onFired(uuid, triggerData)) { sendTriggerDataToClient(trigger); //Send updated trigger before denying return Result.DENY; } //After everything send updated trigger no matter what sendTriggerDataToClient(trigger); } //Next step, now that the triggers have been fire, we need to go through them again //Check if they have been satisfied, and if so, mark them as completed triggers HashSet<ITriggerProvider> toRemove = new HashSet(); for (ITriggerProvider trigger : triggers) { if (cantContinue.contains(trigger)) continue; //If we're bypassing mark all triggers as fired if (trigger.getProvided().isCompleted()) { completedTriggers.add(trigger); toRemove.add(trigger); PacketHandler.sendToTeam(new PacketSyncTriggers(trigger), master.team); } } //Create a list of new triggers to add to the active trigger map HashSet<ICriteria> toComplete = new HashSet(); //Next step, now that we have fired the trigger, we need to go through all the active criteria //We should check if all triggers have been fulfilled for (ITriggerProvider trigger : triggers) { if (cantContinue.contains(trigger) || trigger.getCriteria() == null) continue; ICriteria criteria = trigger.getCriteria(); if (impossible.contains(criteria)) continue; //Check that all triggers are in the completed set List<ITriggerProvider> allTriggers = criteria.getTriggers(); boolean allRequired = criteria.getIfRequiresAllTasks(); int countRequired = criteria.getTasksRequired(); int firedCount = 0; boolean allFired = true; for (ITriggerProvider criteriaTrigger : allTriggers) { //the completed triggers map, doesn't contains all the requirements, then we need to remove it if (!completedTriggers.contains(criteriaTrigger)) { allFired = false; } else firedCount++; } //Complete the criteria and bypass any requirements if ((allFired && allRequired) || (!allRequired && firedCount >= countRequired)) { completedAnyCriteria = true; toComplete.add(criteria); } } //Remove completed triggers from the active map triggers.removeAll(toRemove); return claimRewardsAndCompleteCriteria(toComplete, completedAnyCriteria); } private Result claimRewardsAndCompleteCriteria(HashSet<ICriteria> toComplete, boolean completedAnyCriteria) { //Now that we have removed all the triggers, and marked this as completed and remapped data, // we should add the rewards to the unclaimed rewards list for (ICriteria criteria : toComplete) { for (IRewardProvider reward: criteria.getRewards()) { if (reward.isOnePerTeam()) { unclaimedRewards.get(master.team.getOwner()).addAll(criteria.getRewards()); } else { for (UUID uuid: master.team.getEveryone()) { unclaimedRewards.get(uuid).addAll(criteria.getRewards()); } } for (UUID uuid: master.team.getEveryone()) { PacketHandler.sendToTeam(new PacketSyncUnclaimed(uuid, criteria.getRewards()), master.getTeam()); } } } Set<ICriteria> completed = new HashSet(); todo = new HashSet<Callable>(); //Reset the to do list //Now we should try and dish out any automatic rewards for (UUID uuid: unclaimedRewards.keySet()) { List<IRewardProvider> list = Lists.newArrayList(unclaimedRewards.get(uuid)); Collections.shuffle(list); for (IRewardProvider reward: list) { if (!reward.mustClaim()) { EntityPlayerMP aPlayer = (EntityPlayerMP) PlayerHelper.getPlayerFromUUID(false, uuid); if (aPlayer != null) { if(claimReward(aPlayer, reward)) { completed.add(reward.getCriteria()); } } } } } //Resync unclaimed rewards for (ICriteria criteria: completed) { remapAfterClaiming(criteria); } //TODOLIST for (Callable callable: todo) { try { callable.call(); } catch (Exception e) {} } //Mark data as dirty, whether it changed or not data.markDirty(); return completedAnyCriteria ? Result.ALLOW : Result.DEFAULT; } private int getRewardsGiven(UUID uuid, ICriteria criteria) { int total = 0; if (numberRewards.get(uuid) != null && numberRewards.get(uuid).get(criteria) != null) { total = numberRewards.get(uuid).get(criteria); } return total; } private void setRewardsGiven(UUID uuid, ICriteria criteria, int amount) { HashMap map = numberRewards.get(uuid); if (map == null) { map = new HashMap(); numberRewards.put(uuid, map); } map.put(criteria, amount); } public void remapAfterClaiming(ICriteria criteria) { HashSet<ITriggerProvider> forRemovalFromActive = new HashSet(); //Reset them HashSet<ICriteria> toRemap = new HashSet(); completeCriteria(criteria, forRemovalFromActive, toRemap); remapStuff(forRemovalFromActive, toRemap); for (UUID uuid: master.team.getEveryone()) { setRewardsGiven(uuid, criteria, 0); } } public Collection<IRewardProvider> getUnclaimedRewards(UUID uuid) { return unclaimedRewards.get(uuid); } public boolean claimReward(EntityPlayerMP player, IRewardProvider reward) { UUID uuid = PlayerHelper.getUUIDForPlayer(player); if (reward.isOnePerTeam()) uuid = master.team.getOwner(); ICriteria criteria = reward.getCriteria(); int rewardsGiven = getRewardsGiven(uuid, criteria); if (rewardsGiven < criteria.getAmountOfRewards() || criteria.givesAllRewards()) { reward.getProvided().reward(player); rewardsGiven++; //Increase the amount } setRewardsGiven(uuid, criteria, rewardsGiven); CollectionHelper.remove(unclaimedRewards.get(uuid), reward); PacketHandler.sendToTeam(new PacketSyncUnclaimed(uuid, reward), master.getTeam()); if ((!criteria.givesAllRewards() && rewardsGiven >= criteria.getAmountOfRewards()) || (criteria.givesAllRewards() && rewardsGiven >= criteria.getRewards().size())) { //If all the rewards have been given out, then do some remapping of everything //Remove all remaining rewards from unclaimed for (IRewardProvider provider: reward.getCriteria().getRewards()) { if(CollectionHelper.remove(unclaimedRewards.get(uuid), provider)) { PacketHandler.sendToTeam(new PacketSyncUnclaimed(uuid, provider), master.getTeam()); } } return true; } return false; } public void removeCriteria(ICriteria criteria) { completedCritera.remove(criteria); completedTriggers.removeAll(criteria.getTriggers()); PacketHandler.sendToTeam(new PacketSyncCriteria(false, new Integer[] { 0 }, new ICriteria[] { criteria }), master.team); } private void completeCriteria(ICriteria criteria, HashSet<ITriggerProvider> forRemovalFromActive, HashSet<ICriteria> toRemap) { List<ITriggerProvider> allTriggers = criteria.getTriggers(); int completedTimes = getCriteriaCount(criteria); completedTimes++; completedCritera.put(criteria, completedTimes); //Now that we have updated how times we have completed this quest //We should mark all the triggers for removal from activetriggers, as well as actually remove their stored data for (ITriggerProvider criteriaTrigger : allTriggers) { forRemovalFromActive.add(criteriaTrigger); //Remove all the conflicts triggers for (ICriteria conflict : criteria.getConflicts()) { forRemovalFromActive.addAll(conflict.getTriggers()); } boolean repeat = criteria.canRepeatInfinite(); if (!repeat) { int max = criteria.getRepeatAmount(); int last = getCriteriaCount(criteria); repeat = last < max; } if (repeat) { CollectionHelper.remove(completedTriggers, criteriaTrigger); if (criteriaTrigger instanceof IStoreTriggerData) { triggerDataMap.get(criteriaTrigger.getUniqueID()).readDataFromNBT(new NBTTagCompound()); sendTriggerDataToClient(criteriaTrigger); //Let the client know it was wiped } } } //The next step in the process is to update the active trigger maps for everything //That we unlock with this criteria have been completed toRemap.add(criteria); if (completedTimes == 1) { //Only do shit if this is the first time it was completed toRemap.addAll(RemappingHandler.criteriaToUnlocks.get(criteria)); } Set<EntityPlayerMP> list = PlayerHelper.getPlayersFromUUID(uuid); for (EntityPlayerMP player : list) { PacketHandler.sendToTeam(new PacketSyncCriteria(false, new Integer[] { completedTimes }, new ICriteria[] { criteria }), master.team); if (criteria.displayAchievement()) PacketHandler.sendToTeam(new PacketCompleted(criteria), master.team); } } private void remapStuff(HashSet<ITriggerProvider> forRemovalFromActive, HashSet<ICriteria> toRemap) { //Removes all the triggers from the active map for (ITriggerProvider trigger : forRemovalFromActive) { activeTriggers.get(trigger.getUnlocalisedName()).remove(trigger); } //Remap the criteria for (ICriteria criteria : toRemap) { remapCriteriaOnCompletion(criteria); } } public int getCriteriaCount(ICriteria criteria) { int amount = 0; Integer last = completedCritera.get(criteria); if (last != null) { amount = last; } return amount; } private void remapCriteriaOnCompletion(ICriteria criteria) { ICriteria available = null; //We are now looping though all criteria, we now need to check to see if this //First step is to validate to see if this criteria, is available right now //If the criteria is repeatable, or is not completed continue boolean repeat = criteria.canRepeatInfinite(); if (!repeat) { int max = criteria.getRepeatAmount(); int last = getCriteriaCount(criteria); repeat = last < max; } if (repeat) { if (completedCritera.keySet().containsAll(criteria.getPreReqs())) { //If we have all the requirements, continue //Now that we know that we have all the requirements, we should check for conflicts //If it doesn't contain any of the conflicts, continue forwards if (!containsAny(criteria.getConflicts())) { //The Criteria passed the check for being available, mark it as so available = criteria; } } //If we are allowed to redo triggers, remove from completed completedTriggers.removeAll(criteria.getTriggers()); //Remove all data for the triggers too for (ITriggerProvider trigger: criteria.getTriggers()) { if (triggerDataMap.get(trigger.getUniqueID()) != null) { if (trigger.getProvided() instanceof IStoreTriggerData) { triggerDataMap.get(trigger.getUniqueID()).readDataFromNBT(new NBTTagCompound()); sendTriggerDataToClient(trigger); //Let the client know it was wiped } } } } if (available != null) { List<ITriggerProvider> triggers = criteria.getTriggers(); //Grab a list of all the triggers for (ITriggerProvider provider : triggers) { //If we don't have the trigger in the completed map, mark it as available in the active triggers if (!completedTriggers.contains(provider)) { ITriggerProvider clone = provider.copy(); if (clone.getProvided() instanceof IStoreTriggerData) { //Create a new copy when we remap, with updated requirements if (triggerDataMap.containsKey(clone.getUniqueID())) { NBTTagCompound tag = new NBTTagCompound(); //Create a tag triggerDataMap.get(clone.getUniqueID()).writeDataToNBT(tag); //Write to it ((IStoreTriggerData) clone.getProvided()).readDataFromNBT(tag); //Copy the old data to the clone } triggerDataMap.put(clone.getUniqueID(), (IStoreTriggerData) clone.getProvided()); //Remap the triggers data } activeTriggers.get(clone.getUnlocalisedName()).add(clone); } } } } public void remap() { //Fix the completed completedCritera.remove(null); //Remove any nulls if (Options.debugMode) Progression.logger.log(Level.INFO, "Progression began remapping for the uuid: " + uuid); Set<ICriteria> availableCriteria = new HashSet(); //Recreate the available mappings activeTriggers = HashMultimap.create(); //Recreate the trigger mappings Collection<ICriteria> allCriteria = APICache.getServerCache().getCriteriaSet(); for (ICriteria criteria : allCriteria) { //If the criteria has been marked as impossible don't attach it to anything if (impossible.contains(criteria)) continue; //We are now looping though all criteria, we now need to check to see if this //First step is to validate to see if this criteria, is available right now //If the criteria is repeatable, or is not completed continue boolean repeat = criteria.canRepeatInfinite(); if (!repeat) { int max = criteria.getRepeatAmount(); int last = getCriteriaCount(criteria); repeat = last < max; } if (repeat) { if (completedCritera.keySet().containsAll(criteria.getPreReqs())) { //If we have all the requirements, continue //Now that we know that we have all the requirements, we should check for conflicts //If it doesn't contain any of the conflicts, continue forwards if (!containsAny(criteria.getConflicts())) { //The Criteria passed the check for being available, mark it as so availableCriteria.add(criteria); } } } } //Now that we have remapped all of the criteria, we should remap the triggers for (ICriteria criteria : availableCriteria) { List<ITriggerProvider> triggers = criteria.getTriggers(); //Grab a list of all the triggers for (ITriggerProvider provider : triggers) { //If we don't have the trigger in the completed map, mark it as available in the active triggers ITriggerProvider clone = provider.copy(); if (clone.getProvided() instanceof IStoreTriggerData) { //Create a new copy when we remap, with updated requirements if (triggerDataMap.containsKey(clone.getUniqueID())) { NBTTagCompound tag = new NBTTagCompound(); //Create a tag triggerDataMap.get(clone.getUniqueID()).writeDataToNBT(tag); //Write to it ((IStoreTriggerData) clone.getProvided()).readDataFromNBT(tag); //Copy the old data to the clone } triggerDataMap.put(clone.getUniqueID(), (IStoreTriggerData) clone.getProvided()); //Remap the triggers data } //Only mark it as active if applicable if (!completedTriggers.contains(provider)) { activeTriggers.get(clone.getUnlocalisedName()).add(clone); } } } } }