/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.config;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.context.Context;
import org.terasology.engine.SimpleUri;
import org.terasology.engine.module.ModuleManager;
import org.terasology.input.BindAxisEvent;
import org.terasology.input.BindButtonEvent;
import org.terasology.input.BindableAxis;
import org.terasology.input.BindableButton;
import org.terasology.input.DefaultBinding;
import org.terasology.input.Input;
import org.terasology.input.InputSystem;
import org.terasology.input.RegisterBindAxis;
import org.terasology.input.RegisterBindButton;
import org.terasology.input.RegisterRealBindAxis;
import org.terasology.input.events.AxisEvent;
import org.terasology.input.events.ButtonEvent;
import org.terasology.module.DependencyResolver;
import org.terasology.module.ModuleEnvironment;
import org.terasology.module.ResolutionResult;
import org.terasology.module.predicates.FromModule;
import org.terasology.naming.Name;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.Collections;
/**
* User binds configuration. This holds the key/mouse binding for Button Binds. They are sorted by package.
*
*/
public final class BindsConfig {
private static final Logger logger = LoggerFactory.getLogger(BindsConfig.class);
private ListMultimap<SimpleUri, Input> data = ArrayListMultimap.create();
public BindsConfig() {
}
/**
* Returns true if an input has already been bound to another key
*
* @param newInput The input to check if it has been bound already
* @return True if newInput has been bound. False otherwise.
*/
public boolean isBound(Input newInput) {
return data.containsValue(newInput);
}
/**
* Sets this BindsConfig to be identical to other
*
* @param other The BindsConfig to copy
*/
public void setBinds(BindsConfig other) {
data.clear();
data.putAll(other.data);
}
public List<Input> getBinds(SimpleUri uri) {
return data.get(uri);
}
/**
* Returns whether an input bind has been registered with the BindsConfig.
* It may just have trivial None input.
*
* @param uri The bind's uri
* @return Whether the given bind has been registered with the BindsConfig
*/
public boolean hasBinds(SimpleUri uri) {
return !data.get(uri).isEmpty();
}
/**
* Sets the inputs for a given bind, replacing any previous inputs
*
* @param bindUri
* @param inputs
*/
public void setBinds(SimpleUri bindUri, Input... inputs) {
setBinds(bindUri, Arrays.asList(inputs));
}
public void setBinds(SimpleUri bindUri, Iterable<Input> inputs) {
Set<Input> uniqueInputs = Sets.newLinkedHashSet(inputs);
// Clear existing usages of the given inputs
Iterator<Input> iterator = data.values().iterator();
while (iterator.hasNext()) {
Input i = iterator.next();
if (uniqueInputs.contains(i)) {
iterator.remove();
}
}
data.replaceValues(bindUri, uniqueInputs);
}
/**
* @return A new BindsConfig, with inputs set from the DefaultBinding annotations on bind classes
*/
public static BindsConfig createDefault(Context context) {
ModuleManager moduleManager = context.get(ModuleManager.class);
BindsConfig config = new BindsConfig();
DependencyResolver resolver = new DependencyResolver(moduleManager.getRegistry());
for (Name moduleId : moduleManager.getRegistry().getModuleIds()) {
if (moduleManager.getRegistry().getLatestModuleVersion(moduleId).isCodeModule()) {
ResolutionResult result = resolver.resolve(moduleId);
if (result.isSuccess()) {
try (ModuleEnvironment environment = moduleManager.loadEnvironment(result.getModules(), false)) {
FromModule filter = new FromModule(environment, moduleId);
Iterable<Class<?>> buttons = environment.getTypesAnnotatedWith(RegisterBindButton.class, filter);
Iterable<Class<?>> axes = environment.getTypesAnnotatedWith(RegisterRealBindAxis.class, filter);
config.addButtonDefaultsFor(moduleId, buttons);
config.addAxisDefaultsFor(moduleId, axes);
}
}
}
}
return config;
}
/**
* Updates a config with any binds that it may be missing, through reflection over RegisterBindButton annotations
*/
public void updateForChangedMods(Context context) {
ModuleManager moduleManager = context.get(ModuleManager.class);
DependencyResolver resolver = new DependencyResolver(moduleManager.getRegistry());
for (Name moduleId : moduleManager.getRegistry().getModuleIds()) {
if (moduleManager.getRegistry().getLatestModuleVersion(moduleId).isCodeModule()) {
ResolutionResult result = resolver.resolve(moduleId);
if (result.isSuccess()) {
try (ModuleEnvironment environment = moduleManager.loadEnvironment(result.getModules(), false)) {
FromModule filter = new FromModule(environment, moduleId);
Iterable<Class<?>> buttons = environment.getTypesAnnotatedWith(RegisterBindButton.class, filter);
Iterable<Class<?>> axes = environment.getTypesAnnotatedWith(RegisterRealBindAxis.class, filter);
updateButtonInputsFor(moduleId, buttons);
updateAxisInputsFor(moduleId, axes);
}
}
}
}
}
private void updateButtonInputsFor(Name moduleId, Iterable<Class<?>> classes) {
for (Class<?> buttonEvent : classes) {
if (ButtonEvent.class.isAssignableFrom(buttonEvent)) {
RegisterBindButton info = buttonEvent.getAnnotation(RegisterBindButton.class);
SimpleUri bindUri = new SimpleUri(moduleId, info.id());
if (!hasBinds(bindUri)) {
addBind(moduleId, buttonEvent, info.id());
}
}
}
}
private void updateAxisInputsFor(Name moduleId, Iterable<Class<?>> classes) {
for (Class<?> axisEvent : classes) {
if (AxisEvent.class.isAssignableFrom(axisEvent)) {
RegisterRealBindAxis info = axisEvent.getAnnotation(RegisterRealBindAxis.class);
SimpleUri bindUri = new SimpleUri(moduleId, info.id());
if (!hasBinds(bindUri)) {
addBind(moduleId, axisEvent, info.id());
}
}
}
}
private void addButtonDefaultsFor(Name moduleId, Iterable<Class<?>> classes) {
for (Class<?> buttonEvent : classes) {
if (ButtonEvent.class.isAssignableFrom(buttonEvent)) {
RegisterBindButton info = buttonEvent.getAnnotation(RegisterBindButton.class);
addBind(moduleId, buttonEvent, info.id());
}
}
}
private void addAxisDefaultsFor(Name moduleId, Iterable<Class<?>> classes) {
for (Class<?> axisEvent : classes) {
if (AxisEvent.class.isAssignableFrom(axisEvent)) {
RegisterRealBindAxis info = axisEvent.getAnnotation(RegisterRealBindAxis.class);
addBind(moduleId, axisEvent, info.id());
}
}
}
private void addBind(Name moduleName, Class<?> event, String id) {
List<Input> defaultInputs = Lists.newArrayList();
for (Annotation annotation : event.getAnnotationsByType(DefaultBinding.class)) {
DefaultBinding defaultBinding = (DefaultBinding) annotation;
Input input = defaultBinding.type().getInput(defaultBinding.id());
if (!data.values().contains(input)) {
defaultInputs.add(input);
}
}
SimpleUri bindUri = new SimpleUri(moduleName, id);
setBinds(bindUri, defaultInputs);
}
public void applyBinds(InputSystem inputSystem, ModuleManager moduleManager) {
inputSystem.clearBinds();
ModuleEnvironment env = moduleManager.getEnvironment();
registerButtonBinds(inputSystem, env, env.getTypesAnnotatedWith(RegisterBindButton.class));
registerAxisBinds(inputSystem, env, env.getTypesAnnotatedWith(RegisterBindAxis.class));
registerRealAxisBinds(inputSystem, env, env.getTypesAnnotatedWith(RegisterRealBindAxis.class));
}
private void registerAxisBinds(InputSystem inputSystem, ModuleEnvironment environment, Iterable<Class<?>> classes) {
for (Class<?> registerBindClass : classes) {
RegisterBindAxis info = registerBindClass.getAnnotation(RegisterBindAxis.class);
Name moduleId = environment.getModuleProviding(registerBindClass);
SimpleUri id = new SimpleUri(moduleId, info.id());
if (BindAxisEvent.class.isAssignableFrom(registerBindClass)) {
BindableButton positiveButton = inputSystem.getBindButton(new SimpleUri(info.positiveButton()));
BindableButton negativeButton = inputSystem.getBindButton(new SimpleUri(info.negativeButton()));
if (positiveButton == null) {
logger.warn("Failed to register axis \"{}\", missing positive button \"{}\"", id, info.positiveButton());
continue;
}
if (negativeButton == null) {
logger.warn("Failed to register axis \"{}\", missing negative button \"{}\"", id, info.negativeButton());
continue;
}
try {
BindableAxis bindAxis = inputSystem.registerBindAxis(id.toString(), (BindAxisEvent) registerBindClass.newInstance(), positiveButton, negativeButton);
bindAxis.setSendEventMode(info.eventMode());
logger.debug("Registered axis bind: {}", id);
} catch (InstantiationException | IllegalAccessException e) {
logger.error("Failed to register axis bind \"{}\"", id, e);
}
} else {
logger.error("Failed to register axis bind \"{}\", does not extend BindAxisEvent", id);
}
}
}
private void registerRealAxisBinds(InputSystem inputSystem, ModuleEnvironment environment, Iterable<Class<?>> classes) {
for (Class<?> registerBindClass : classes) {
RegisterRealBindAxis info = registerBindClass.getAnnotation(RegisterRealBindAxis.class);
Name moduleId = environment.getModuleProviding(registerBindClass);
SimpleUri id = new SimpleUri(moduleId, info.id());
if (BindAxisEvent.class.isAssignableFrom(registerBindClass)) {
try {
BindAxisEvent instance = (BindAxisEvent) registerBindClass.newInstance();
BindableAxis bindAxis = inputSystem.registerRealBindAxis(id.toString(), instance);
bindAxis.setSendEventMode(info.eventMode());
for (Input input : getBinds(id)) {
inputSystem.linkAxisToInput(input, id);
}
logger.debug("Registered axis bind: {}", id);
} catch (InstantiationException | IllegalAccessException e) {
logger.error("Failed to register axis bind \"{}\"", id, e);
}
} else {
logger.error("Failed to register axis bind \"{}\", does not extend BindAxisEvent", id);
}
}
}
private void registerButtonBinds(InputSystem inputSystem, ModuleEnvironment environment, Iterable<Class<?>> classes) {
for (Class<?> registerBindClass : classes) {
RegisterBindButton info = registerBindClass.getAnnotation(RegisterBindButton.class);
SimpleUri bindUri = new SimpleUri(environment.getModuleProviding(registerBindClass), info.id());
if (BindButtonEvent.class.isAssignableFrom(registerBindClass)) {
try {
BindableButton bindButton = inputSystem.registerBindButton(bindUri, info.description(), (BindButtonEvent) registerBindClass.newInstance());
bindButton.setMode(info.mode());
bindButton.setRepeating(info.repeating());
getBinds(bindUri).stream().filter(input -> input != null).forEach(input ->
inputSystem.linkBindButtonToInput(input, bindUri));
logger.debug("Registered button bind: {}", bindUri);
} catch (InstantiationException | IllegalAccessException e) {
logger.error("Failed to register button bind \"{}\"", e);
}
} else {
logger.error("Failed to register button bind \"{}\", does not extend BindButtonEvent", bindUri);
}
}
}
static class Handler implements JsonSerializer<BindsConfig>, JsonDeserializer<BindsConfig> {
@Override
public BindsConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
BindsConfig result = new BindsConfig();
JsonObject inputObj = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : inputObj.entrySet()) {
SetMultimap<String, Input> map = context.deserialize(entry.getValue(), SetMultimap.class);
for (String id : map.keySet()) {
SimpleUri uri = new SimpleUri(new Name(entry.getKey()), id);
result.data.putAll(uri, map.get(id));
}
}
return result;
}
@Override
public JsonElement serialize(BindsConfig src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
SetMultimap<Name, SimpleUri> bindByModule = HashMultimap.create();
for (SimpleUri key : src.data.keySet()) {
bindByModule.put(key.getModuleName(), key);
}
List<Name> sortedModules = Lists.newArrayList(bindByModule.keySet());
Collections.sort(sortedModules);
for (Name moduleId : sortedModules) {
SetMultimap<String, Input> moduleBinds = HashMultimap.create();
for (SimpleUri bindUri : bindByModule.get(moduleId)) {
moduleBinds.putAll(bindUri.getObjectName().toString(), src.data.get(bindUri));
}
JsonElement map = context.serialize(moduleBinds, SetMultimap.class);
result.add(moduleId.toString(), map);
}
return result;
}
}
}