Merge branch 'master' of git@github.com:sk89q/worldedit.git

This commit is contained in:
hretsam 2011-08-07 02:42:12 +02:00
commit 282b3fbd05
13 changed files with 296 additions and 187 deletions

122
README.md
View File

@ -16,6 +16,12 @@ simply run:
Maven will automatically download dependencies for you. Note: For that to work,
be sure to add Maven to your "PATH".
Issue Tracker
-------------
Please submit bug reports and feature requests here:
http://redmine.sk89q.com/projects/worldedit/issues
Contributing
------------
@ -24,118 +30,4 @@ WorldEdit on GitHub, add your changes, and then submit a pull request. We'll
look at it, make comments, and merge it into WorldEdit if everything
works out.
Your submissions have to be licensed under the GNU General Public License v3.
General Concepts
----------------
The entry point for all of WorldEdit is in `com.sk89q.worldedit.WorldEdit`.
This is where all the events and chat commands are handled. The commands
themselves are found in the `com.sk89q.worldedit.commands` package.
Each user has a _session_ that stores session-related data, including
history and clipboard. The class that handles session data is
`com.sk89q.worldedit.LocalSession`. A copy of it is created when needed
by the `getSession` method of `WorldEdit` and it's also stored on
`WorldEdit` in a hash map. The history is merely a list of
`com.sk89q.worldedit.EditSession`s, while the clipboard is a copy of
`com.sk89q.worldedit.CuboidClipboard`.
Now, one of the most important classes in WorldEdit is
`com.sk89q.worldedit.EditSession`. Nearly all block sets and gets are routed
through it because it automatically records a log of operations (for undo),
handles block placement order, and does a lot of magic to make sure things
come out the way it is intended. However, to make sure that block placement
order is adhered, remember to call `EditSession.enableQueue()` and later
`EditSession.flushQueue()`. Also, to actually an edit session in a player's
history, it has to be passed to `LocalSession.remember(EditSession)`.
Blocks in WorldEdit are entirely abstracted. Block types and block data not
simply passed around; rather, because blocks can contain a lot more data
(such as with signs and such), all blocks are an instance of
`com.sk89q.worldedit.blocks.BaseBlock`. For special block types, there's
a `SignBlock`, a `ChestBlock`, etc. Blocks are __detached__ from the world,
meaning they don't know where they are. You can pass them around freely
if you want (this is why syntax like `//set sign:3|Hi|there!` can work).
If you are making a command, you need to add the new command to `plugin.yml`
if you are using Bukkit. However,
`com.sk89q.worldedit.dev.DocumentationPrinter` is a program that will
generate `plugin.yml` by using Java reflection on the command classes.
Commands are given an edit session automatically (with queue
enabled) and so there's not much to set up. If you want to add a new class
altogether that contains commands, you need to update the constructor of
`com.sk89q.worldedit.WorldEdit` load your class.
### Core Routines ###
`com.sk89q.worldedit.WorldEdit.getBlock` handles the block syntax
(such as `sign:3|Hi|there!`).
`com.sk89q.worldedit.WorldEdit.getBlockPattern` handles the pattern
syntax (such as `90%rock,10%brick` or `#clipboard`).
Package Summary
---------------
WorldEdit is well organized and uses abstraction heavily to make adding new
things easy. An explanation of WorldEdit's package layout is as follows:
* `com.sk89q.bukkit.migration` has classes to handle permissions for
Bukkit plugins until Bukkit attains built-in permissions support
* `com.sk89q.util` has some utility classes
* `com.sk89q.util.commands` has some base command handling code
(commands in WorldEdit are defined using Java annotations)
* `com.sk89q.worldedit` has core WorldEdit classes
* `com.sk89q.worldedit.bags` has support for block sources and sinks
such as inventory (which allows blocks to be taken from a player's
inventory)
* `com.sk89q.worldedit.blocks` abstracts blocks from the game
(such as chest blocks, etc.) and has support for all block data
* `com.sk89q.worldedit.bukkit` contains the implementation of WorldEdit
for Bukkit as a plugin
* `com.sk89q.worldedit.commands` has all of WorldEdit's chat commands
* `com.sk89q.worldedit.data` contains classes to read Minecraft's world
files directly from disk
* `com.sk89q.worldedit.dev` contains a class to generate documentation
and other development-related files
* `com.sk89q.worldedit.filters` contains filters used for the smoothing
algorithm
* `com.sk89q.worldedit.patterns` contains the pattern support
(such as for `//set 90%rock,10%air` or `//set #clipboard`)
* `com.sk89q.worldedit.regions` contains the selection regions for
WorldEdit; there's only one at the moment (a cuboid), but different
region shapes can be added easily
* `com.sk89q.worldedit.scripting` contains scripting engines
* `com.sk89q.worldedit.snapshots` contains snapshot loading code (but
actual world file reading code is in `com.sk89q.worldedit.data`)
* `com.sk89q.worldedit.superpickaxe` contains the code for the different
super pickaxe modes
* `com.sk89q.worldedit.superpickaxe.brush` contains the different brush
shapes for the brush super pickaxe tools
* `com.sk89q.worldedit.util` has some utility classes
* `org.jnbt` is the JNBT library to read JNBT formatted files
Task Tutorials
--------------
### How to Add a Command ###
1. If you want to add your command to an existing category of commands
(check out the classes in `com.sk89q.worldedit.commands`) then you
can just re-use one. If you want to create a new class, create a new
class (it does not have to inherit or implement anything) and add it
to the constructor of `com.sk89q.worldedit.WorldEdit`.
2. Add a new method, named anything.
3. Add the `@Command` annotation to signify that it is a command. The
`aliases` property contains a list of command aliases and the first
one in the list is the main alias. `usage` contains parameter usage
information. `desc` is a short description. `flags` is an optional
string of flags (each flag is only one character long).
`min` is the minimum number of arguments. `max` is the maximum number of
arguments and it can be -1 to allow an unlimited number.
4. Adding `@CommandPermissions` causes permissions to be checked for the
command. Only one permission needs to be satisfied in the list.
5. Write the command.
6. If using Bukkit, update `plugin.yml` or run
`com.sk89q.worldedit.dev.DocumentationPrinter` to generate it for you.
7. Compile and test!
Your submissions have to be licensed under the GNU General Public License v3.

