Skip to content

Commit

Permalink
Examples for the experimental features. (#7444)
Browse files Browse the repository at this point in the history
* Clean up existing example issues.

* Fix some issues with queues.

* Add an example structure.

* Fix the old tests.

* Add example scripts for the experimental features.

* Fix toString.

* Apply suggestions from code review

* Change text type.

* Fix spacing.

* Change node names.

* Fix issues from merge.

* Update src/main/resources/scripts/-examples/experimental features/for loops.sk

Co-authored-by: Patrick Miller <[email protected]>

* Fix some more merge errors.

---------

Co-authored-by: Patrick Miller <[email protected]>
  • Loading branch information
Moderocky and APickledWalrus authored Jan 15, 2025
1 parent c9ae203 commit 1fab937
Show file tree
Hide file tree
Showing 21 changed files with 882 additions and 276 deletions.
6 changes: 3 additions & 3 deletions src/main/java/ch/njol/skript/expressions/ExprQueue.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class ExprQueue extends SimpleExpression<SkriptQueue> {

static {
Skript.registerExpression(ExprQueue.class, SkriptQueue.class, ExpressionType.COMBINED,
"[a] new queue [(of|with) %-objects%]");
"[a] [new] queue [(of|with) %-objects%]");
}

private @Nullable Expression<?> contents;
Expand Down Expand Up @@ -88,8 +88,8 @@ public Class<? extends SkriptQueue> getReturnType() {
@Override
public String toString(@Nullable Event event, boolean debug) {
if (contents == null)
return "a new queue";
return "a new queue of " + contents.toString(event, debug);
return "a queue";
return "a queue of " + contents.toString(event, debug);
}

}
1 change: 1 addition & 0 deletions src/main/java/ch/njol/skript/registrations/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Experimental feature toggles as provided by Skript itself.
*/
public enum Feature implements Experiment {
EXAMPLES("examples", LifeCycle.STABLE),
QUEUES("queues", LifeCycle.EXPERIMENTAL),
FOR_EACH_LOOPS("for loop", LifeCycle.EXPERIMENTAL, "for [each] loop[s]"),
SCRIPT_REFLECTION("reflection", LifeCycle.EXPERIMENTAL, "[script] reflection"),
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/ch/njol/skript/sections/SecFor.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ public boolean init(Expression<?>[] exprs,
.getName() + " implements Container but is missing the required @ContainerType annotation");
this.expression = new ContainerExpression((Expression<? extends Container<?>>) expression, type.value());
}
if (expression.isSingle()) {
if (this.getParser().hasExperiment(Feature.QUEUES) // Todo: change this if other iterable things are added
&& expression.isSingle()
&& (expression instanceof Variable<?> || Iterable.class.isAssignableFrom(expression.getReturnType()))) {
// Some expressions return one thing but are potentially iterable anyway, e.g. queues
super.iterableSingle = true;
} else if (expression.isSingle()) {
Skript.error("Can't loop '" + expression + "' because it's only a single value");
return false;
}
Expand Down
35 changes: 26 additions & 9 deletions src/main/java/ch/njol/skript/sections/SecLoop.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ch.njol.skript.lang.*;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.ContainerExpression;
import ch.njol.skript.registrations.Feature;
import ch.njol.skript.util.Container;
import ch.njol.skript.util.Container.ContainerType;
import ch.njol.skript.util.LiteralUtils;
Expand All @@ -19,10 +20,7 @@
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.*;

@Name("Loop")
@Description({
Expand Down Expand Up @@ -85,6 +83,7 @@ public class SecLoop extends LoopSection {
private boolean guaranteedToLoop;
private Object nextValue = null;
private boolean loopPeeking;
protected boolean iterableSingle;

@Override
@SuppressWarnings("unchecked")
Expand All @@ -107,7 +106,12 @@ public boolean init(Expression<?>[] exprs,
this.expression = new ContainerExpression((Expression<? extends Container<?>>) expression, type.value());
}

if (expression.isSingle()) {
if (this.getParser().hasExperiment(Feature.QUEUES) // Todo: change this if other iterable things are added
&& expression.isSingle()
&& (expression instanceof Variable<?> || Iterable.class.isAssignableFrom(expression.getReturnType()))) {
// Some expressions return one thing but are potentially iterable anyway, e.g. queues
this.iterableSingle = true;
} else if (expression.isSingle()) {
Skript.error("Can't loop '" + expression + "' because it's only a single value");
return false;
}
Expand All @@ -124,11 +128,24 @@ public boolean init(Expression<?>[] exprs,
protected @Nullable TriggerItem walk(Event event) {
Iterator<?> iter = iteratorMap.get(event);
if (iter == null) {
iter = expression instanceof Variable variable ? variable.variablesIterator(event) : expression.iterator(event);
if (iter != null && iter.hasNext()) {
iteratorMap.put(event, iter);
if (iterableSingle) {
Object value = expression.getSingle(event);
if (value instanceof Iterable<?> iterable) {
iter = iterable.iterator();
// Guaranteed to be ordered so we try it first
} else if (value instanceof Container<?> container) {
iter = container.containerIterator();
} else {
iter = Collections.singleton(value).iterator();
}
} else {
iter = null;
iter = expression instanceof Variable<?> variable ? variable.variablesIterator(event) :
expression.iterator(event);
if (iter != null && iter.hasNext()) {
iteratorMap.put(event, iter);
} else {
iter = null;
}
}
}

Expand Down
74 changes: 74 additions & 0 deletions src/main/java/ch/njol/skript/structures/StructExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ch.njol.skript.structures;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.doc.*;
import ch.njol.skript.lang.Literal;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.function.FunctionEvent;
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.skript.registrations.Feature;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.entry.EntryContainer;
import org.skriptlang.skript.lang.structure.Structure;

@NoDoc
@Name("Example")
@Description({
"Examples are structures that are parsed, but will never be run.",
"They are used as miniature tutorials for demonstrating code snippets in the example files.",
"Scripts containing an example are seen as 'examples' by the parser and may have special safety restrictions."
})
@Examples({"""
example:
broadcast "hello world"
# this is never run"""
})
@Since("INSERT VERSION")
public class StructExample extends Structure {

public static final Priority PRIORITY = new Priority(550);

static {
Skript.registerStructure(StructExample.class,
"example"
);
}

private SectionNode source;

@Override
public boolean init(Literal<?>[] literals, int matchedPattern, ParseResult parseResult,
@Nullable EntryContainer entryContainer) {
if (!this.getParser().hasExperiment(Feature.EXAMPLES))
return false;
assert entryContainer != null; // cannot be null for non-simple structures
this.source = entryContainer.getSource();
return true;
}

@Override
public boolean load() {
ParserInstance parser = this.getParser();
// This acts like a 'function' except without some of the features (e.g. returns)
// The code is parsed and loaded, but then discarded since it will never be run
// This allows things like parse problems and errors to be detected.
parser.setCurrentEvent("example", FunctionEvent.class);
ScriptLoader.loadItems(source);
parser.deleteCurrentEvent();
return true;
}

@Override
public Priority getPriority() {
return PRIORITY;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "example";
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.skriptlang.skript.lang.util;

import ch.njol.skript.lang.util.common.AnyAmount;
import ch.njol.skript.util.Container;
import ch.njol.yggdrasil.YggdrasilSerializable;
import org.jetbrains.annotations.NotNull;

Expand All @@ -10,8 +11,9 @@
* A queue of elements.
* Elements will only be added to the queue if they are not null, with nothing happening if the elements are null.
*/
@Container.ContainerType(Object.class)
public class SkriptQueue extends LinkedList<@NotNull Object>
implements Deque<Object>, Queue<Object>, YggdrasilSerializable, AnyAmount {
implements Deque<Object>, Queue<Object>, YggdrasilSerializable, AnyAmount, Container<Object> {

@Override
public boolean add(Object element) {
Expand Down Expand Up @@ -97,4 +99,9 @@ public Object[] removeRangeSafely(int fromIndex, int toIndex) {
return this.size();
}

@Override
public Iterator<Object> containerIterator() {
return this.iterator();
}

}
64 changes: 32 additions & 32 deletions src/main/resources/scripts/-examples/chest menus.sk
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,58 @@
#

command /simplechest:
permission: skript.example.chest
trigger:
set {_chest} to a new chest inventory named "Simple Chest"
set slot 0 of {_chest} to apple # Slots are numbered 0, 1, 2...
open {_chest} to player
permission: skript.example.chest
trigger:
set {_chest} to a new chest inventory named "Simple Chest"
set slot 0 of {_chest} to apple # Slots are numbered 0, 1, 2...
open {_chest} to player

#
# An example of listening for click events in a chest inventory.
# This can be used to create fancy button-style interfaces for users.
#

command /chestmenu:
permission: skript.example.menu
trigger:
set {_menu} to a new chest inventory with 1 row named "Simple Menu"
set slot 4 of {_menu} to stone named "Button" # Slots are numbered 0, 1, 2...
open {_menu} to player
permission: skript.example.menu
trigger:
set {_menu} to a new chest inventory with 1 row named "Simple Menu"
set slot 4 of {_menu} to stone named "Button" # Slots are numbered 0, 1, 2...
open {_menu} to player

on inventory click: # Listen for players clicking in an inventory.
name of event-inventory is "Simple Menu" # Make sure it's our menu.
cancel event
if index of event-slot is 4: # The button slot.
send "You clicked the button."
else:
send "You didn't click the button."
name of event-inventory is "Simple Menu" # Make sure it's our menu.
cancel event
if index of event-slot is 4: # The button slot.
send "You clicked the button."
else:
send "You didn't click the button."

#
# An example of making and filling a fancy inventory with a function.
# This demonstrates another way you can use chest inventories, and a safe way of listening to specific ones.
#

aliases:
menu items = TNT, lava bucket, string, coal, oak planks
menu items = TNT, lava bucket, string, coal, oak planks

function makeMenu(name: text, rows: number) :: inventory:
set {_gui} to a new chest inventory with {_rows} rows with name {_name}
loop {_rows} * 9 times: # Fill the inventory with random items.
set slot loop-number - 1 of {_gui} to random item out of menu items
return {_gui}
function make_menu(name: text, rows: number) :: inventory:
set {_gui} to a new chest inventory with {_rows} rows with name {_name}
loop {_rows} * 9 times: # Fill the inventory with random items.
set slot loop-number - 1 of {_gui} to random item out of menu items
return {_gui}

command /fancymenu:
permission: skript.example.menu
trigger:
set {_menu} to makeMenu("hello", 4)
add {_menu} to {my inventories::*} # Prepare to listen to this inventory.
open {_menu} to player
permission: skript.example.menu
trigger:
set {_menu} to make_menu("hello", 4)
add {_menu} to {my inventories::*} # Prepare to listen to this inventory.
open {_menu} to player

on inventory click: # Listen for players clicking in any inventory.
if {my inventories::*} contains event-inventory: # Make sure it's our menu.
cancel event
send "You clicked slot %index of event-slot%!"
if {my inventories::*} contains event-inventory: # Make sure it's our menu.
cancel event
send "You clicked slot %index of event-slot%!"

on inventory close: # No longer need to listen to this inventory.
{my inventories::*} contains event-inventory
remove event-inventory from {my inventories::*}
{my inventories::*} contains event-inventory
remove event-inventory from {my inventories::*}
Loading

0 comments on commit 1fab937

Please sign in to comment.