-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #347 from vincent4vx/feature-ai-scripting
feat(ai): Add scripting for AI + refactor scripting system with admin
- Loading branch information
Showing
72 changed files
with
2,649 additions
and
321 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import fr.quatrevieux.araknemu.game.fight.ai.action.builder.GeneratorBuilder | ||
import fr.quatrevieux.araknemu.game.fight.ai.action.util.CastSpell | ||
import fr.quatrevieux.araknemu.game.fight.ai.action.util.Movement | ||
import fr.quatrevieux.araknemu.game.fight.ai.factory.AbstractAiBuilderFactory | ||
import fr.quatrevieux.araknemu.game.fight.ai.simulation.CastSimulation | ||
import fr.quatrevieux.araknemu.game.fight.ai.simulation.Simulator | ||
|
||
/* | ||
* This file is part of Araknemu. | ||
* | ||
* Araknemu is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Araknemu is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Araknemu. If not, see <https://www.gnu.org/licenses/>. | ||
* | ||
* Copyright (c) 2017-2024 Vincent Quatrevieux | ||
*/ | ||
|
||
/** | ||
* Example of a random AI | ||
* | ||
* To create a new AI type you have to extends AbstractAiBuilderFactory and override one of the configure method | ||
* The AI name will be the class name in upper case. If you want to change the name, you can override the name() method | ||
* | ||
* Note: be aware of the maximum length of the name. By default the maximum length is 12 characters. | ||
*/ | ||
class Random extends AbstractAiBuilderFactory { | ||
final Simulator simulator | ||
final java.util.Random random | ||
|
||
// The scripting API will inject dynamically all the required dependencies | ||
// If one of the dependencies is not declared in the container, it will be created automatically | ||
Random(Simulator simulator) { | ||
this.simulator = simulator | ||
this.random = new java.util.Random() | ||
} | ||
|
||
|
||
// Override this method to configure the AI | ||
// Note: you can also override configure(GeneratorBuilder builder, PlayableFighter fighter) instead of this one to have access to the fighter | ||
@Override | ||
protected void configure(GeneratorBuilder builder) { | ||
// Now you can add actions to the AI pipeline | ||
// The pipeline defines the priority of the actions | ||
// If the first action can be executed, it will be executed until it can't | ||
// And then the second action will be executed, and so on | ||
builder | ||
.add(new Movement({ Math.random() }, { true })) | ||
.add(new CastSpell(simulator, new CastSpell.SimulationSelector() { | ||
@Override | ||
boolean valid(CastSimulation simulation) { | ||
random.nextBoolean() | ||
} | ||
|
||
@Override | ||
double score(CastSimulation simulation) { | ||
Math.random() | ||
} | ||
})) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
src/main/java/fr/quatrevieux/araknemu/core/di/ContainerInstantiator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* This file is part of Araknemu. | ||
* | ||
* Araknemu is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Araknemu is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Araknemu. If not, see <https://www.gnu.org/licenses/>. | ||
* | ||
* Copyright (c) 2017-2024 Vincent Quatrevieux | ||
*/ | ||
|
||
package fr.quatrevieux.araknemu.core.di; | ||
|
||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
||
import java.lang.reflect.Constructor; | ||
|
||
/** | ||
* Default implementation of {@link Instantiator} using a {@link Container} to resolve | ||
* already defined services. | ||
* | ||
* When an object is not declared explicitly in the container, the instantiator will try to | ||
* resolve recursively the constructor arguments and instantiate them. | ||
* | ||
* This implementation will not cache instances. Unless the type is declared in the container, | ||
* a new instance is created each time. | ||
*/ | ||
public final class ContainerInstantiator implements Instantiator { | ||
private final Container container; | ||
|
||
public ContainerInstantiator(Container container) { | ||
this.container = container; | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("argument") | ||
public <T> T instantiate(Class<T> clazz) throws ContainerException { | ||
if (container.has(clazz)) { | ||
return container.get(clazz); | ||
} | ||
|
||
if (clazz.isPrimitive() || clazz.isArray() || clazz.getName().startsWith("java.")) { | ||
throw new InstantiatorException("Cannot instantiate primitive types or classes from java.* package"); | ||
} | ||
|
||
for (Constructor<?> constructor : clazz.getConstructors()) { | ||
final @Nullable Object @Nullable [] parameters = resolveArguments(constructor); | ||
|
||
if (parameters == null) { | ||
continue; | ||
} | ||
|
||
try { | ||
return (T) constructor.newInstance(parameters); | ||
} catch (Exception e) { | ||
// Ignore and try next constructor | ||
} | ||
} | ||
|
||
// No constructor found : try to instantiate without constructor | ||
try { | ||
return clazz.newInstance(); | ||
} catch (Exception e) { | ||
throw new InstantiatorException("Cannot instantiate object of type " + clazz.getName(), e); | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Try to resolve constructor arguments | ||
* When an argument value can't be resolved, return null to indicate that the constructor can't be used | ||
* | ||
* @return Array of arguments or null when cannot instantiate the object | ||
*/ | ||
private @Nullable Object @Nullable [] resolveArguments(Constructor<?> constructor) { | ||
final Class<?>[] parametersTypes = constructor.getParameterTypes(); | ||
final @Nullable Object[] parameters = new Object[parametersTypes.length]; | ||
|
||
for (int i = 0; i < parameters.length; ++i) { | ||
try { | ||
parameters[i] = instantiate(parametersTypes[i]); | ||
} catch (ContainerException e) { | ||
return null; | ||
} | ||
} | ||
|
||
return parameters; | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
src/main/java/fr/quatrevieux/araknemu/core/di/Instantiator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* This file is part of Araknemu. | ||
* | ||
* Araknemu is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Araknemu is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Araknemu. If not, see <https://www.gnu.org/licenses/>. | ||
* | ||
* Copyright (c) 2017-2024 Vincent Quatrevieux | ||
*/ | ||
|
||
package fr.quatrevieux.araknemu.core.di; | ||
|
||
/** | ||
* Base type for instantiate objects using autowiring | ||
* Use to instantiate objects declared externally, like using a script engine | ||
*/ | ||
public interface Instantiator { | ||
/** | ||
* Try to instantiate an object of the given class | ||
* | ||
* @param clazz The class to instantiate | ||
* @return The instantiated object | ||
* | ||
* @param <T> The type of the object | ||
*/ | ||
public <T> T instantiate(Class<T> clazz) throws ContainerException; | ||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/fr/quatrevieux/araknemu/core/di/InstantiatorException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* This file is part of Araknemu. | ||
* | ||
* Araknemu is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Araknemu is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Araknemu. If not, see <https://www.gnu.org/licenses/>. | ||
* | ||
* Copyright (c) 2017-2024 Vincent Quatrevieux | ||
*/ | ||
|
||
package fr.quatrevieux.araknemu.core.di; | ||
|
||
/** | ||
* Exception thrown when an error occurs during object instantiation | ||
*/ | ||
public class InstantiatorException extends ContainerException { | ||
public InstantiatorException(String message) { | ||
super(message); | ||
} | ||
|
||
public InstantiatorException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
src/main/java/fr/quatrevieux/araknemu/core/scripting/HotReloadProxy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* This file is part of Araknemu. | ||
* | ||
* Araknemu is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* Araknemu is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with Araknemu. If not, see <https://www.gnu.org/licenses/>. | ||
* | ||
* Copyright (c) 2017-2024 Vincent Quatrevieux | ||
*/ | ||
|
||
package fr.quatrevieux.araknemu.core.scripting; | ||
|
||
import org.apache.logging.log4j.Logger; | ||
import org.checkerframework.checker.nullness.qual.NonNull; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
||
import java.io.IOException; | ||
import java.lang.reflect.InvocationHandler; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Proxy; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.FileTime; | ||
|
||
/** | ||
* Handle proxy creation to enable hot reload of scripts | ||
* | ||
* @param <T> The type of the script class | ||
*/ | ||
public final class HotReloadProxy<@NonNull T> implements InvocationHandler, HotReloadableScript<T> { | ||
private final ScriptLoader loader; | ||
private final Logger logger; | ||
private final Class<T> type; | ||
private final Path file; | ||
private @NonNull T inner; | ||
private FileTime lastModified; | ||
|
||
HotReloadProxy(ScriptLoader loader, Class<T> type, Logger logger, Path file, @NonNull T inner) throws IOException { | ||
this.loader = loader; | ||
this.logger = logger; | ||
this.type = type; | ||
this.file = file; | ||
this.inner = inner; | ||
this.lastModified = Files.getLastModifiedTime(file); | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("override.return") | ||
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | ||
if (method.getDeclaringClass().isAssignableFrom(HotReloadableScript.class)) { | ||
return method.invoke(this, args); | ||
} | ||
|
||
return method.invoke(load(), args); | ||
} | ||
|
||
@Override | ||
public T getInternalInstance() { | ||
return inner; | ||
} | ||
|
||
@Override | ||
public Path getScriptFile() { | ||
return file; | ||
} | ||
|
||
@Override | ||
public Class<T> getScriptType() { | ||
return type; | ||
} | ||
|
||
/** | ||
* Create the proxy instance | ||
*/ | ||
public @NonNull T instantiate() { | ||
return type.cast(Proxy.newProxyInstance( | ||
type.getClassLoader(), | ||
new Class[] { | ||
type, | ||
HotReloadableScript.class, | ||
}, | ||
this | ||
)); | ||
} | ||
|
||
private @NonNull T load() { | ||
try { | ||
final FileTime newModified = Files.getLastModifiedTime(file); | ||
|
||
if (!newModified.equals(lastModified)) { | ||
logger.debug("The script {} has been modified. Reload it.", file); | ||
|
||
final T newInner = loader.load(file, type); | ||
|
||
if (newInner != null) { | ||
inner = newInner; | ||
lastModified = newModified; | ||
} else { | ||
logger.error("The new version of the script {} is not compatible with type {}. Keep last version.", file, type); | ||
} | ||
} | ||
} catch (Throwable e) { | ||
logger.error("Failed to reload script " + file + ". Keep last version.", e); | ||
} | ||
|
||
return inner; | ||
} | ||
} |
Oops, something went wrong.