package com.cardshifter.core.replays; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import net.zomis.cardshifter.ecs.config.ConfigComponent; import net.zomis.cardshifter.ecs.usage.CardshifterIO; import com.cardshifter.api.both.PlayerConfigMessage; import com.cardshifter.modapi.actions.ActionPerformEvent; 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.GameOverEvent; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; public class ReplayRecordSystem implements ECSSystem { @JsonProperty private final List<ReplayAction> actionInformation = new ArrayList<>(); @JsonProperty @JsonTypeInfo(include = As.PROPERTY, use = Id.NAME, property = "_type") private final Map<Integer, PlayerConfigMessage> entityConfigs = new HashMap<>(); private final long seed; private final File file; private List<String> playerNames; private final String modName; ReplayRecordSystem(@JsonProperty("seed") long seed) { this.seed = seed; this.modName = null; this.file = null; } public ReplayRecordSystem(ECSGame game, String modName, File output) { this.seed = fetchSeed(game.getRandom()); this.modName = modName; this.file = output; } private long fetchSeed(Random random) { try { Field field = random.getClass().getDeclaredField("seed"); field.setAccessible(true); Object result = field.get(random); AtomicLong seed = (AtomicLong) result; return seed.get(); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException("Cannot initialize random seed", e); } } @Override public void startGame(ECSGame game) { if (file == null) { return; } game.getEvents().registerHandlerAfter(this, ActionPerformEvent.class, this::recordAction); game.getEvents().registerHandlerAfter(this, GameOverEvent.class, this::saveReplay); Set<Entity> configs = game.getEntitiesWithComponent(ConfigComponent.class); for (Entity configEntity : configs) { ConfigComponent config = configEntity.getComponent(ConfigComponent.class); entityConfigs.put(configEntity.getId(), new PlayerConfigMessage(0, modName, config.getConfigs())); } } private void recordAction(ActionPerformEvent event) { actionInformation.add(ReplayAction.forAction(event)); } private void saveReplay(GameOverEvent event) { List<PlayerComponent> players = event.getGame().getEntitiesWithComponent(PlayerComponent.class) .stream().map(pl -> pl.getComponent(PlayerComponent.class)) .collect(Collectors.toList()); players.sort(Comparator.comparing(PlayerComponent::getIndex)); this.playerNames = players.stream().map(pl -> pl.getName()).collect(Collectors.toList()); try { CardshifterIO.mapper().writeValue(file, this); } catch (IOException e) { throw new RuntimeException("Unable to save replay", e); } } public long getSeed() { return seed; } public List<ReplayAction> getActionInformation() { return actionInformation; } public Map<Integer, PlayerConfigMessage> getEntityConfigs() { return Collections.unmodifiableMap(entityConfigs); } public List<String> getPlayerNames() { return playerNames; } public String getModName() { return modName; } }