diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 9bd73cf1..57b1f092 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1270,16 +1270,22 @@ expr1 ; expr2 => expr2 // with expr1 as a side-effect ## Global variables -All defined functions are compiled, stored persistently, and available globally - accessible to all other scripts. +All defined functions are compiled, stored persistently, and available globally within the app. Functions can only be undefined via call to `undef('fun')`, which would erase global entry for function `fun`. -Since all variables have local scope inside each function, one way to share large objects is via global variables +Since all variables have local scope inside each function, or each command script, + global variables is a way to share the global state. + +Any variable that is used with a name that starts with `'global_'` will be stored and accessible globally, not only +inside the current scope. If used directly in the chat window with the default app, it will persist across calls to `/script` +function. Like functions, which are global, global variables can only be undefined via `undef`. + +For apps running in `'global'` scope - all players will share the same global variables and defined functions, +and with `player` scope, each player hosts its own state for each app, so function and global_variables are distinct. -Any variable that is used with a name that starts with `'global_'` will be stored and accessible globally, not, -inside current scope. It will also persist across scripts, so if a procedure needs to use its own construct, it -needs to define it, or initialize it explicitly, or undefine it via `undef`
-a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1,2,3,1,1] +/script run a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1, 2, 3, 1, 1] +/script run a(); a(); global_list // => [1, 2, 3, 1, 1, 1, 1]### `Operator ->` @@ -3468,40 +3474,49 @@ inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack # Scarpet events system -Provides the ability to execute specific function whenever an event occurs. The functions to be registered need to -conform with the arguments to the event specification. When loading module functions, each function that starts -with `__on_...` and has the required arguments, will be bound automatically. In case of player specific modules, -all player action events will be directed to the appropriate player space, and all tick events will be executed in -the global context, so its not a good idea to mix these two, so use either of these, or use commands to call tick -events directly, or handle player specific data inside an app. +Scarpet provides the ability to execute specific function whenever an event occurs. The functions to be subscribed for an event +need to conform with the arguments to the event specification. There are several built-in events triggered when certain in-game +events occur, but app designers can create their own events and trigger them across all loaded apps. -Most events strive to report right before they take an effect in the game. The purpose of that is that this give a choice +When loading the app, each function that starts +with `__on_
+foo(a) -> print(a);
+handle_event('boohoo', 'foo');
+
+bar(a, b, c) -> print([a, b, c]);
+handle_event('boohoo', 'bar', 2, 3) // using b = 2, c = 3, a - passed by the caller
+
+handle_event('tick', _() -> foo('tick happened')); // built-in event
+
+handle_event('tick', null) // nah, ima good, kthxbai
+
+
+### `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
+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
+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.
+Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover
+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.
+
+
+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('tick') // triggering tick event in all loaded apps
+
+
+## Custom events example
+
+The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous
+squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but
+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
+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
+// detecting event in a tick loop
+__config() -> {'scope' -> 'global'};
+
+global_sneaks = {};
+__on_tick() -> for(player('all'),
+ if (_~'sneaking',
+ sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0);
+ 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:_)
+ )
+)
+```
+
+```
+// 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
used to display current events and bounded functions. use `add_to` ro register 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