Merge pull request #601 from gnembon/rework_events

rework of the event system, fixes #596
This commit is contained in:
gnembon authored and GitHub committed 2020-12-02 00:50:43 -04:00
commit 1dd711a7e3
13 files changed
+367 -246

No files matched your search

+6 -1
View File
@@ -6,6 +6,10 @@ out/
classes/ classes/
publish/ publish/
# git
.shelf/
# idea # idea
.idea/ .idea/
@@ -27,4 +31,5 @@ bin/
# fabric # fabric
run/ run/
logs/
+46 -99
View File
@@ -9,10 +9,8 @@ with `__on_<event>` and has the required arguments, will be bound automatically
of such function would result in unbinding the app from this event. of such function would result in unbinding the app from this event.
In case of `player` scoped apps, In case of `player` scoped apps,
all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, cannot be handled all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific
in `'player'` scoped app. player target will be executed multiple times, once for each player, and once in `'global'` scoped apps.
`'global'` scoped apps will receive both global and player built-in events.
Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice
for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the
@@ -31,8 +29,8 @@ if it accepts required number of parameters.
## Built-in global events ## Built-in global events
Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance
simply wouldn't know which player to hook up this event to. is responsible independently from handling their events, so a global event may be executed multiple times for each player.
### `__on_tick()` ### `__on_tick()`
Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()` Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()`
@@ -205,8 +203,11 @@ is handled before scoreboard values for these statistics are changed.
App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value
as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is
either targetting all global context apps, if no target player has been identified, or only player scoped apps, if the target player either targetting all apps instances for all players, including global apps, if no target player has been identified,
is specified. or only player scoped apps, if the target player
is specified, running once for that player app. You cannot target global apps with player-targeted signals. Built-in events
do target global apps, since their first argument is clearly defined and passed. That may change in the future in case there is
a compelling argument to be able to target global apps with player scopes.
Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have
have no idea why you would need that. The following snippets have the same effect: have no idea why you would need that. The following snippets have the same effect:
@@ -230,11 +231,11 @@ handle_event('player_breaks_block', null);
And `signal_event` can be used as a trigger, called twice for player based built-in events And `signal_event` can be used as a trigger, called twice for player based built-in events
``` ```
signal_event('player_breaks_block', player, player, block); // to target all player scoped apps signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
``` ```
or (for global only events) or (for global events)
``` ```
signal_event('tick') // trigger only global scoped apps with no extra arguments signal_event('tick') // trigger all apps with a tick event
``` ```
### `handle_event(event, callback ...)` ### `handle_event(event, callback ...)`
@@ -248,6 +249,8 @@ If extra arguments are provided, they will be appended to the argument list of t
Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event, Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event,
or incorect number of parameters for the event). or incorect number of parameters for the event).
If a callback is specified as `null`, the given app (or player app instance )stops handling that event.
<pre> <pre>
foo(a) -> print(a); foo(a) -> print(a);
handle_event('boohoo', 'foo'); handle_event('boohoo', 'foo');
@@ -262,111 +265,55 @@ handle_event('tick', null) // nah, ima good, kthxbai
### `signal_event(event, target_player?, ... args?)` ### `signal_event(event, target_player?, ... args?)`
Fires a specific event. If the event does not exist (only `handle_event` creates missing mising new events), or provided argument list Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list
was not matching the callee expected arguments, returns `null`, was not matching the callee expected arguments, returns `null`,
otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting
only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event. only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event.
If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped. If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover Note that all built-in player events have a player as a first argument, so to trigger these events, you need to
all app scopes. Note that all built-in player events have a player as a first argument, so to trigger these events, you need to
provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function. provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function.
<pre> <pre>
signal_event('player_breaks_block', player, player, block); // to target all player scoped apps signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
signal_event('tick') // triggering tick event in all loaded apps signal_event('tick') // trigger all apps with a tick event
</pre> </pre>
## Custom events example ## Custom events example
The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous The following example shows how you can communicate between different instances of the same player scoped app. It important to note
squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called
in this case the event is once called as a global event, and once as a player event, targeting each app once. Custom events is `tp_request` and is triggered with a command.
a good idea to share common event detection code with one app, and using the outcome of these events in multiple places.
Obviously you would define each event handler once not twice in your app system.
In this
case here we have two ways of detecting all players squatting for more than 40 ticks - one of those apps will produce cloud particles
in an apprpriate location, and the other will produce a sound when it happens, and they both will proceed to signal the same event,
thankfully for different scopes, otherwise other apps would trigger twice.
``` ```
// detector1.sc // tpa.sc
// detecting event in a tick loop global_requester = null;
__config() -> {'scope' -> 'global'}; __config() -> {
'commands' -> {
global_sneaks = {}; '<player>' -> _(to) -> signal_event('tp_request', to, player()),
__on_tick() -> for(player('all'), 'accept' -> _() -> if(global_requester,
if (_~'sneaking', run('tp '+global_requester~'command_name');
sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0); global_requester = null
if (sneak_time > 20 && sneak_time < 40,
particle('cloud', pos(_) + [0, _~'height'/2, 0] - _~'look', 1, 0.1)
);
if (sneak_time == 40,
signal_event('player_farts', _, _) // triggering all player scoped apps from a global app
) )
, // else },
delete(global_sneaks:_) 'arguments' -> {
) 'player' -> {'type' -> 'players', 'single' -> true}
) }
};
handle_event('tp_request', _(req) -> (
global_requester = req;
print(player(), format(
'w '+req+' requested to teleport to you. Click ',
'yb here', '^yb here', '!/tpa accept',
'w to accept it.'
));
));
``` ```
```
// detector2.sc
// detecting events in a more appropriate way - player based and event based app
__on_player_starts_sneaking(p) ->
(
global_sneaking = tick_time();
schedule(40, 'check', p);
);
check(p) ->
if( global_sneaking && tick_time() - global_sneaking == 40,
sound('entity.shulker.shoot', pos(p), 1, 0.6, 'player');
signal_event('player_farts', null, p) // trigerring all global apps from a player scoped app
);
__on_player_stops_sneaking(p) -> (global_sneaking = false);
```
And then two other apps that meant to do something with this event. One mimics an explosion, and the other - grows crops around
the player.
```
client1.sc
__config() -> {'scope' -> 'global'};
on_fart(p, msg) ->
(
print(p+msg);
particle('explosion', pos(p));
sound('entity.generic.explode', pos(p), 1, 0.5);
);
handle_event('player_farts', 'on_fart', ' goes kaboom');
```
```
client2.sc
longsneak(p) ->
(
ppos = pos(p);
loop ( 500,
target = ppos+[rand(12),rand(8),rand(12)]-[rand(12),rand(8),rand(12)];
if (material(target) == 'plant' && ticks_randomly(target),
particle('happy_villager', target, 2, 0.4)
);
random_tick(target)
)
);
handle_event('player_farts', 'longsneak');
```
## `/script event` command ## `/script event` command
used to display current events and bounded functions. use `add_to` ro register new event, or `remove_from` to used to display current events and bounded functions. use `add_to` to register a new event, or `remove_from` to
unbind a specific function from an event. Function to be bounded to an event needs to have the same number of unbind a specific function from an event. Function to be bounded to an event needs to have the same number of
parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load` parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load`
that have functions listed above will be automatically bounded and unbounded when script is unloaded. that handle specific built-in events will be automatically bounded, and unbounded when script is unloaded.
+2 -2
View File
@@ -33,10 +33,10 @@ the app state and all players interactions run in the same context, sharing defi
explicitly keyed with players, player names, uuids, etc. explicitly keyed with players, player names, uuids, etc.
Even for `'player'` scoped apps, you can access specific player app with with commandblocks using Even for `'player'` scoped apps, you can access specific player app with with commandblocks using
`/execute as <player> run script in <app> run ...`. `/execute as <player> run script in <app> run ...`.
To access global/server state for a player app, you need to disown the command from any player, To access global/server state for a player app, which you shouldn't do, you need to disown the command from any player,
so either use a command block, or any so either use a command block, or any
arbitrary entity: `/execute as @e[type=bat,limit=1] run script in <app> globals` for instance, however arbitrary entity: `/execute as @e[type=bat,limit=1] run script in <app> globals` for instance, however
running anything in the global scope for a `'player'` scoped app is not recommended. running anything in the global scope for a `'player'` scoped app is not intended.
* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will * `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will
stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down.
This is to allow to store multiple apps on the server/world and selectively decide which one should be running at This is to allow to store multiple apps on the server/world and selectively decide which one should be running at
+14 -1
View File
@@ -5,7 +5,19 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import carpet.commands.*; import carpet.commands.CounterCommand;
import carpet.commands.DistanceCommand;
import carpet.commands.DrawCommand;
import carpet.commands.InfoCommand;
import carpet.commands.LogCommand;
import carpet.commands.MobAICommand;
import carpet.commands.PerimeterInfoCommand;
import carpet.commands.PlayerCommand;
import carpet.commands.ProfileCommand;
import carpet.commands.ScriptCommand;
import carpet.commands.SpawnCommand;
import carpet.commands.TestCommand;
import carpet.commands.TickCommand;
import carpet.network.ServerNetworkHandler; import carpet.network.ServerNetworkHandler;
import carpet.helpers.HopperCounter; import carpet.helpers.HopperCounter;
import carpet.helpers.TickSpeed; import carpet.helpers.TickSpeed;
@@ -143,6 +155,7 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
{ {
ServerNetworkHandler.onPlayerJoin(player); ServerNetworkHandler.onPlayerJoin(player);
LoggerRegistry.playerConnected(player); LoggerRegistry.playerConnected(player);
scriptServer.onPlayerJoin(player);
extensions.forEach(e -> e.onPlayerLoggedIn(player)); extensions.forEach(e -> e.onPlayerLoggedIn(player));
} }
@@ -18,6 +18,7 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.block.Block; import net.minecraft.block.Block;
@@ -84,7 +85,7 @@ public class ScriptCommand
private static CompletableFuture<Suggestions> suggestCode( private static CompletableFuture<Suggestions> suggestCode(
CommandContext<ServerCommandSource> context, CommandContext<ServerCommandSource> context,
SuggestionsBuilder suggestionsBuilder SuggestionsBuilder suggestionsBuilder
) ) throws CommandSyntaxException
{ {
CarpetScriptHost currentHost = getHost(context); CarpetScriptHost currentHost = getHost(context);
String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT); String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
@@ -291,7 +292,7 @@ public class ScriptCommand
)?1:0)))))). )?1:0)))))).
then(literal("remove_from"). then(literal("remove_from").
then(argument("event", StringArgumentType.word()). then(argument("event", StringArgumentType.word()).
suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.publicEvents(CarpetServer.scriptServer).stream().map(ev -> ev.name).collect(Collectors.toList()) ,bb)). suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.publicEvents(CarpetServer.scriptServer).stream().filter(CarpetEventServer.Event::isNeeded).map(ev -> ev.name).collect(Collectors.toList()) ,bb)).
then(argument("call", StringArgumentType.greedyString()). then(argument("call", StringArgumentType.greedyString()).
suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.getEvent(StringArgumentType.getString(cc, "event"), CarpetServer.scriptServer).handler.callList.stream().map(CarpetEventServer.Callback::toString), bb)). suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.getEvent(StringArgumentType.getString(cc, "event"), CarpetServer.scriptServer).handler.callList.stream().map(CarpetEventServer.Callback::toString), bb)).
executes( (cc) -> CarpetServer.scriptServer.events.removeEventFromCommand( executes( (cc) -> CarpetServer.scriptServer.events.removeEventFromCommand(
@@ -311,14 +312,14 @@ public class ScriptCommand
suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.modules.keySet(), bb)). suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.modules.keySet(), bb)).
then(b).then(u).then(o).then(l).then(s).then(c).then(h).then(i).then(e).then(t)))); then(b).then(u).then(o).then(l).then(s).then(c).then(h).then(i).then(e).then(t))));
} }
private static CarpetScriptHost getHost(CommandContext<ServerCommandSource> context) private static CarpetScriptHost getHost(CommandContext<ServerCommandSource> context) throws CommandSyntaxException
{ {
CarpetScriptHost host; CarpetScriptHost host;
try try
{ {
String name = StringArgumentType.getString(context, "app").toLowerCase(Locale.ROOT); String name = StringArgumentType.getString(context, "app").toLowerCase(Locale.ROOT);
CarpetScriptHost parentHost = CarpetServer.scriptServer.modules.getOrDefault(name, CarpetServer.scriptServer.globalHost); CarpetScriptHost parentHost = CarpetServer.scriptServer.modules.getOrDefault(name, CarpetServer.scriptServer.globalHost);
host = parentHost.retrieveForExecution(context.getSource()); host = parentHost.retrieveOwnForExecution(context.getSource());
} }
catch (IllegalArgumentException ignored) catch (IllegalArgumentException ignored)
{ {
@@ -327,7 +328,7 @@ public class ScriptCommand
host.setChatErrorSnooper(context.getSource()); host.setChatErrorSnooper(context.getSource());
return host; return host;
} }
private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) throws CommandSyntaxException
{ {
CarpetScriptHost host = getHost(c); CarpetScriptHost host = getHost(c);
return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()); return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList());
@@ -350,7 +351,7 @@ public class ScriptCommand
} }
return 1; return 1;
} }
private static int listGlobals(CommandContext<ServerCommandSource> context, boolean all) private static int listGlobals(CommandContext<ServerCommandSource> context, boolean all) throws CommandSyntaxException
{ {
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
@@ -425,7 +426,7 @@ public class ScriptCommand
//host.resetErrorSnooper(); // lets say no need to reset the snooper in case something happens on the way //host.resetErrorSnooper(); // lets say no need to reset the snooper in case something happens on the way
} }
private static int invoke(CommandContext<ServerCommandSource> context, String call, BlockPos pos1, BlockPos pos2, String args) private static int invoke(CommandContext<ServerCommandSource> context, String call, BlockPos pos1, BlockPos pos2, String args) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -453,7 +454,7 @@ public class ScriptCommand
} }
private static int compute(CommandContext<ServerCommandSource> context, String expr) private static int compute(CommandContext<ServerCommandSource> context, String expr) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -463,7 +464,7 @@ public class ScriptCommand
}); });
} }
private static int scriptScan(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr) private static int scriptScan(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -512,7 +513,7 @@ public class ScriptCommand
private static int scriptFill(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr, private static int scriptFill(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr,
BlockStateArgument block, Predicate<CachedBlockPosition> replacement, String mode) BlockStateArgument block, Predicate<CachedBlockPosition> replacement, String mode) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -14,6 +14,7 @@ import carpet.script.value.Value;
import carpet.script.value.ValueConversions; import carpet.script.value.ValueConversions;
import carpet.settings.ParsedRule; import carpet.settings.ParsedRule;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
@@ -47,8 +48,9 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@@ -65,59 +67,84 @@ public class CarpetEventServer
public static class Callback public static class Callback
{ {
public final String host; public final String host;
public final String optionalTarget;
public final FunctionValue function; public final FunctionValue function;
public final List<Value> parametrizedArgs; public final List<Value> parametrizedArgs;
public Callback(String host, FunctionValue function, List<Value> parametrizedArgs) public Callback(String host, String target, FunctionValue function, List<Value> parametrizedArgs)
{ {
this.host = host; this.host = host;
this.function = function; this.function = function;
this.optionalTarget = target;
this.parametrizedArgs = parametrizedArgs==null?NOARGS:parametrizedArgs; this.parametrizedArgs = parametrizedArgs==null?NOARGS:parametrizedArgs;
} }
/** /**
* Used also in entity events * Used also in entity events
* @param asSource - entity command source * @param sender - entity command source
* @param runtimeArgs = options * @param runtimeArgs = options
*/ */
public boolean execute(ServerCommandSource asSource, List<Value> runtimeArgs) public boolean execute(ServerCommandSource sender, List<Value> runtimeArgs)
{ {
if (this.parametrizedArgs.isEmpty()) if (!this.parametrizedArgs.isEmpty())
return CarpetServer.scriptServer.runas(
asSource.withLevel(CarpetSettings.runPermissionLevel),
host, function, runtimeArgs);
else
{ {
List<Value> combinedArgs = new ArrayList<>(runtimeArgs); runtimeArgs = new ArrayList<>(runtimeArgs);
combinedArgs.addAll(this.parametrizedArgs); runtimeArgs.addAll(this.parametrizedArgs);
return CarpetServer.scriptServer.runas(
asSource.withLevel(CarpetSettings.runPermissionLevel),
host, function, combinedArgs);
} }
return CarpetServer.scriptServer.runEventCall(
sender.withLevel(CarpetSettings.runPermissionLevel),
host, optionalTarget, function, runtimeArgs
);
} }
/** /**
* Used also in entity events * Used also in entity events
* @param recipient - optional target player argument * @param sender - sender of the signal
* @param optionalRecipient - optional target player argument
* @param runtimeArgs = options * @param runtimeArgs = options
*/ */
public Boolean targetPlayer(ServerPlayerEntity recipient, List<Value> runtimeArgs) public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, List<Value> runtimeArgs)
{ {
// recipent of the call doesn't match the handlingHost
if (optionalRecipient != null && !optionalRecipient.getEntityName().equals(optionalTarget))
return 0;
List<Value> args = runtimeArgs; List<Value> args = runtimeArgs;
if (!this.parametrizedArgs.isEmpty()) if (!this.parametrizedArgs.isEmpty())
{ {
args = new ArrayList<>(runtimeArgs); args = new ArrayList<>(runtimeArgs);
args.addAll(this.parametrizedArgs); args.addAll(this.parametrizedArgs);
} }
return CarpetServer.scriptServer.runForPlayer(recipient, host, function, args); return CarpetServer.scriptServer.signal(sender, optionalRecipient, host, function, args, false);
} }
@Override @Override
public String toString() public String toString()
{ {
return function.getString()+((host==null)?"":"(from "+host+")"); return function.getString()+((host==null)?"":"(from "+host+(optionalTarget == null?"":"/"+optionalTarget)+")");
}
public static class Signature
{
String function;
String host;
String target;
public Signature(String fun, String h, String t)
{
function = fun;
host = h;
target = t;
}
}
public static Signature fromString(String str)
{
Pattern find = Pattern.compile("(\\w+)(?:\\(from (\\w+)(?:/(\\w+))?\\))?");
Matcher matcher = find.matcher(str);
if(matcher.matches())
{
return new Signature(matcher.group(1), matcher.group(2), matcher.group(3));
}
return new Signature(str, null, null);
} }
} }
@@ -129,7 +156,8 @@ public class CarpetEventServer
public ScheduledCall(CarpetContext context, FunctionValue function, List<Value> args, long dueTime) public ScheduledCall(CarpetContext context, FunctionValue function, List<Value> args, long dueTime)
{ {
super(context.host.getName(), function, args); // ignoring target as we will be always calling self
super(context.host.getName(), null, function, args);
this.ctx = context; this.ctx = context;
this.dueTime = dueTime; this.dueTime = dueTime;
} }
@@ -139,7 +167,7 @@ public class CarpetEventServer
*/ */
public void execute() public void execute()
{ {
CarpetServer.scriptServer.runas(ctx.origin, ctx.s, host, function, parametrizedArgs); CarpetServer.scriptServer.runScheduledCall(ctx.origin, ctx.s, host, (CarpetScriptHost) ctx.host, function, parametrizedArgs);
} }
@@ -173,7 +201,7 @@ public class CarpetEventServer
} }
} }
public int callCustom(ServerPlayerEntity receipient, List<Value> callArg) public int signal(ServerCommandSource sender, ServerPlayerEntity optinoalReceipient, List<Value> callArg)
{ {
if (callList.isEmpty()) return 0; if (callList.isEmpty()) return 0;
//List<Callback> fails = new ArrayList<>(); //List<Callback> fails = new ArrayList<>();
@@ -181,15 +209,13 @@ public class CarpetEventServer
int successes = 0; int successes = 0;
for (Callback call: callList) for (Callback call: callList)
{ {
Boolean result = call.targetPlayer(receipient, callArg); successes += Math.max(0, call.signal(sender, optinoalReceipient, callArg));
//if (result == Boolean.FALSE) fails.add(call);
if (result == Boolean.TRUE) successes++;
} }
//for (Callback call : fails) callList.remove(call); //for (Callback call : fails) callList.remove(call);
return successes; return successes;
} }
public boolean addEventCall(ServerCommandSource source, String hostName, String funName, Function<ScriptHost, Boolean> verifier) public boolean addFromExternal(ServerCommandSource source, String hostName, String funName, Consumer<ScriptHost> hostOnEventHandler)
{ {
ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName); ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName);
if (host == null) if (host == null)
@@ -198,11 +224,7 @@ public class CarpetEventServer
Messenger.m(source, "r Unknown app "+hostName); Messenger.m(source, "r Unknown app "+hostName);
return false; return false;
} }
if (!verifier.apply(host)) hostOnEventHandler.accept(host);
{
Messenger.m(source, "r Global event can only be added to apps with global scope");
return false;
}
FunctionValue udf = host.getFunction(funName); FunctionValue udf = host.getFunction(funName);
if (udf == null || udf.getArguments().size() != reqArgs) if (udf == null || udf.getArguments().size() != reqArgs)
{ {
@@ -210,11 +232,24 @@ public class CarpetEventServer
Messenger.m(source, "r Callback doesn't expect required number of arguments: "+reqArgs); Messenger.m(source, "r Callback doesn't expect required number of arguments: "+reqArgs);
return false; return false;
} }
String target = null;
if (host.isPerUser())
{
try
{
target = source.getPlayer().getEntityName();
}
catch (CommandSyntaxException e)
{
Messenger.m(source, "r Cannot add event to a player scoped app from a command without a player context");
return false;
}
}
//all clear //all clear
//remove duplicates //remove duplicates
removeEventCall(hostName, udf.getString()); removeEventCall(hostName, target, udf.getString());
callList.add(new Callback(hostName, udf, null)); callList.add(new Callback(hostName, target, udf, null));
return true; return true;
} }
public boolean addEventCallInternal(ScriptHost host, FunctionValue function, List<Value> args) public boolean addEventCallInternal(ScriptHost host, FunctionValue function, List<Value> args)
@@ -225,20 +260,39 @@ public class CarpetEventServer
} }
//all clear //all clear
//remove duplicates //remove duplicates
removeEventCall(host.getName(), function.getString()); removeEventCall(host.getName(), host.user, function.getString());
callList.add(new Callback(host.getName(), function, args)); callList.add(new Callback(host.getName(), host.user, function, args));
return true; return true;
} }
public void removeEventCall(String hostName, String funName) public void removeEventCall(String hostName, String target, String funName)
{ {
callList.removeIf((c)-> c.function.getString().equals(funName) && ( hostName == c.host || (c.host != null && c.host.equalsIgnoreCase(hostName) )) ); callList.removeIf((c)-> c.function.getString().equals(funName)
&& (Objects.equals(c.host, hostName))
&& (Objects.equals(c.optionalTarget, target))
);
} }
public void removeAllCalls(CarpetScriptHost host) public void removeAllCalls(CarpetScriptHost host)
{ {
callList.removeIf((c)-> c.host != null && c.host.equals(host.getName())); callList.removeIf((c)-> (Objects.equals(c.host, host.getName()))
&& (Objects.equals(c.optionalTarget, host.user)));
} }
public void createChildEvents(CarpetScriptHost host)
{
List<Callback> copyCalls = new ArrayList<>();
callList.forEach((c)->
{
if ((Objects.equals(c.host, host.getName()))
&& c.optionalTarget == null)
{
copyCalls.add(new Callback(c.host, host.user, c.function, c.parametrizedArgs));
}
});
callList.addAll(copyCalls);
}
public void clearEverything() public void clearEverything()
{ {
callList.clear(); callList.clear();
@@ -801,6 +855,12 @@ public class CarpetEventServer
host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.removeAllCalls(host)); host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.removeAllCalls(host));
} }
public static void transferAllHostEventsToChild(CarpetScriptHost host)
{
byName.values().forEach((e) -> e.handler.createChildEvents(host));
host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.createChildEvents(host));
}
public static void clearAllBuiltinEvents() public static void clearAllBuiltinEvents()
{ {
byName.values().forEach(e -> e.handler.clearEverything()); byName.values().forEach(e -> e.handler.clearEverything());
@@ -888,7 +948,7 @@ public class CarpetEventServer
{ {
return false; return false;
} }
boolean added = ev.handler.addEventCall(source, host, funName, h -> canAddEvent(ev, h)); boolean added = ev.handler.addFromExternal(source, host, funName, h -> onEventAddedToHost(ev, h));
if (added) Messenger.m(source, "gi Added " + funName + " to " + event); if (added) Messenger.m(source, "gi Added " + funName + " to " + event);
return added; return added;
} }
@@ -897,8 +957,7 @@ public class CarpetEventServer
{ {
// this is globals only // this is globals only
Event ev = Event.byName.get(event); Event ev = Event.byName.get(event);
if (!canAddEvent(ev, host)) onEventAddedToHost(ev, host);
throw new InternalExpressionException("Global event "+event+" can only be added to apps with global scope");
boolean success = ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); boolean success = ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args);
if (!success) throw new InternalExpressionException("Global event "+event+" requires "+ev.handler.reqArgs+", not "+(function.getNumParams()-((args==null)?0:args.size()))); if (!success) throw new InternalExpressionException("Global event "+event+" requires "+ev.handler.reqArgs+", not "+(function.getNumParams()-((args==null)?0:args.size())));
} }
@@ -906,22 +965,21 @@ public class CarpetEventServer
public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List<Value> args) public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List<Value> args)
{ {
Event ev = Event.getOrCreateCustom(event, host.getScriptServer()); Event ev = Event.getOrCreateCustom(event, host.getScriptServer());
if (!canAddEvent(ev, host)) onEventAddedToHost(ev, host);
throw new InternalExpressionException("Global built-in event "+event+" can only be handled in apps with global scope");
return ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); return ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args);
} }
public int signalEvent(String event, CarpetScriptHost host, ServerPlayerEntity player, List<Value> callArgs) public int signalEvent(String event, CarpetContext cc, ServerPlayerEntity optionalTarget, List<Value> callArgs)
{ {
Event ev = Event.getEvent(event, host.getScriptServer()); Event ev = Event.getEvent(event, ((CarpetScriptHost)cc.host).getScriptServer());
if (ev == null) return -1; if (ev == null) return -1;
return ev.handler.callCustom(player, callArgs); return ev.handler.signal(cc.s, optionalTarget, callArgs);
} }
private boolean canAddEvent(Event event, ScriptHost host) private void onEventAddedToHost(Event event, ScriptHost host)
{ {
if (event.deprecated()) host.issueDeprecation(event.name+" event"); if (event.deprecated()) host.issueDeprecation(event.name+" event");
return !(event.globalOnly && (host.perUser || host.parent != null)); //return !(event.globalOnly && (host.perUser || host.parent != null));
} }
public boolean removeEventFromCommand(ServerCommandSource source, String event, String funName) public boolean removeEventFromCommand(ServerCommandSource source, String event, String funName)
@@ -932,8 +990,8 @@ public class CarpetEventServer
Messenger.m(source, "r Unknown event: " + event); Messenger.m(source, "r Unknown event: " + event);
return false; return false;
} }
Pair<String,String> call = decodeCallback(funName); Callback.Signature call = Callback.fromString(funName);
ev.handler.removeEventCall(call.getLeft(), call.getRight()); ev.handler.removeEventCall(call.host, call.target, call.function);
// could verified if actually removed // could verified if actually removed
Messenger.m(source, "gi Removed event: " + funName + " from "+event); Messenger.m(source, "gi Removed event: " + funName + " from "+event);
return true; return true;
@@ -949,13 +1007,15 @@ public class CarpetEventServer
public void removeBuiltInEvent(String event, CarpetScriptHost host, String funName) public void removeBuiltInEvent(String event, CarpetScriptHost host, String funName)
{ {
Event ev = Event.getEvent(event, host.getScriptServer()); Event ev = Event.getEvent(event, host.getScriptServer());
if (ev != null) ev.handler.removeEventCall(host.getName(), funName); if (ev != null) ev.handler.removeEventCall(host.getName(), host.user, funName);
} }
public void removeAllHostEvents(CarpetScriptHost host) public void removeAllHostEvents(CarpetScriptHost host)
{ {
// remove event handlers // remove event handlers
Event.removeAllHostEvents(host); Event.removeAllHostEvents(host);
if (host.isPerUser())
for (ScriptHost child: host.userHosts.values()) Event.removeAllHostEvents((CarpetScriptHost) child);
// remove scheduled calls // remove scheduled calls
scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName())); scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName()));
} }
@@ -1,6 +1,7 @@
package carpet.script; package carpet.script;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.api.Auxiliary; import carpet.script.api.Auxiliary;
import carpet.script.argument.FunctionArgument; import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.Module; import carpet.script.bundled.Module;
@@ -15,7 +16,6 @@ import carpet.script.value.MapValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.StringValue; import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import carpet.CarpetSettings;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@@ -117,7 +117,7 @@ public class CarpetScriptHost extends ScriptHost
private static int execute(CommandContext<ServerCommandSource> ctx, String hostName, FunctionArgument<Value> funcSpec, List<String> paramNames) throws CommandSyntaxException private static int execute(CommandContext<ServerCommandSource> ctx, String hostName, FunctionArgument<Value> funcSpec, List<String> paramNames) throws CommandSyntaxException
{ {
CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveForExecution(ctx.getSource()); CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource());
List<String> argNames = funcSpec.function.getArguments(); List<String> argNames = funcSpec.function.getArguments();
if ((argNames.size()-funcSpec.args.size()) != paramNames.size()) if ((argNames.size()-funcSpec.args.size()) != paramNames.size())
throw new SimpleCommandExceptionType(new LiteralText("Target function "+funcSpec.function.getPrettyString()+" as wrong number of arguments, required "+paramNames.size()+", found "+argNames.size()+" with "+funcSpec.args.size()+" provided")).create(); throw new SimpleCommandExceptionType(new LiteralText("Target function "+funcSpec.function.getPrettyString()+" as wrong number of arguments, required "+paramNames.size()+", found "+argNames.size()+" with "+funcSpec.args.size()+" provided")).create();
@@ -177,6 +177,14 @@ public class CarpetScriptHost extends ScriptHost
return new CarpetScriptHost(scriptServer, main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp); return new CarpetScriptHost(scriptServer, main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp);
} }
@Override
protected void transferToChild(ScriptHost host)
{
super.transferToChild(host);
// transfer Events
CarpetEventServer.Event.transferAllHostEventsToChild((CarpetScriptHost) host);
}
@Override @Override
public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function) public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function)
{ {
@@ -319,38 +327,58 @@ public class CarpetScriptHost extends ScriptHost
} }
} }
public CarpetScriptHost retrieveForExecution(ServerCommandSource source) public List<CarpetScriptHost> retrieveForExecution(ServerCommandSource source, String optionalTarget)
{ {
CarpetScriptHost host = this; List<CarpetScriptHost> targets = new ArrayList<>();
if (perUser) if (perUser)
{ {
try if (optionalTarget == null)
{ {
ServerPlayerEntity player = source.getPlayer(); for (ServerPlayerEntity player : source.getMinecraftServer().getPlayerManager().getPlayerList())
host = (CarpetScriptHost) retrieveForExecution(player.getName().getString()); {
CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName());
targets.add(host);
if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource());
}
} }
catch (CommandSyntaxException e) else
{ {
host = (CarpetScriptHost) retrieveForExecution((String) null); ServerPlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget);
if (player != null)
{
CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName());
targets.add(host);
if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource());
}
} }
} }
if (host.errorSnooper == null) host.setChatErrorSnooper(source); else
return host; {
targets.add(this);
if (this.errorSnooper == null) this.setChatErrorSnooper(source);
}
return targets;
} }
public CarpetScriptHost retrieveForExecution(ServerPlayerEntity target) public CarpetScriptHost retrieveOwnForExecution(ServerCommandSource source) throws CommandSyntaxException
{ {
// allowing only run
if (perUser == (target == null)) return null;
if (!perUser) if (!perUser)
{ {
if (errorSnooper == null) setChatErrorSnooper(this.scriptServer.server.getCommandSource()); if (errorSnooper == null) setChatErrorSnooper(source);
return this; return this;
} }
// user based // user based
CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(target.getName().getString()); ServerPlayerEntity player;
if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(target.getCommandSource()); try
{
player = source.getPlayer();
}
catch (CommandSyntaxException ignored)
{
throw new SimpleCommandExceptionType(new LiteralText("Cannot run player based apps without the player context")).create();
}
CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(player.getEntityName());
if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(source);
return userHost; return userHost;
} }
@@ -1,8 +1,8 @@
package carpet.script; package carpet.script;
import carpet.CarpetSettings;
import carpet.script.argument.FunctionArgument; import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.BundledModule; import carpet.script.bundled.BundledModule;
import carpet.CarpetSettings;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.script.bundled.FileModule; import carpet.script.bundled.FileModule;
import carpet.script.bundled.Module; import carpet.script.bundled.Module;
@@ -296,6 +296,11 @@ public class CarpetScriptServer
Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app"); Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app");
return false; return false;
} }
if (newHost.isPerUser())
{
// that will provide player hosts right at the startup
newHost.retrieveForExecution(source, null);
}
long end = System.nanoTime(); long end = System.nanoTime();
CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms"); CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms");
return true; return true;
@@ -325,8 +330,8 @@ public class CarpetScriptServer
requires((player) -> modules.containsKey(hostName) && useValidator.apply(player)). requires((player) -> modules.containsKey(hostName) && useValidator.apply(player)).
executes( (c) -> executes( (c) ->
{ {
Value response = modules.get(hostName).retrieveForExecution(c.getSource()). CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
handleCommandLegacy(c.getSource(),"__command", null, ""); Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger(); return (int)response.readInteger();
}); });
@@ -355,15 +360,15 @@ public class CarpetScriptServer
then(literal(function). then(literal(function).
requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null). requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null).
executes((c) -> { executes((c) -> {
Value response = modules.get(hostName).retrieveForExecution(c.getSource()). CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
handleCommandLegacy(c.getSource(), function, null, ""); Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString()); if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString());
return (int) response.readInteger(); return (int) response.readInteger();
}). }).
then(argument("args...", StringArgumentType.greedyString()). then(argument("args...", StringArgumentType.greedyString()).
executes( (c) -> { executes( (c) -> {
Value response = modules.get(hostName).retrieveForExecution(c.getSource()). CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
handleCommandLegacy(c.getSource(), function,null, StringArgumentType.getString(c, "args...")); Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args..."));
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger(); return (int)response.readInteger();
}))); })));
@@ -394,54 +399,84 @@ public class CarpetScriptServer
return true; return true;
} }
public boolean runas(ServerCommandSource source, String hostname, FunctionValue udf, List<Value> argv) public boolean runEventCall(ServerCommandSource sender, String hostname, String optionalTarget, FunctionValue udf, List<Value> argv)
{
return runas(BlockPos.ORIGIN, source, hostname, udf, argv);
}
public boolean runas(BlockPos origin, ServerCommandSource source, String hostname, FunctionValue udf, List<Value> argv)
{ {
CarpetScriptHost host = globalHost; CarpetScriptHost host = globalHost;
try if (hostname != null)
host = modules.get(hostname);
if (host == null) return false;
// dummy call for player apps that reside on the global copy - do not run them, but report as successes.
if (host.isPerUser() && optionalTarget==null) return true;
ServerPlayerEntity target = null;
if (optionalTarget != null)
{ {
if (hostname != null) target = sender.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget);
host = modules.get(hostname).retrieveForExecution(source); if (target == null) return false;
host.callUDF(origin, source, udf, argv);
} }
catch (NullPointerException | InvalidCallbackException npe) int successes = signal(sender, target, hostname, udf, argv, true );
{ return successes >= 0;
return false;
}
return true;
} }
public Boolean runForPlayer(ServerPlayerEntity recipient, String hostname, FunctionValue udf, List<Value> argv) public void runScheduledCall(BlockPos origin, ServerCommandSource source, String hostname, CarpetScriptHost host, FunctionValue udf, List<Value> argv)
{
if (hostname != null && !modules.containsKey(hostname)) // well - scheduled call app got unloaded
return;
try
{
host.callUDF(origin, source, udf, argv);
}
catch (NullPointerException | InvalidCallbackException ignored)
{
}
}
/**
* returns number of successful calls in the host or -1 if failed exceptionally
* @param sender
* @param optionalRecipient
* @param hostname
* @param udf
* @param argv
* @param reportFails - returns -1 when any call failed
* @return
*/
public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, String hostname, FunctionValue udf, List<Value> argv, boolean reportFails)
{ {
CarpetScriptHost host;
ServerCommandSource source;
if (hostname == null) if (hostname == null)
{ {
if (recipient != null) return null; if (optionalRecipient != null) return 0;
host = globalHost; try
source = server.getCommandSource(); {
globalHost.callUDF(BlockPos.ORIGIN, sender, udf, argv);
}
catch (NullPointerException | InvalidCallbackException npe)
{
return reportFails?-1:0;
}
return 1;
} }
else String hostRecipient = optionalRecipient == null?null:optionalRecipient.getEntityName();
ServerCommandSource source = (optionalRecipient == null)? sender : optionalRecipient.getCommandSource().withLevel(CarpetSettings.runPermissionLevel);
int successes = 0;
for (CarpetScriptHost host : modules.get(hostname).retrieveForExecution(sender, hostRecipient))
{ {
host = modules.get(hostname).retrieveForExecution(recipient); // getPlayer will always return nonnull cause retrieve for execution only returns hosts for existing players.
if (host == null) // not applicable ServerCommandSource executingSource = host.perUser
return null; ? source.getMinecraftServer().getPlayerManager().getPlayer(host.user).getCommandSource()
source = (recipient == null)? server.getCommandSource() : recipient.getCommandSource(); : source.getMinecraftServer().getCommandSource();
try
{
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
}
catch (NullPointerException | InvalidCallbackException npe)
{
if (reportFails) return -1;
continue;
}
successes ++;
} }
try return successes;
{
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
}
catch (NullPointerException | InvalidCallbackException npe)
{
return false;
}
return true;
} }
public void tick() public void tick()
@@ -461,6 +496,23 @@ public class CarpetScriptServer
} }
} }
public void onPlayerJoin(ServerPlayerEntity player)
{
modules.values().forEach(h ->
{
if (h.isPerUser())
{
try
{
h.retrieveOwnForExecution(player.getCommandSource());
}
catch (CommandSyntaxException ignored)
{
}
}
});
}
static class TransferData static class TransferData
{ {
boolean perUser; boolean perUser;
@@ -9,6 +9,7 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -19,7 +20,7 @@ import java.util.Map;
public class EntityEventsGroup public class EntityEventsGroup
{ {
private final Map<Event, Map<String, CarpetEventServer.Callback>> actions; private final Map<Event, Map<Pair<String,String>, CarpetEventServer.Callback>> actions;
private final Entity entity; private final Entity entity;
public EntityEventsGroup(Entity e) public EntityEventsGroup(Entity e)
{ {
@@ -30,37 +31,47 @@ public class EntityEventsGroup
public void onEvent(Event type, Object... args) public void onEvent(Event type, Object... args)
{ {
if (actions.isEmpty()) return; // most of the cases, trying to be nice if (actions.isEmpty()) return; // most of the cases, trying to be nice
Map<String, CarpetEventServer.Callback> actionSet = actions.get(type); Map<Pair<String,String>, CarpetEventServer.Callback> actionSet = actions.get(type);
if (actionSet == null) return; if (actionSet == null) return;
for (Iterator<Map.Entry<String, CarpetEventServer.Callback>> iterator = actionSet.entrySet().iterator(); iterator.hasNext(); ) for (Iterator<Map.Entry<Pair<String,String>, CarpetEventServer.Callback>> iterator = actionSet.entrySet().iterator(); iterator.hasNext(); )
{ {
Map.Entry<String, CarpetEventServer.Callback> action = iterator.next(); Map.Entry<Pair<String,String>, CarpetEventServer.Callback> action = iterator.next();
ScriptHost host = CarpetServer.scriptServer.getHostByName(action.getKey()); Pair<String,String> key = action.getKey();
ScriptHost host = CarpetServer.scriptServer.getHostByName(key.getLeft());
if (host == null) if (host == null)
{ {
iterator.remove(); iterator.remove();
continue; continue;
} }
if (key.getRight() != null)
{
if (entity.getServer().getPlayerManager().getPlayer(key.getRight())==null)
{
iterator.remove();
continue;
}
}
if (!type.call(action.getValue(), entity, args)) if (!type.call(action.getValue(), entity, args))
iterator.remove(); iterator.remove();
} }
if (actionSet.isEmpty()) actions.remove(type); if (actionSet.isEmpty()) actions.remove(type);
} }
public void addEvent(Event type, String hostName, FunctionValue fun, List<Value> extraargs) public void addEvent(Event type, ScriptHost host, FunctionValue fun, List<Value> extraargs)
{ {
Pair<String,String> key = Pair.of(host.getName(), host.user);
if (fun != null) if (fun != null)
{ {
CarpetEventServer.Callback call = type.create(hostName, fun, extraargs); CarpetEventServer.Callback call = type.create(key, fun, extraargs);
if (call == null) if (call == null)
throw new InternalExpressionException("wrong number of arguments for callback, required "+type.argcount); throw new InternalExpressionException("wrong number of arguments for callback, required "+type.argcount);
actions.computeIfAbsent(type, k -> new HashMap<>()).put(hostName, call); actions.computeIfAbsent(type, k -> new HashMap<>()).put(key, call);
} }
else else
{ {
actions.computeIfAbsent(type, k -> new HashMap<>()).remove(hostName); actions.computeIfAbsent(type, k -> new HashMap<>()).remove(key);
if (actions.get(type).isEmpty()) if (actions.get(type).isEmpty())
actions.remove(type); actions.remove(type);
} }
@@ -107,13 +118,13 @@ public class EntityEventsGroup
argcount = args+1; // entity is not extra argcount = args+1; // entity is not extra
byName.put(identifier, this); byName.put(identifier, this);
} }
public CarpetEventServer.Callback create(String hostName, FunctionValue function, List<Value> extraArgs) public CarpetEventServer.Callback create(Pair<String,String> key, FunctionValue function, List<Value> extraArgs)
{ {
if ((function.getArguments().size()-(extraArgs == null ? 0 : extraArgs.size())) != argcount) if ((function.getArguments().size()-(extraArgs == null ? 0 : extraArgs.size())) != argcount)
{ {
return null; return null;
} }
return new CarpetEventServer.Callback(hostName, function, extraArgs); return new CarpetEventServer.Callback(key.getLeft(), key.getRight(), function, extraArgs);
} }
public boolean call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args) public boolean call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args)
{ {
+10 -4
View File
@@ -318,15 +318,21 @@ public abstract class ScriptHost
ScriptHost oldUserHost = userHosts.get(user); ScriptHost oldUserHost = userHosts.get(user);
if (oldUserHost != null) return oldUserHost; if (oldUserHost != null) return oldUserHost;
ScriptHost userHost = this.duplicate(); ScriptHost userHost = this.duplicate();
userHost.modules.putAll(this.modules);
this.moduleData.forEach((key, value) -> userHost.moduleData.put(key, new ModuleData(key, value)));
// fixing imports
userHost.moduleData.forEach((module, data) -> data.setImportsBasedOn(userHost, this.moduleData.get(data.parent)));
userHost.user = user; userHost.user = user;
this.transferToChild(userHost);
userHosts.put(user, userHost); userHosts.put(user, userHost);
return userHost; return userHost;
} }
protected void transferToChild(ScriptHost host)
{
// adding imports
host.modules.putAll(this.modules);
this.moduleData.forEach((key, value) -> host.moduleData.put(key, new ModuleData(key, value)));
// fixing imports
host.moduleData.forEach((module, data) -> data.setImportsBasedOn(host, this.moduleData.get(data.parent)));
}
public void handleExpressionException(String msg, ExpressionException exc) public void handleExpressionException(String msg, ExpressionException exc)
{ {
System.out.println(msg+": "+exc); System.out.println(msg+": "+exc);
@@ -961,6 +961,7 @@ public class Auxiliary {
{ {
if (lv.size() == 0) if (lv.size() == 0)
throw new InternalExpressionException("'signal' requires at least one argument"); throw new InternalExpressionException("'signal' requires at least one argument");
CarpetContext cc = (CarpetContext)c;
CarpetScriptServer server = ((CarpetScriptHost)c.host).getScriptServer(); CarpetScriptServer server = ((CarpetScriptHost)c.host).getScriptServer();
String eventName = lv.get(0).evalValue(c).getString(); String eventName = lv.get(0).evalValue(c).getString();
// no such event yet // no such event yet
@@ -975,10 +976,10 @@ public class Auxiliary {
args = FunctionValue.resolveArgs(lv.subList(2, lv.size()), c, t); args = FunctionValue.resolveArgs(lv.subList(2, lv.size()), c, t);
} }
} }
int counts = ((CarpetScriptHost)c.host).getScriptServer().events.signalEvent(eventName, (CarpetScriptHost) c.host, player, args); int counts = ((CarpetScriptHost)c.host).getScriptServer().events.signalEvent(eventName, cc, player, args);
if (counts < 0) return LazyValue.NULL; if (counts < 0) return LazyValue.NULL;
Value ret = new NumericValue(counts); Value ret = new NumericValue(counts);
return (cc, tt) -> ret; return (c_, t_) -> ret;
}); });
} }
@@ -1372,7 +1372,7 @@ public class EntityValue extends Value
EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName); EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName);
if (event == null) if (event == null)
throw new InternalExpressionException("Unknown entity event: " + eventName); throw new InternalExpressionException("Unknown entity event: " + eventName);
((EntityInterface)entity).getEventContainer().addEvent(event, cc.host.getName(), fun, args); ((EntityInterface)entity).getEventContainer().addEvent(event, cc.host, fun, args);
} }
@Override @Override
@@ -90,12 +90,9 @@ calculate(end)->(
) )
); );
_carpets() -> system_info('world_carpet_rules'):'carpets'=='true';
on_player_places_block(player, item_tuple, hand, block) -> on_player_places_block(player, item_tuple, hand, block) ->
( (
if(block==global_assist_block && _carpets(), if(block==global_assist_block,
effective_pos = pos(block)+[0.5, 0, 0.5]; effective_pos = pos(block)+[0.5, 0, 0.5];
if(global_current_start==null||player~'sneaking',//wont complain for first carpet if(global_current_start==null||player~'sneaking',//wont complain for first carpet
set_start(effective_pos) set_start(effective_pos)
@@ -107,7 +104,7 @@ on_player_places_block(player, item_tuple, hand, block) ->
on_player_uses_item(player, item_tuple, hand) -> on_player_uses_item(player, item_tuple, hand) ->
( (
if (item_tuple:0 == global_assist_block && hand == 'mainhand' && _carpets(), if (item_tuple:0 == global_assist_block && hand == 'mainhand',
if (player ~'sneaking', if (player ~'sneaking',
global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1); global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1);
display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode)); display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode));