package com.cardshifter.modapi.base;
import java.util.ArrayList;
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.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.cardshifter.modapi.events.*;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
/**
* Starting point for the entire ECS
*
* @author Simon Forsberg
*/
public final class ECSGame {
private static final Logger logger = LogManager.getLogger(ECSGame.class);
private final AtomicInteger ids = new AtomicInteger();
/**
* All the entities of a single game
*/
private final Map<Integer, Entity> entities = new HashMap<>();
private final EventExecutor events = new EventExecutor();
/**
* All the systems that comprise the game
*/
private final List<ECSSystem> systems = new ArrayList<>();
private final Random random = new Random();
/**
* An enum for the current state of the game
*/
private ECSGameState gameState = ECSGameState.NOT_STARTED;
public ECSGame() {
}
/**
* Creates an entity, assigns an Id, adds it to the entities of the game object
* @return The created entity
*/
public Entity newEntity() {
Entity entity = new Entity(this, ids.incrementAndGet());
this.entities.put(entity.getId(), entity);
getEvents().executePostEvent(new EntityCreatedEvent(entity));
return entity;
}
/**
* Executes an event while performing something in the middle of executing the event.
* It will first do an event for listeners that have registered before, then
* it will do runInBetween, then it will fire off the listeners that have registered
* for after.
*
* @param <T> The event type that is executed
* @param event The event to execute
* @param runInBetween The action to run in between
* @return The same event that was executed
*/
public <T extends IEvent> T executeEvent(T event, Runnable runInBetween) {
return events.executeEvent(event, runInBetween);
}
/**
*
* @param <T> The component type to create
* @param class1 Creates and returns the requested component type
* @return A Component Retriever of the specified class
*/
public <T extends Component> ComponentRetriever<T> componentRetreiver(Class<T> class1) {
return new ComponentRetriever<>(class1);
}
/**
*
* @param clazz The component to search for
* @return All entities that contain the component
*/
public Set<Entity> getEntitiesWithComponent(Class<? extends Component> clazz) {
return entities.values().stream().filter(e -> e.hasComponent(clazz)).collect(Collectors.toSet());
}
/**
*
* @return The EventExecutor object
*/
public EventExecutor getEvents() {
return events;
}
/**
* Add a system to the systems list.
* If the game is in any other state besides NOT_STARTED, the system will be started
*
* @param system The ECSSystem to add
*/
public void addSystem(ECSSystem system) {
logger.info("Add system: " + system);
this.systems.add(system);
Retrievers.inject(system, this);
if (gameState != ECSGameState.NOT_STARTED) {
system.startGame(this);
}
}
/**
* Starts the game if the game is in the NOT_STARTED state.
* Starts each of the systems in the systems list.
* Fires off the StartGameEvent
*/
public void startGame() {
if (gameState != ECSGameState.NOT_STARTED) {
throw new IllegalStateException("Game is already started");
}
systems.forEach(sys -> sys.startGame(this));
gameState = ECSGameState.RUNNING;
events.executePostEvent(new StartGameEvent(this));
}
/**
* Randomize a number
*
* @param min Lower bound (inclusive)
* @param max Upper bound (inclusive)
* @return A random number in the inclusive range min - max
*/
public int randomRange(int min, int max) {
return random.nextInt(max - min + 1) + min;
}
/**
*
* @return A random number from Random() class
*/
public Random getRandom() {
return random;
}
/**
* Fire off a GameOverEvent, set the game state to GAME_ENDED.
*/
public void endGame() {
this.executeCancellableEvent(new GameOverEvent(this), () -> gameState = ECSGameState.GAME_ENDED);
}
/**
*
* @return The current state of the game
*/
public ECSGameState getGameState() {
return gameState;
}
/**
*
* @return True if the state is GAME_ENDED
*/
public boolean isGameOver() {
return gameState == ECSGameState.GAME_ENDED;
}
/**
* Removes the entity that matches the id of the supplied entity.
*
* @param entity The entity to remove
*/
void removeEntity(Entity entity) {
entities.remove(entity.getId());
}
/**
*
* @param condition The type of entity to search for
* @return A list of matching entities.
*/
public List<Entity> findEntities(Predicate<Entity> condition) {
return entities.values().stream().filter(condition).collect(Collectors.toList());
}
/**
*
* @param <T> The CancellableEvent
* @param event The event to execute
* @param runInBetween The action to run in between
* @return The same event that was executed
*/
public <T extends CancellableEvent> T executeCancellableEvent(T event, Runnable runInBetween) {
return events.executeCancellableEvent(event, runInBetween);
}
/**
*
* @param entity The id of the entity to get
* @return The requested entity object
*/
public Entity getEntity(int entity) {
return entities.get(entity);
}
/**
* Sets a new seed for the random object.
*
* @param seed The seed to set
*/
public void setRandomSeed(long seed) {
random.setSeed(seed);
}
/**
*
* @param <T> Generic ECSSystem
* @param clazz The system class to search for
* @return A list of systems that match the input system
*/
public <T extends ECSSystem> List<T> findSystemsOfClass(Class<T> clazz) {
return systems.stream()
.filter(sys -> clazz.isAssignableFrom(sys.getClass()))
.map(obj -> clazz.cast(obj))
.collect(Collectors.toList());
}
/**
* Also removes any listeners that were listening for that system.
*
* @param system The ECSSystem to remove
* @return Whether or not the system was successfully removed
*/
public boolean removeSystem(ECSSystem system) {
logger.info("Remove system " + system);
events.removeListenersWithIdentifier(system);
return systems.remove(system);
}
// TODO: copy actions. Set<ActionOptions>. choose one, choose two
// TODO: More Hearthstone-like features. Enchantments, effects, battlecry, deathrattle, etc.
}