View File

@ -294,6 +294,9 @@ commands:
none:
description: Turn off all superpickaxe alternate modes
usage: /<command>
farwand:
description: Wand tool at a distance
usage: /<command>
tree:
description: Tree generator tool
usage: /<command> [type]

View File

@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import org.bukkit.util.config.Configuration;
import org.bukkit.util.config.ConfigurationNode;
public class ConfigurationPermissionsResolver implements PermissionsResolver {
private Configuration config;
@ -35,6 +36,16 @@ public class ConfigurationPermissionsResolver implements PermissionsResolver {
public ConfigurationPermissionsResolver(Configuration config) {
this.config = config;
}
public static void generateDefaultPerms(Configuration config) {
config.setProperty("permissions.groups.default.permissions", new String[] {
"worldedit.reload",
"worldedit.selection.*",
"worlds.creative.worldedit.region.*"});
config.setProperty("permissions.groups.admins.permissions", new String[]{"*"});
config.setProperty("permissions.users.sk89q.permissions", new String[]{"worldedit.*"});
config.setProperty("permissions.users.sk89q.groups", new String[]{"admins"});
}
public void load() {
userGroups = new HashMap<String,Set<String>>();

View File

@ -20,7 +20,6 @@
package com.sk89q.bukkit.migration;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;

View File

@ -19,34 +19,58 @@
package com.sk89q.bukkit.migration;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.config.Configuration;
public class PermissionsResolverManager implements PermissionsResolver {
private Configuration config;
private static final String CONFIG_HEADER = "#\r\n" +
"# WEPIF Configuration File\r\n" +
"#\r\n" +
"# This file handles permissions configuration for every plugin using WEPIF\r\n" +
"#\r\n" +
"# About editing this file:\r\n" +
"# - DO NOT USE TABS. You MUST use spaces or Bukkit will complain. If\r\n" +
"# you use an editor like Notepad++ (recommended for Windows users), you\r\n" +
"# must configure it to \"replace tabs with spaces.\" In Notepad++, this can\r\n" +
"# be changed in Settings > Preferences > Language Menu.\r\n" +
"# - Don't get rid of the indents. They are indented so some entries are\r\n" +
"# in categories (like \"enforce-single-session\" is in the \"protection\"\r\n" +
"# category.\r\n" +
"# - If you want to check the format of this file before putting it\r\n" +
"# into WEPIF, paste it into http://yaml-online-parser.appspot.com/\r\n" +
"# and see if it gives \"ERROR:\".\r\n" +
"# - Lines starting with # are comments and so they are ignored.\r\n" +
"#\r\n" +
"# About Configuration Permissions\r\n" +
"# - See http://wiki.sk89q.com/wiki/WorldEdit/Permissions/Bukkit\r\n" +
"# - Now with multiworld support (see example)\r\n" +
"\r\n";
private Server server;
private PermissionsResolver perms;
private Configuration permsConfig;
private String name;
private Logger logger;
public PermissionsResolverManager(Configuration config, Server server, String name, Logger logger) {
this.config = config;
this.server = server;
this.name = name;
this.logger = logger;
loadConfig(new File("wepif.yml")); // TODO: config migration, maybe
findResolver();
}
public void findResolver() {
if (tryDinnerPerms()) return;
if (tryPluginPermissionsResolver()) return;
if (tryNijiPermissions()) return;
if (tryFlatFilePermissions()) return;
perms = new ConfigurationPermissionsResolver(config);
perms = new ConfigurationPermissionsResolver(permsConfig);
logger.info(name + ": No known permissions plugin detected. Using configuration file for permissions.");
}
@ -85,7 +109,7 @@ public class PermissionsResolverManager implements PermissionsResolver {
}
private boolean tryDinnerPerms() {
if (!config.getBoolean("permissions.dinner-perms", true))
if (!permsConfig.getBoolean("dinnerperms", true))
return false;
perms = new DinnerPermsResolver(server);
logger.info(name + ": Using the Bukkit Permissions API.");
@ -123,4 +147,36 @@ public class PermissionsResolverManager implements PermissionsResolver {
return perms.getGroups(player);
}
private boolean loadConfig(File file) {
boolean isUpdated = false;
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
permsConfig = new Configuration(file);
permsConfig.load();
List<String> keys = permsConfig.getKeys();
permsConfig.setHeader(CONFIG_HEADER);
if (!keys.contains("dinnerperms")) {
permsConfig.setProperty("dinnerperms", permsConfig.getBoolean("dinner-perms", true));
isUpdated = true;
}
if (keys.contains("dinner-perms")) {
permsConfig.removeProperty("dinner-perms");
isUpdated = true;
}
if (!keys.contains("permissions")) {
ConfigurationPermissionsResolver.generateDefaultPerms(permsConfig);
isUpdated = true;
}
if (isUpdated) {
logger.info("WEPIF: Updated config file");
permsConfig.save();
}
return isUpdated;
}
}

View File

@ -1327,7 +1327,7 @@ public class EditSession {
for (int y = upperY; y >= lowerY; --y) {
Vector pt = new Vector(x, y, z);
Vector above = new Vector(x, y + 1, z);
//Vector above = new Vector(x, y + 1, z);
int blockType = getBlockType(pt);
boolean isTransformable =
@ -1687,7 +1687,8 @@ public class EditSession {
pos = pos.subtract(0, height, 0);
}
if (pos.getBlockY() - height - 1 < 0) {
// Only do this check if height is negative --Elizabeth
if (height < 0 && pos.getBlockY() - height - 1 < 0) {
height = pos.getBlockY() + 1;
} else if (pos.getBlockY() + height - 1 > 127) {
height = 127 - pos.getBlockY() + 1;

View File

@ -879,26 +879,31 @@ public class WorldEdit {
}
/**
* Called on left click (not on a block).
* Called on arm swing.
*
* @param player
* @return
*/
public boolean handleLeftClick(LocalPlayer player) {
public boolean handleArmSwing(LocalPlayer player) {
LocalSession session = getSession(player);
WorldVector pos = player.getSolidBlockTrace(config.navigationWandMaxDistance);
if (player.getItemInHand() == config.wandItem) {
return selectFirstPoint(player, session, pos);
} else if (player.getItemInHand() == config.navigationWand
if (player.getItemInHand() == config.navigationWand
&& config.navigationWandMaxDistance > 0
&& player.hasPermission("worldedit.navigation.jumpto")) {
WorldVector pos = player.getSolidBlockTrace(config.navigationWandMaxDistance);
if (pos != null) {
player.findFreePosition(pos);
} else {
player.printError("No block in sight (or too far)!");
}
}
Tool tool = session.getTool(player.getItemInHand());
if (tool != null && tool instanceof DoubleActionTraceTool) {
if (tool.canUse(player)) {
((DoubleActionTraceTool) tool).actSecondary(server, config, player, session);
return true;
}
}
return false;
}
@ -918,16 +923,13 @@ public class WorldEdit {
if (!player.passThroughForwardWall(40)) {
player.printError("Nothing to pass through!");
}
} else if (player.getItemInHand() == config.wandItem) {
WorldVector pos = player.getSolidBlockTrace(config.navigationWandMaxDistance);
return selectSecondPoint(player, session, pos);
}
Tool tool = session.getTool(player.getItemInHand());
if (tool != null && tool instanceof TraceTool) {
if (tool.canUse(player)) {
((TraceTool)tool).act(server, config, player, session);
((TraceTool) tool).actPrimary(server, config, player, session);
return true;
}
}
@ -943,10 +945,18 @@ public class WorldEdit {
* @return false if you want the action to go through
*/
public boolean handleBlockRightClick(LocalPlayer player, WorldVector clicked) {
int itemInHand = player.getItemInHand();
LocalSession session = getSession(player);
if (player.getItemInHand() == config.wandItem) {
return selectSecondPoint(player, session, clicked);
if (itemInHand == config.wandItem && session.isToolControlEnabled()
&& player.hasPermission("worldedit.selection.pos")) {
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectSecondary(clicked)) {
selector.explainSecondarySelection(player, session, clicked);
}
return true;
}
Tool tool = session.getTool(player.getItemInHand());
@ -971,8 +981,22 @@ public class WorldEdit {
public boolean handleBlockLeftClick(LocalPlayer player, WorldVector clicked) {
LocalSession session = getSession(player);
if (player.getItemInHand() == config.wandItem) {
return selectFirstPoint(player, session, clicked);
if (player.getItemInHand() == config.wandItem) {
if (session.isToolControlEnabled()
&& player.hasPermission("worldedit.selection.pos")) {
// Bug workaround
if (clicked.getBlockX() == 0 && clicked.getBlockY() == 0
&& clicked.getBlockZ() == 0) {
return false;
}
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectPrimary(clicked)) {
selector.explainPrimarySelection(player, session, clicked);
}
return true;
}
} else if (player.isHoldingPickAxe() && session.hasSuperPickAxe()) {
if (session.getSuperPickaxe() != null) {
if (session.getSuperPickaxe().canUse(player)) {
@ -991,40 +1015,6 @@ public class WorldEdit {
}
}
return false;
}
private boolean selectFirstPoint(LocalPlayer player, LocalSession session, WorldVector clicked)
{
if (session.isToolControlEnabled()
&& player.hasPermission("worldedit.selection.pos")) {
// Bug workaround
if (clicked.getBlockX() == 0 && clicked.getBlockY() == 0
&& clicked.getBlockZ() == 0) {
return false;
}
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectPrimary(clicked)) {
selector.explainPrimarySelection(player, session, clicked);
}
return true;
}
return false;
}
private boolean selectSecondPoint(LocalPlayer player, LocalSession session, WorldVector clicked)
{
if (session.isToolControlEnabled()
&& player.hasPermission("worldedit.selection.pos")) {
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectSecondary(clicked)) {
selector.explainSecondarySelection(player, session, clicked);
}
return true;
}
return false;
}

View File

@ -21,8 +21,6 @@ package com.sk89q.worldedit.bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerAnimationEvent;
import org.bukkit.event.player.PlayerAnimationType;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerListener;
@ -30,7 +28,6 @@ import org.bukkit.event.player.PlayerQuitEvent;
import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.LocalWorld;
import com.sk89q.worldedit.WorldVector;
import com.sk89q.worldedit.blocks.BlockID;
/**
* Handles all events thrown in relation to a Player
@ -94,8 +91,14 @@ public class WorldEditPlayerListener extends PlayerListener {
LocalPlayer player = wrapPlayer(event.getPlayer());
if (plugin.getWorldEdit().handleBlockLeftClick(player, pos)) {
event.setCancelled(true);
}
if (plugin.getWorldEdit().handleArmSwing(wrapPlayer(event.getPlayer()))) {
event.setCancelled(true);
} else if (plugin.getWorldEdit().handleLeftClick(wrapPlayer(event.getPlayer()))) {
}
} else if (event.getAction() == Action.LEFT_CLICK_AIR) {
if (plugin.getWorldEdit().handleArmSwing(wrapPlayer(event.getPlayer()))) {
event.setCancelled(true);
}
} else if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
@ -106,17 +109,15 @@ public class WorldEditPlayerListener extends PlayerListener {
if (plugin.getWorldEdit().handleBlockRightClick(player, pos)) {
event.setCancelled(true);
} else if (plugin.getWorldEdit().handleRightClick(wrapPlayer(event.getPlayer()))) {
}
if (plugin.getWorldEdit().handleRightClick(wrapPlayer(event.getPlayer()))) {
event.setCancelled(true);
}
} else if (event.getAction() == Action.RIGHT_CLICK_AIR) {
if (plugin.getWorldEdit().handleRightClick(wrapPlayer(event.getPlayer()))) {
event.setCancelled(true);
}
} else if (event.getAction() == Action.LEFT_CLICK_AIR) {
if (plugin.getWorldEdit().handleLeftClick(wrapPlayer(event.getPlayer()))) {
event.setCancelled(true);
}
}
}

View File

@ -176,4 +176,20 @@ public class ToolCommands {
player.print("Floating tree remover tool bound to "
+ ItemType.toHeldName(player.getItemInHand()) + ".");
}
@Command(
aliases = {"farwand"},
usage = "",
desc = "Wand at a distance tool",
min = 0,
max = 0
)
@CommandPermissions({"worldedit.tool.farwand"})
public static void farwand(CommandContext args, WorldEdit we,
LocalSession session, LocalPlayer player, EditSession editSession)
throws WorldEditException {
session.setTool(player.getItemInHand(), new DistanceWand());
player.print("Far wand tool bound to " + ItemType.toHeldName(player.getItemInHand()) + ".");
}
}

View File

@ -36,8 +36,8 @@ import com.sk89q.worldedit.tools.brushes.SphereBrush;
* @author sk89q
*/
public class BrushTool implements TraceTool {
private static int MAX_RANGE = 500;
private int range = -1;
protected static int MAX_RANGE = 500;
protected int range = -1;
private Mask mask = null;
private Brush brush = new SphereBrush();
private Pattern material = new SingleBlockPattern(new BaseBlock(BlockID.COBBLESTONE));
@ -164,7 +164,7 @@ public class BrushTool implements TraceTool {
* @param session
* @return true to deny
*/
public boolean act(ServerInterface server, LocalConfiguration config,
public boolean actPrimary(ServerInterface server, LocalConfiguration config,
LocalPlayer player, LocalSession session) {
WorldVector target = null;
if (this.range > -1) {

View File

@ -0,0 +1,96 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.tools;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.bags.BlockBag;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.regions.RegionSelector;
/**
* A wand that can be used at a distance.
*
* @author wizjany
*/
public class DistanceWand extends BrushTool implements DoubleActionTraceTool {
public DistanceWand() {
super("worldedit.wand");
}
public boolean canUse(LocalPlayer player) {
return player.hasPermission("worldedit.wand");
}
public boolean actSecondary(ServerInterface server, LocalConfiguration config,
LocalPlayer player, LocalSession session) {
if (session.isToolControlEnabled() && player.hasPermission("worldedit.selection.pos")) {
WorldVector target = getTarget(player);
if (target == null) return true;
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectPrimary(target)) {
selector.explainPrimarySelection(player, session, target);
}
return true;
}
return false;
}
@Override
public boolean actPrimary(ServerInterface server, LocalConfiguration config,
LocalPlayer player, LocalSession session) {
if (session.isToolControlEnabled() && player.hasPermission("worldedit.selection.pos")) {
WorldVector target = getTarget(player);
if (target == null) return true;
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectSecondary(target)) {
selector.explainSecondarySelection(player, session, target);
}
return true;
}
return false;
}
public WorldVector getTarget(LocalPlayer player) {
WorldVector target = null;
if (this.range > -1) {
target = player.getBlockTrace(getRange(), true);
} else {
target = player.getBlockTrace(MAX_RANGE);
}
if (target == null) {
player.printError("No block in sight!");
return null;
}
// Bug workaround
if (target.getBlockX() == 0 && target.getBlockY() == 0 && target.getBlockZ() == 0) {
return null;
}
return target;
}
}

View File

@ -0,0 +1,44 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.tools;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.WorldVector;
/**
* Represents a trace tool that also has a secondary/primary function.
*/
public interface DoubleActionTraceTool extends TraceTool {
/**
* Perform the secondary action. Should return true to deny the default
* action.
*
* @param server
* @param config
* @param player
* @param session
* @return true to deny
*/
public boolean actSecondary(ServerInterface server, LocalConfiguration config,
LocalPlayer player, LocalSession session);
}

View File

@ -40,6 +40,6 @@ public interface TraceTool extends Tool {
* @param session
* @return true to deny
*/
public boolean act(ServerInterface server, LocalConfiguration config,
public boolean actPrimary(ServerInterface server, LocalConfiguration config,
LocalPlayer player, LocalSession session);
}