Jump to content

1.11.2 Forge Hook a player click "Respawn"


Heltrato

Recommended Posts

If your quest-system makes use of the Capability System, you need to transfer the data to the "new" player, as mentioned in Forge's documentation.

https://mcforge.readthedocs.io/en/latest/datastorage/capabilities/#persisting-across-player-deaths

Also previously known as eAndPi.

"Pi, is there a station coming up where we can board your train of thought?" -Kronnn

Published Mods: Underworld

Handy links: Vic_'s Forge events Own WIP Tutorials.

Link to comment
Share on other sites

On 11/15/2017 at 10:31 PM, Matryoshika said:

If your quest-system makes use of the Capability System, you need to transfer the data to the "new" player, as mentioned in Forge's documentation.

https://mcforge.readthedocs.io/en/latest/datastorage/capabilities/#persisting-across-player-deaths

I hope i can make my english more understandble.

 

Sorry for my late reply, but apparently we didnt use capablity for the quest system.. What we did is we use JsonElement for our save data and just define it in the class (urghhh how can i explain it )

 

anyways here is our Mission Class

 

Spoiler

package mhfc.net.common.quests;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;

import com.google.gson.JsonElement;

import mhfc.net.MHFCMain;
import mhfc.net.common.core.registry.MHFCExplorationRegistry;
import mhfc.net.common.core.registry.MHFCQuestRegistry;
import mhfc.net.common.core.registry.MHFCSoundRegistry;
import mhfc.net.common.eventhandler.MHFCTickHandler;
import mhfc.net.common.eventhandler.TickPhase;
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.IQuestDefinition;
import mhfc.net.common.quests.api.IQuestReward;
import mhfc.net.common.quests.api.ISpawnInformation;
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.rewards.NullReward;
import mhfc.net.common.quests.spawns.NoSpawn;
import mhfc.net.common.quests.world.IQuestAreaSpawnController;
import mhfc.net.common.quests.world.QuestFlair;
import mhfc.net.common.util.PlayerMap;
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.SoundCategory;
import net.minecraft.util.text.TextComponentTranslation;

public class Mission implements QuestGoalSocket, AutoCloseable {

	public static boolean ifPlayerDies;

	public static final String KEY_TYPE_RUNNING = "running";
	private static final int DELAY_BEFORE_TP_IN_SECONDS = 5;

	private static enum QuestState {
		PENDING,
		RUNNING,
		FINISHED_SUCCESS,
		FINISHED_FAIL,
		RESIGNED;
	}

	private static enum PlayerState {
		IN_TOWN,
		ON_QUEST,
		WAITING_FOR_BACK_TP;
	}

	private static class QuestingPlayerState {
		public EntityPlayerMP player;
		public boolean vote;
		@SuppressWarnings("unused")
		public boolean restoreInventory;
		@SuppressWarnings("unused")
		public boolean reward;
		public IExplorationManager previousManager;
		public JsonElement previousSaveData;
		public PlayerState playerState;

		public QuestingPlayerState(EntityPlayerMP p, boolean vote, boolean restoreInventory, boolean reward) {
			this.player = p;
			this.restoreInventory = restoreInventory;
			this.vote = vote;
			this.reward = reward;
			// this bind should also consider player if it dies once ! ,
			this.previousManager = MHFCExplorationRegistry.getExplorationManagerFor(p);
			this.previousSaveData = this.previousManager.saveState();
			this.playerState = PlayerState.IN_TOWN;
		}
	}

	private static QuestingPlayerState newAttribute(EntityPlayerMP player) {
		return new QuestingPlayerState(player, false, true, false);
	}

	private final String missionID;
	private IQuestDefinition 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 IQuestReward reward;
	protected ISpawnInformation spawns;
	protected int fee;

	private boolean closed;

