diff --git a/gradlew.bat b/gradlew.bat
index 8508ef684d4..a51ec4f5886 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,82 +1,82 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-@rem SPDX-License-Identifier: Apache-2.0
-@rem
-
-@if "%DEBUG%"=="" @echo off
-@rem ##########################################################################
-@rem
-@rem gradlew startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables, and ensure extensions are enabled
-setlocal EnableExtensions
-
-set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-"%COMSPEC%" /c exit 1
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-"%COMSPEC%" /c exit 1
-
-:execute
-@rem Setup the command line
-
-
-
-@rem Execute gradlew
-@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
-@rem which allows us to clear the local environment before executing the java command
-endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
-
-:exitWithErrorLevel
-@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
-"%COMSPEC%" /c exit %ERRORLEVEL%
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem gradlew startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables, and ensure extensions are enabled
+setlocal EnableExtensions
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+"%COMSPEC%" /c exit 1
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+"%COMSPEC%" /c exit 1
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute gradlew
+@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
+@rem which allows us to clear the local environment before executing the java command
+endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
+
+:exitWithErrorLevel
+@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
+"%COMSPEC%" /c exit %ERRORLEVEL%
diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java
index 10704eda253..9bc8edb2dd9 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java
@@ -1,8 +1,11 @@
package org.skriptlang.skript.bukkit.entity.player;
import ch.njol.skript.Skript;
+import ch.njol.skript.classes.EnumClassInfo;
import ch.njol.skript.lang.util.SimpleEvent;
+import ch.njol.skript.registrations.Classes;
import io.papermc.paper.event.player.AsyncChatEvent;
+import org.bukkit.Statistic;
import org.skriptlang.skript.addon.AddonModule;
import org.skriptlang.skript.addon.HierarchicalAddonModule;
import org.skriptlang.skript.addon.SkriptAddon;
@@ -19,11 +22,24 @@ public PlayerModule(AddonModule parentModule) {
super(parentModule);
}
+ @Override
+ protected void initSelf(SkriptAddon addon) {
+ Classes.registerClass(new EnumClassInfo<>(Statistic.class, "statistic", "statistics")
+ .user("statistics?")
+ .name("Statistic")
+ .description(
+ "Represents a countable statistic, which is tracked by the server. "
+ + "For the list of all available statistics, "
+ + "see the Minecraft wiki.")
+ .since("INSERT VERSION"));
+ }
+
@Override
protected void loadSelf(SkriptAddon addon) {
EventValueRegistry eventValueRegistry = addon.registry(EventValueRegistry.class);
register(addon,
+ syntaxRegistry -> EvtPlayerStatisticChange.register(syntaxRegistry, eventValueRegistry),
EffBan::register,
EffKick::register,
syntaxRegistry -> EvtPlayerGameModeChange.register(syntaxRegistry, eventValueRegistry),
@@ -37,7 +53,8 @@ protected void loadSelf(SkriptAddon addon) {
ExprPlayerListHeaderFooter::register,
ExprPlayerListName::register,
ExprPlayerListPriority::register,
- ExprQuitMessage::register
+ ExprQuitMessage::register,
+ ExprStatistic::register
);
if (Skript.classExists("io.papermc.paper.event.player.PlayerPickBlockEvent")) {
register(addon,
diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/events/EvtPlayerStatisticChange.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/events/EvtPlayerStatisticChange.java
new file mode 100644
index 00000000000..65a8d6c7376
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/events/EvtPlayerStatisticChange.java
@@ -0,0 +1,95 @@
+package org.skriptlang.skript.bukkit.entity.player.elements.events;
+
+import ch.njol.skript.aliases.ItemType;
+import ch.njol.skript.entity.EntityData;
+import ch.njol.skript.lang.Literal;
+import ch.njol.skript.lang.SkriptEvent;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.lang.SyntaxStringBuilder;
+import org.bukkit.Statistic;
+import org.bukkit.event.Event;
+import org.bukkit.event.player.PlayerStatisticIncrementEvent;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue;
+import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue.Time;
+import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry;
+import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+
+import static ch.njol.skript.bukkitutil.EntityUtils.toSkriptEntityData;
+
+@SuppressWarnings("unchecked")
+public class EvtPlayerStatisticChange extends SkriptEvent {
+
+ public static void register(SyntaxRegistry syntaxRegistry, EventValueRegistry registry) {
+ syntaxRegistry.register(BukkitSyntaxInfos.Event.KEY, BukkitSyntaxInfos.Event.builder(EvtPlayerStatisticChange.class, "Player Statistic Change")
+ .supplier(EvtPlayerStatisticChange::new)
+ .addEvent(PlayerStatisticIncrementEvent.class)
+ .addPatterns("player statistic (change|increase|increment) [of %-statistics% stat[istic][s]]")
+ .addDescription("""
+ Called when a player's statistic changes.
+ Some statistics like 'play one minute' do not call this event, because they get called too often.
+ This event is only called when the server updates a statistic and as such does not call if the statistic expression is used.
+ """)
+ .addExample("""
+ on player statistic increase:
+ broadcast "%player%'s statistic '%event-statistic%' increased! It is now %event-number%!
+ """)
+ .addExample("""
+ on player statistic increase of leave game:
+ broadcast "%player% has now left the game %event-number% times!"
+ """)
+ .addSince("INSERT VERSION")
+ .build());
+
+ registry.register(EventValue.builder(PlayerStatisticIncrementEvent.class, Integer.class)
+ .getter(PlayerStatisticIncrementEvent::getNewValue)
+ .build());
+
+ registry.register(EventValue.builder(PlayerStatisticIncrementEvent.class, Integer.class)
+ .getter(PlayerStatisticIncrementEvent::getPreviousValue)
+ .time(Time.PAST)
+ .build());
+
+ registry.register(EventValue.builder(PlayerStatisticIncrementEvent.class, Statistic.class)
+ .getter(PlayerStatisticIncrementEvent::getStatistic)
+ .build());
+
+ registry.register(EventValue.builder(PlayerStatisticIncrementEvent.class, ItemType.class)
+ .getter(event -> new ItemType(event.getMaterial()))
+ .build());
+
+ registry.register(EventValue.builder(PlayerStatisticIncrementEvent.class, EntityData.class)
+ .getter(event -> toSkriptEntityData(event.getEntityType()))
+ .build());
+ }
+
+ private Literal statistics;
+
+ @Override
+ public boolean init(Literal>[] literals, int i, ParseResult parseResult) {
+ statistics = (Literal) literals[0];
+ return true;
+ }
+
+ @Override
+ public boolean check(Event event) {
+ if (statistics == null)
+ return true;
+ Statistic statistic = ((PlayerStatisticIncrementEvent) event).getStatistic();
+ for (Statistic value : this.statistics.getAll(event)) {
+ if (statistic.equals(value))
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return new SyntaxStringBuilder(event, debug)
+ .append("player statistic change")
+ .appendIf(statistics != null, "of", statistics)
+ .toString();
+ }
+
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprStatistic.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprStatistic.java
new file mode 100644
index 00000000000..0544a5c0ecb
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprStatistic.java
@@ -0,0 +1,222 @@
+package org.skriptlang.skript.bukkit.entity.player.elements.expressions;
+
+import ch.njol.skript.aliases.ItemType;
+import ch.njol.skript.bukkitutil.EntityUtils;
+import ch.njol.skript.classes.Changer.ChangeMode;
+import ch.njol.skript.doc.Description;
+import ch.njol.skript.doc.Example;
+import ch.njol.skript.doc.Name;
+import ch.njol.skript.doc.Since;
+import ch.njol.skript.entity.EntityData;
+import ch.njol.skript.expressions.base.PropertyExpression;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.lang.SyntaxStringBuilder;
+import ch.njol.skript.registrations.Classes;
+import ch.njol.util.Kleenean;
+import ch.njol.util.coll.CollectionUtils;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Statistic;
+import org.bukkit.Statistic.Type;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+
+import java.util.Arrays;
+
+@SuppressWarnings("unchecked")
+@Name("Player Statistic")
+@Description("""
+ Represents a statistic of a player.
+ Some statistics require an entity type or item type to be specified.
+ See https://minecraft.wiki/w/Statistics for a full list of statistics.
+ Using this expression does not call Player Statistic Change event.
+ """)
+@Example("""
+ command /basicinfo:
+ trigger:
+ send "You've killed %player kills statistic of player% players!"
+ send "You've broken %mine block statistic using sand of player% sand!"
+ """)
+@Example("""
+ on death:
+ chance of 50%:
+ remove 1 from deaths statistic of player
+ send "You got lucky and didn't ruin your kdr!" to victim
+ """)
+@Example("""
+ on break of tnt:
+ chance of 25%:
+ add 2 to mine block statistic using tnt (itemtype) of player
+ send "You gained +2 tnt broken! (25% chance)"
+ """)
+@Since("INSERT VERSION")
+public class ExprStatistic extends PropertyExpression {
+
+ public static void register(SyntaxRegistry registry) {
+ registry.register(
+ SyntaxRegistry.EXPRESSION,
+ infoBuilder(
+ ExprStatistic.class,
+ Integer.class,
+ "%statistic% stat[istic] [using %-entitydata/itemtype%]",
+ "offlineplayers",
+ false
+ )
+ .supplier(ExprStatistic::new)
+ .build()
+ );
+ }
+
+ private Expression statistic;
+ private Expression> ofType;
+
+ @Override
+ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
+ if (matchedPattern == 1) {
+ statistic = (Expression) expressions[1];
+ ofType = expressions[2];
+ setExpr((Expression extends OfflinePlayer>) expressions[0]);
+ } else {
+ statistic = (Expression) expressions[0];
+ ofType = expressions[1];
+ setExpr((Expression extends OfflinePlayer>) expressions[2]);
+ }
+ return true;
+ }
+
+ @Override
+ protected Integer[] get(Event event, OfflinePlayer[] source) {
+ Statistic statistic = this.statistic.getSingle(event);
+ if (statistic == null)
+ return new Integer[0];
+ Object type = ofType != null ? ofType.getSingle(event) : null;
+ if (checkTyping(statistic, type))
+ return new Integer[0];
+
+ return Arrays.stream(source)
+ .map(player -> getStatistic(player, statistic, type))
+ .toArray(Integer[]::new);
+ }
+
+ @Override
+ public Class> @Nullable [] acceptChange(ChangeMode mode) {
+ return switch (mode) {
+ case SET, ADD, REMOVE, RESET, DELETE -> CollectionUtils.array(Integer.class);
+ default -> null;
+ };
+ }
+
+ @Override
+ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) {
+ int amount = (mode == ChangeMode.RESET || mode == ChangeMode.DELETE) ? 0 : (Integer) delta[0];
+ Statistic statistic = this.statistic.getSingle(event);
+ if (statistic == null)
+ return;
+ Object type = ofType != null ? ofType.getSingle(event) : null;
+ if (checkTyping(statistic, type))
+ return;
+
+ switch (mode) {
+ case SET, RESET, DELETE -> {
+ for (OfflinePlayer player : getExpr().getArray(event))
+ applyStatistic(player, statistic, amount, type);
+ }
+ case ADD -> {
+ for (OfflinePlayer player : getExpr().getArray(event))
+ applyStatistic(player, statistic, getStatistic(player, statistic, type) + amount, type);
+ }
+ case REMOVE -> {
+ for (OfflinePlayer player : getExpr().getArray(event))
+ applyStatistic(player, statistic, getStatistic(player, statistic, type) - amount, type);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the value of a specific statistic for a given player.
+ *
+ * @param player The player whose statistic is being retrieved.
+ * @param statistic The statistic itself. See Statistics for a list of every possible statistic.
+ * @param type The statistic type. Can be ITEM, BLOCK or ENTITY.
+ * @return The value of the specified statistic of the player.
+ */
+ private int getStatistic(OfflinePlayer player, Statistic statistic, Object type) {
+ Statistic.Type statisticType = statistic.getType();
+ if (type instanceof ItemType item && (statisticType == Statistic.Type.ITEM || statisticType == Statistic.Type.BLOCK)) {
+ return player.getStatistic(statistic, item.getMaterial());
+ } else if (type instanceof EntityData> data && statisticType == Statistic.Type.ENTITY) {
+ return player.getStatistic(statistic, EntityUtils.toBukkitEntityType(data));
+ }
+
+ return player.getStatistic(statistic);
+ }
+
+ /**
+ * Applies the given value to the statistic of the player.
+ *
+ * @param player The player whose statistic is being modified.
+ * @param statistic The statistic itself. See Statistics for a list of every possible statistic.
+ * @param value The value that the statistic should be changed to.
+ * @param type The statistic type. Can be ITEM, BLOCK or ENTITY.
+ */
+ private void applyStatistic(OfflinePlayer player, Statistic statistic, Integer value, Object type) {
+ Statistic.Type statisticType = statistic.getType();
+ value = Math.max(0, value);
+ if (type instanceof ItemType item && (statisticType == Statistic.Type.ITEM || statisticType == Statistic.Type.BLOCK)) {
+ player.setStatistic(statistic, item.getMaterial(), value);
+ } else if (type instanceof EntityData> data && statisticType == Statistic.Type.ENTITY) {
+ player.setStatistic(statistic, EntityUtils.toBukkitEntityType(data), value);
+ } else {
+ player.setStatistic(statistic, value);
+ }
+ }
+
+ /**
+ * Checks if the specified statistic needs a type or not.
+ *
+ *
+ * @param statistic The statistic itself. See Statistics for a list of every possible statistic.
+ * @param ofType The statistic type. Can be ITEM, BLOCK or ENTITY.
+ * @return True if the statistic with the given type is invalid else false if it is valid.
+ */
+ private boolean checkTyping(Statistic statistic, Object ofType) {
+ Type statisticType = statistic.getType();
+
+ String statisticString = "The statistic '" + statistic + "'";
+ if (ofType == null && statisticType != Type.UNTYPED) {
+ if (statisticType == Type.ITEM || statisticType == Type.BLOCK) {
+ error(statisticString + " requires an item type to be specified.");
+ } else if (statisticType == Type.ENTITY) {
+ error(statisticString + " requires an entity data to be specified.");
+ }
+ return true;
+ } else if (this.ofType != null && statisticType == Type.UNTYPED) {
+ warning(statisticString + " does not require an entity data or item type to be provided, so it will be ignored.");
+ }
+
+ if (ofType instanceof ItemType && statisticType == Type.ENTITY) {
+ error(statisticString + " requires an entity data, but '" + Classes.toString(ofType) + "' was provided.");
+ return true;
+ } else if (ofType instanceof EntityData && (statisticType == Type.ITEM || statisticType == Type.BLOCK)) {
+ error(statisticString + " requires an item type, but '" + Classes.toString(ofType) + "' was provided.");
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public Class getReturnType() {
+ return Integer.class;
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return new SyntaxStringBuilder(event, debug)
+ .append("the statistic", statistic, "of", getExpr().getArray(event))
+ .appendIf(ofType != null, "using", ofType)
+ .toString();
+ }
+
+}
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index d72d5ecb0b8..d756724bf7e 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2506,8 +2506,7 @@ particle:
white_ash: white ash particle¦s @a
white_smoke: white smoke particle¦s @a
witch: witch particle¦s @a
-
- # 26.2
+ # 26.2
noxious_gas: noxious gas particle¦s @a, nausea gas particle¦s @a
noxious_gas_cloud: noxious gas cloud¦s @a, nausea gas cloud particle¦s @a
sulfur_cube_goo: sulfur cube goo particle¦s @a, sulfur cube jump particle¦s @a, sulfur cube jumping particle¦s @a
@@ -2517,6 +2516,92 @@ particle:
geyser_plume: geyser plume particle¦s @a
geyser_poof: geyser poof particle¦s @a
+# -- Statistics --
+statistics:
+ animals_bred: animals bred
+ armor_cleaned: armor cleaned
+ aviate_one_cm: aviate one cm, glide distance, elytra distance
+ banner_cleaned: banner cleaned
+ beacon_interaction: beacon interaction
+ bell_ring: bell ring
+ boat_one_cm: boat one cm, boat distance
+ break_item: break item
+ brewingstand_interaction: brewing stand interaction, interact with brewing stand
+ cake_slices_eaten: cake slices eaten
+ cauldron_filled: cauldron filled
+ cauldron_used: cauldron used
+ chest_opened: chest opened
+ clean_shulker_box: clean shulker box
+ climb_one_cm: climb one cm, climb distance
+ craft_item: craft item
+ crafting_table_interaction: crafting table interaction
+ crouch_one_cm: crouch one cm, sneak distance
+ damage_absorbed: damage absorbed
+ damage_blocked_by_shield: damage blocked by shield
+ damage_dealt: damage dealt
+ damage_dealt_absorbed: damage dealt absorbed
+ damage_dealt_resisted: damage dealt resisted
+ damage_resisted: damage resisted
+ damage_taken: damage taken
+ deaths: deaths
+ dispenser_inspected: dispenser inspected
+ drop: drop
+ drop_count: drop count, items dropped
+ dropper_inspected: dropper inspected
+ enderchest_opened: ender chest opened
+ entity_killed_by: entity killed by, times killed by entity
+ fall_one_cm: fall one cm, fall distance
+ fish_caught: fish caught
+ flower_potted: flower potted
+ fly_one_cm: fly one cm, fly distance
+ furnace_interaction: furnace interaction
+ hopper_inspected: hopper inspected
+ horse_one_cm: horse one cm, horse distance, horse riding distance
+ interact_with_anvil: interact with anvil, anvil interaction
+ interact_with_blast_furnace: interact with blast furnace, blast furnace interaction
+ interact_with_campfire: interact with campfire, campfire interaction
+ interact_with_cartography_table: interact with cartography table, cartography table interaction
+ interact_with_grindstone: interact with grindstone, grindstone interaction
+ interact_with_lectern: interact with lectern, lectern interaction
+ interact_with_loom: interact with loom, loom interaction
+ interact_with_smithing_table: interact with smithing table, smithing table interaction
+ interact_with_smoker: interact with smoker, smoker interaction
+ interact_with_stonecutter: interact with stone cutter, stone cutter interaction
+ item_enchanted: item enchanted
+ jump: jump
+ kill_entity: kill entity, entities killed
+ leave_game: leave game
+ mine_block: mine block
+ minecart_one_cm: minecart one cm, minecart distance
+ mob_kills: mob kills, mobs killed
+ noteblock_played: note block played
+ noteblock_tuned: note block tuned
+ open_barrel: open barrel
+ pickup: pick up, items picked up
+ pig_one_cm: pig one cm, pig distance, pig riding distance
+ play_one_minute: play one minute, ticks played, play time, time played
+ player_kills: player kills
+ raid_trigger: raid trigger
+ raid_win: raid win
+ record_played: record played
+ shulker_box_opened: shulker box opened
+ sleep_in_bed: sleep in bed
+ sneak_time: sneak time, crouch time
+ sprint_one_cm: sprint one cm, sprint distance
+ strider_one_cm: strider one cm, strider distance, strider riding distance
+ swim_one_cm: swim one cm, swim distance
+ talked_to_villager: talked to villager
+ target_hit: target hit
+ time_since_death: time since death
+ time_since_rest: time since rest
+ total_world_time: total world time
+ traded_with_villager: traded with villager
+ trapped_chest_triggered: trapped chest triggered
+ use_item: use item
+ walk_on_water_one_cm: walk on water one cm, walk on water distance
+ walk_one_cm: walk one cm, walk distance
+ walk_under_water_one_cm: walk under water one cm, walk underwater once cm, walk under water distance, walk underwater distance
+
# -- Teleport Flags --
teleport flags:
retain_open_inventory: opened inventory, open inventory, inventory
diff --git a/src/test/skript/tests/syntaxes/expressions/ExprStatistic.sk b/src/test/skript/tests/syntaxes/expressions/ExprStatistic.sk
new file mode 100644
index 00000000000..8950869f41f
--- /dev/null
+++ b/src/test/skript/tests/syntaxes/expressions/ExprStatistic.sk
@@ -0,0 +1,47 @@
+using error catching
+
+test "statistic":
+ set player kills statistic of test-offline player to 45
+ assert player kills statistic of test-offline player is 45 with "Failed to set kills statistic"
+ set {_error} to "The statistic 'MINE_BLOCK' requires an item type to be specified."
+ catch runtime errors:
+ set mine block statistic of test-offline player to 5
+ assert last caught errors is {_error} with "set player's mine block statistic without giving an item should give error but did not"
+ set mine block statistic using sand of test-offline player to 25
+ assert mine block statistic using sand of test-offline player is 25 with "Failed to set mine block statistic using sand"
+ set {_error} to "The statistic 'KILL_ENTITY' requires an entity data to be specified."
+ catch runtime errors:
+ set test-offline player's kill entity statistic to 5
+ assert last caught errors is {_error} with "set player's kill entity statistic should error but did not"
+ set kill entity stat using pig of test-offline player to 5
+ assert kill entity statistic using pig of test-offline player is 5 with "Failed to set kill entity stat using pig"
+ set {_error} to "The statistic 'PLAYER_KILLS' does not require an entity data or item type to be provided, so it will be ignored."
+ catch runtime errors:
+ set player kills stat using tnt (itemtype) of test-offline player to 75
+ assert last caught errors is {_error} with "set player's player kill statistic should give warning but did not"
+ set {_error} to "The statistic 'KILL_ENTITY' requires an entity data to be specified."
+ catch runtime errors:
+ set kill entity stat of test-offline player to 5
+ assert last caught errors is {_error} with "set player's kill entity stat without providing an entity data should error but did not"
+ set {_error} to "The statistic 'MINE_BLOCK' requires an item type to be specified."
+ catch runtime errors:
+ set mine block stat of test-offline player to 5
+ assert last caught errors is {_error} with "set player's mine block statistic without providing an item type should error but did not"
+ set {_error} to "The statistic 'MINE_BLOCK' requires an item type, but 'pig' was provided."
+ catch runtime errors:
+ set mine block stat using pig of test-offline player to 5
+ assert last caught errors is {_error} with "set player's mine block stat using pig should error but did not"
+ set {_error} to "The statistic 'KILL_ENTITY' requires an entity data, but 'sand' was provided."
+ catch runtime errors:
+ set kill entity stat using sand of test-offline player to 5
+ assert last caught errors is {_error} with "set player's kill entity stat using sand should error but did not"
+ set jump statistic of test-offline player to 54
+ assert jump statistic of test-offline player is 54 with "Failed to set jump statistic"
+ add 6 to jump statistic of test-offline player
+ assert jump statistic of test-offline player is 60 with "Failed to add to jump statistic"
+ remove 6 from jump statistic of test-offline player
+ assert jump statistic of test-offline player is 54 with "Failed to remove from jump statistic"
+ reset jump statistic of test-offline player
+ assert jump statistic of test-offline player is 0 with "Failed to reset jump statistic"
+ delete jump statistic of test-offline player
+ assert jump statistic of test-offline player is 0 with "Failed to delete jump statistic"