package com.cardshifter.core.replays; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; import com.cardshifter.api.config.PlayerConfig; import net.zomis.cardshifter.ecs.config.ConfigComponent; import com.cardshifter.api.both.PlayerConfigMessage; import com.cardshifter.modapi.actions.ActionAllowedCheckEvent; import com.cardshifter.modapi.actions.ActionComponent; import com.cardshifter.modapi.actions.ActionPerformEvent; import com.cardshifter.modapi.actions.Actions; import com.cardshifter.modapi.actions.ECSAction; import com.cardshifter.modapi.actions.SpecificActionSystem; import com.cardshifter.modapi.actions.TargetSet; import com.cardshifter.modapi.ai.AIComponent; import com.cardshifter.modapi.ai.AISystem; import com.cardshifter.modapi.base.ECSGame; import com.cardshifter.modapi.base.ECSSystem; import com.cardshifter.modapi.base.Entity; import com.cardshifter.modapi.base.PlayerComponent; import com.cardshifter.modapi.events.StartGameEvent; public class ReplayPlaybackSystem implements ECSSystem { private static final String NEXT_STEP = "Next Step"; private final ReplayRecordSystem replayData; private int currentActionIndex; private final ECSGame game; public ReplayPlaybackSystem(ECSGame game, ReplayRecordSystem replay) { this.replayData = replay; this.game = game; applySeed(game, replay.getSeed()); } private void applySeed(ECSGame game, long newSeed) { Random random = game.getRandom(); try { Field field = random.getClass().getDeclaredField("seed"); field.setAccessible(true); Object result = field.get(random); AtomicLong seed = (AtomicLong) result; seed.set(newSeed); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException("Cannot initialize random seed", e); } } @Override public void startGame(ECSGame game) { game.getEntitiesWithComponent(AIComponent.class).forEach(e -> e.getComponent(AIComponent.class).setPaused(true)); game.getEvents().registerHandlerAfter(this, StartGameEvent.class, e -> this.onStart(e, game)); List<Entity> players = new ArrayList<>(game.getEntitiesWithComponent(PlayerComponent.class)); // players.sort(Comparator.comparing(pl -> pl.getComponent(PlayerComponent.class).getIndex())); players.forEach(this::setPlayerName); players.forEach(this::giveReplayControls); } private void setPlayerName(Entity playerEntity) { PlayerComponent playerInfo = playerEntity.getComponent(PlayerComponent.class); int index = playerInfo.getIndex(); if (this.replayData.getPlayerNames() != null) { playerInfo.setName(this.replayData.getPlayerNames().get(index)); } } private void onStart(StartGameEvent event, ECSGame game) { game.findSystemsOfClass(ReplayRecordSystem.class).forEach(game::removeSystem); game.findSystemsOfClass(AISystem.class).forEach(game::removeSystem); game.addSystem(new NextStepReplayActionSystem()); } private static class NextStepReplayActionSystem extends SpecificActionSystem { public NextStepReplayActionSystem() { super(NEXT_STEP); } @Override protected void isAllowed(ActionAllowedCheckEvent event) { event.setAllowed(event.getEntity() == event.getPerformer()); } @Override protected void onPerform(ActionPerformEvent event) { } } private void giveReplayControls(Entity player) { ActionComponent actions = player.getComponent(ActionComponent.class); if (actions == null) { actions = new ActionComponent(); player.addComponent(actions); } actions.addAction(new ECSAction(player, NEXT_STEP, e -> true, this::nextStep)); } private void nextStep(ECSAction replayAction) { if (isReplayFinished()) { return; } nextStep(); } public boolean isReplayFinished() { return currentActionIndex >= replayData.getActionInformation().size(); } public void nextStep() { ReplayAction step = replayData.getActionInformation().get(currentActionIndex); Entity entity = game.getEntity(step.getEntity()); ECSAction action = Actions.getAction(entity, step.getActionName()); if (action.getTargetSets().size() != step.getTargets().size()) { throw new ReplayException("Number of targetsets does not match for action " + action + " at action index " + currentActionIndex); } for (int i = 0; i < action.getTargetSets().size(); i++) { TargetSet targetSet = action.getTargetSets().get(i); List<Integer> targets = step.getTargets().get(i); targetSet.clearTargets(); for (int target : targets) { Entity targetEntity = game.getEntity(target); if (targetEntity == null) { throw new ReplayException("Target " + target + " not found when performing " + action + " at action index " + currentActionIndex); } targetSet.addTarget(targetEntity); } } boolean performSuccess = action.perform(game.getEntity(step.getPerformer())); if (!performSuccess) { throw new ReplayException("Replay action not correctly performed " + action + " at action index " + currentActionIndex); } currentActionIndex++; } public void setPlayerConfigs(ECSGame game) { for (Entry<Integer, PlayerConfigMessage> storedConfig : replayData.getEntityConfigs().entrySet()) { System.out.println("Processing stored config " + storedConfig.getKey() + ": " + storedConfig.getValue()); Entity entity = game.getEntity(storedConfig.getKey()); ConfigComponent config = entity.getComponent(ConfigComponent.class); for (Entry<String, PlayerConfig> configuration : storedConfig.getValue().getConfigs().entrySet()) { System.out.println("Adding config " + configuration.getKey() + " of type " + configuration.getValue().getClass() + ": " + configuration.getValue()); config.addConfig(configuration.getKey(), configuration.getValue()); } config.setConfigured(true); } } }