	public Mission(
			String missionID,
			QuestGoal goal,
			GroupProperty goalProperties,
			int maxPartySize,
			IQuestReward reward,
			ISpawnInformation spawns,
			int fee,
			CompletionStage<IActiveArea> activeArea,
			IQuestDefinition 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 == null ? new NullReward() : reward;
		this.spawns = spawns == null ? NoSpawn.INSTANCE : spawns;
		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 getFee() {
		return fee;
	}

	@Override
	public void questGoalStatusNotification(QuestGoal goal, EnumSet<QuestStatus> newStatus) {
		if (newStatus.contains(QuestStatus.Fulfilled) && state == QuestState.RUNNING) {
			onSuccess();
			this.state = QuestState.FINISHED_SUCCESS;
		}
		if (newStatus.contains(QuestStatus.Failed) && state == QuestState.RUNNING) {
			onFail();
			this.state = QuestState.FINISHED_FAIL;
		}
		updatePlayers();
	}

	protected void onAreaFinished(IActiveArea area) {
		this.questingArea = Objects.requireNonNull(area);
		tryStart();
	}

	protected boolean canStart() {
		return allVotes() && this.questingArea != null;
	}

	private void tryStart() {
		if (state == QuestState.PENDING && canStart()) {
			this.state = QuestState.RUNNING;
			onStart();
			resetVotes();
		}
	}

	protected void onFail() {
		for (EntityPlayerMP player : getPlayers()) {
			player.world.playSound(
					null,
					player.getPosition(),
					MHFCSoundRegistry.getRegistry().questNotification,
					SoundCategory.MUSIC,
					2F,
					1F);
			player.sendMessage(new TextComponentTranslation("mhfc.quests.status.failed"));
		}
		// TODO do special stuff for fail
		onEnd();
	}

	protected void onSuccess() {
		for (QuestingPlayerState playerState : playerAttributes.values()) {
			playerState.player.world.playSound(
					null,
					playerState.player.getPosition(),
					MHFCSoundRegistry.getRegistry().questClear,
					SoundCategory.MUSIC,
					2F,
					1F);
			playerState.player.world.playSound(
					null,
					playerState.player.getPosition(),
					MHFCSoundRegistry.getRegistry().questNotification,
					SoundCategory.NEUTRAL,
					2F,
					2F);
			playerState.player.sendMessage(new TextComponentTranslation("mhfc.quests.status.success"));
		}
		onEnd();
	}

	protected void onStart() {
		questGoal.setActive(true);
		QuestExplorationManager.bindPlayersToMission(getPlayers(), this);
		for (QuestingPlayerState playerAttributes : playerAttributes.values()) {
			EntityPlayerMP player = playerAttributes.player;
			playerAttributes.playerState = PlayerState.ON_QUEST;
			IExplorationManager explorationManager = MHFCExplorationRegistry.getExplorationManagerFor(player);
			explorationManager.respawn(null);
		}

		updatePlayers();
		resetVotes();
		spawns.enqueueSpawns(getSpawnController());
	}

	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() {
		List<CompletionStage<Void>> teleports = new ArrayList<>();
		for (QuestingPlayerState playerState : playerAttributes.values()) {
			CompletionStage<Void> afterReward = teleportBack(playerState).thenAccept(v -> {
				if (state == QuestState.FINISHED_SUCCESS) {
					playerState.player.sendMessage(new TextComponentTranslation("mhfc.quests.status.rewardGranted"));
					reward.grantReward(playerState.player);
					playerState.reward = true;
				}
			});
			teleports.add(afterReward);
		}
		CompletionStage<Void> afterTeleports = CompletableFuture.allOf(teleports.toArray(new CompletableFuture[0]));
		afterTeleports.<Void>handle((r, e) -> null).thenCompose(v -> {
			return MHFCTickHandler.schedule(TickPhase.SERVER_POST, 5 * 20, () -> {
				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().getMissionForPlayer(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().setMissionForPlayer(player, this);
		updatePlayerInitial(player);
		updatePlayers();
	}

	private QuestingPlayerState removePlayer(EntityPlayerMP player) {
		QuestingPlayerState att = playerAttributes.removePlayer(player);
		if (att != null) {
			PacketPipeline.networkPipe.sendTo(MessageMissionStatus.departing(missionID), player);
			MHFCQuestRegistry.getRegistry().setMissionForPlayer(player, null);
			//
		}
		return att;
	}

	private CompletionStage<Void> teleportBack(QuestingPlayerState att) {
		Objects.requireNonNull(att);
		if (att.playerState == PlayerState.WAITING_FOR_BACK_TP) {
			return CompletableFuture.completedFuture(null);
		}
		if (att.playerState == PlayerState.IN_TOWN) {
			// Pretend we're gonna to tp him, but actually just rebind the player
			att.playerState = PlayerState.WAITING_FOR_BACK_TP;
			return MHFCTickHandler.schedule(TickPhase.SERVER_POST, DELAY_BEFORE_TP_IN_SECONDS * 20, () -> {
				EntityPlayerMP player = att.player;
				MHFCExplorationRegistry.bindPlayer(att.previousManager, player);
				MHFCExplorationRegistry.respawnPlayer(player, att.previousSaveData);
				att.playerState = PlayerState.IN_TOWN;
			});
		}

		att.player.sendMessage(
				new TextComponentTranslation("mhfc.quests.status.teleportSoon", DELAY_BEFORE_TP_IN_SECONDS));
		att.playerState = PlayerState.WAITING_FOR_BACK_TP;
		return MHFCTickHandler.schedule(TickPhase.SERVER_POST, DELAY_BEFORE_TP_IN_SECONDS * 20, () -> {
			EntityPlayerMP player = att.player;
			player.sendMessage(new TextComponentTranslation("mhfc.quests.status.teleport"));

			// Remove the player, otherwise the rebind will trigger another remove
			removePlayer(player);

			MHFCExplorationRegistry.bindPlayer(att.previousManager, player);
			MHFCExplorationRegistry.respawnPlayer(player, att.previousSaveData);
			att.playerState = PlayerState.IN_TOWN;
		});
	}


	public boolean joinPlayer(EntityPlayerMP player) {
		if (!canJoin(player)) {
			return false;
		}
		addPlayer(player);
		return true;
	}

	public void quitPlayer(EntityPlayerMP player) {
		QuestingPlayerState attributes = removePlayer(player);
		if (attributes != null) {
			teleportBack(attributes);
		}
		if (playerCount() == 0) {
			onEnd();
		}
	}

	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 IQuestDefinition 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.isEmpty() && playerAttributes.values().stream().allMatch((x) -> x.vote);
	}

	public void voteEnd(EntityPlayerMP player) {
		QuestingPlayerState attributes = getPlayerAttributes(player);
		attributes.vote = true;

		boolean end = allVotes();
		if (end && state == QuestState.RUNNING) {
			onEnd();
			state = QuestState.RESIGNED;
			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();
		if (questingArea != null) {
			// Could be closed before area is finished
			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);
	}

}

 

 

Then here is the main method in which im telling u the player should be transferred back to his old save point in the overworld, teleporting from the quest area.

Spoiler


private CompletionStage<Void> teleportBack(QuestingPlayerState att) {
		Objects.requireNonNull(att);
		if (att.playerState == PlayerState.WAITING_FOR_BACK_TP) {
			return CompletableFuture.completedFuture(null);
		}
		if (att.playerState == PlayerState.IN_TOWN) {
			// Pretend we're gonna to tp him, but actually just rebind the player
			att.playerState = PlayerState.WAITING_FOR_BACK_TP;
			return MHFCTickHandler.schedule(TickPhase.SERVER_POST, DELAY_BEFORE_TP_IN_SECONDS * 20, () -> {
				EntityPlayerMP player = att.player;
				MHFCExplorationRegistry.bindPlayer(att.previousManager, player);
				MHFCExplorationRegistry.respawnPlayer(player, att.previousSaveData);
				att.playerState = PlayerState.IN_TOWN;
			});
		}

		att.player.sendMessage(
				new TextComponentTranslation("mhfc.quests.status.teleportSoon", DELAY_BEFORE_TP_IN_SECONDS));
		att.playerState = PlayerState.WAITING_FOR_BACK_TP;
		return MHFCTickHandler.schedule(TickPhase.SERVER_POST, DELAY_BEFORE_TP_IN_SECONDS * 20, () -> {
			EntityPlayerMP player = att.player;
			player.sendMessage(new TextComponentTranslation("mhfc.quests.status.teleport"));

			// Remove the player, otherwise the rebind will trigger another remove
			removePlayer(player);

			MHFCExplorationRegistry.bindPlayer(att.previousManager, player);
			MHFCExplorationRegistry.respawnPlayer(player, att.previousSaveData);
			att.playerState = PlayerState.IN_TOWN;
		});
	}

 

 

 

Link to comment
Share on other sites

When a player dies (and during dimension travel) the player instance is removed, and a new instance is made. This is likely why the "save-state" is null.

You will need to transfer the data just like with Capabilities.

Oh, and you should not store the EntityPlayer (or derivatives like EntityPlayerMP) directly. You should be using a WeakReference<EntityPlayerMP> instead.
Storing an EntityPlayer can cause memory leaks. What if the player dies repeatedly? A new savestate would exist for each player instances, and the old player instances would not be garbagecollected because they're still being referenced.
A WeakReference allows the garbagecollector to remove the player from memory in case of death/log outs etc.

 

Alternatively use the UUID instead, and get the EntityPlayer from that.

Edited by Matryoshika

Also previously known as eAndPi.

"Pi, is there a station coming up where we can board your train of thought?" -Kronnn

Published Mods: Underworld

Handy links: Vic_'s Forge events Own WIP Tutorials.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • They were already updated, and just to double check I even did a cleanup and fresh update from that same page. I'm quite sure drivers are not the problem here. 
    • i tried downloading the drivers but it says no AMD graphics hardware has been detected    
    • Update your AMD/ATI drivers - get the drivers from their website - do not update via system  
    • As the title says i keep on crashing on forge 1.20.1 even without any mods downloaded, i have the latest drivers (nvidia) and vanilla minecraft works perfectly fine for me logs: https://pastebin.com/5UR01yG9
    • Hello everyone, I'm making this post to seek help for my modded block, It's a special block called FrozenBlock supposed to take the place of an old block, then after a set amount of ticks, it's supposed to revert its Block State, Entity, data... to the old block like this :  The problem I have is that the system breaks when handling multi blocks (I tried some fix but none of them worked) :  The bug I have identified is that the function "setOldBlockFields" in the item's "setFrozenBlock" function gets called once for the 1st block of multiblock getting frozen (as it should), but gets called a second time BEFORE creating the first FrozenBlock with the data of the 1st block, hence giving the same data to the two FrozenBlock :   Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=head] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@73681674 BlockEntityData : id:"minecraft:bed",x:3,y:-60,z:-6} Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=3, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=2, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} here is the code inside my custom "freeze" item :    @Override     public @NotNull InteractionResult useOn(@NotNull UseOnContext pContext) {         if (!pContext.getLevel().isClientSide() && pContext.getHand() == InteractionHand.MAIN_HAND) {             BlockPos blockPos = pContext.getClickedPos();             BlockPos secondBlockPos = getMultiblockPos(blockPos, pContext.getLevel().getBlockState(blockPos));             if (secondBlockPos != null) {                 createFrozenBlock(pContext, secondBlockPos);             }             createFrozenBlock(pContext, blockPos);             return InteractionResult.SUCCESS;         }         return super.useOn(pContext);     }     public static void createFrozenBlock(UseOnContext pContext, BlockPos blockPos) {         BlockState oldState = pContext.getLevel().getBlockState(blockPos);         BlockEntity oldBlockEntity = oldState.hasBlockEntity() ? pContext.getLevel().getBlockEntity(blockPos) : null;         CompoundTag oldBlockEntityData = oldState.hasBlockEntity() ? oldBlockEntity.serializeNBT() : null;         if (oldBlockEntity != null) {             pContext.getLevel().removeBlockEntity(blockPos);         }         BlockState FrozenBlock = setFrozenBlock(oldState, oldBlockEntity, oldBlockEntityData);         pContext.getLevel().setBlockAndUpdate(blockPos, FrozenBlock);     }     public static BlockState setFrozenBlock(BlockState blockState, @Nullable BlockEntity blockEntity, @Nullable CompoundTag blockEntityData) {         BlockState FrozenBlock = BlockRegister.FROZEN_BLOCK.get().defaultBlockState();         ((FrozenBlock) FrozenBlock.getBlock()).setOldBlockFields(blockState, blockEntity, blockEntityData);         return FrozenBlock;     }  
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.