diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index bcb529e5..2bffdb88 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -910,11 +910,12 @@ length('foo') => 3 ### `rand(expr), rand(expr, seed)` returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions, -boolean functions, or `bool`), returns false if the randomly selected value is less than 1\. This means -that `rand(2)` returns true half of the time and `rand(5)` returns true for 80% (4/5) of the time. If seed is not -provided, uses a random seed. If seed is provided, each consecutive call to rand() will act like 'next' call to the -same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number -(per app), then your random sequence will revert to the beginning. +boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means +that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not +provided, uses a random seed that's shared across all scarpet apps. +If seed is provided, each consecutive call to rand() will act like 'next' call to the +same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app), +so if you exceed this number, your random sequences will revert to the beginning and start over.
map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
@@ -5228,7 +5229,7 @@ Available options in the scarpet app space:
* `world_folder` - name of the direct folder in the saves that holds world files
* `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
* `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
-
+ * `world_spawn_point` - world spawn point
Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'`
diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index 6c1e8312..cb8b8572 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -229,6 +229,13 @@ Causes a block to be harvested by a specified player entity. Honors player item
tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break
that block, a block doesn't get destroyed either.
+### `create_explosion(pos, power?, mode?, fire?, source?, attacker?)`
+
+Creates an explosion at a given position. Default values of optional parameters are: `'power'` - `4` (TNT power),
+`'mode'` (block breaking effect `none`, `destroy` or `break`: `break`, `fire` (whether extra fire blocks should be created) - `false`,
+`source` (exploding entity) - `null` and `attacker` (entity responsible for trigerring) - `null`. Explosions created with this
+endpoint cannot be captured with `__on_explosion` event, however they will be captured by `__on_explosion_outcome`.
+
### `weather()`,`weather(type)`,`weather(type, ticks)`
If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index e646df83..e0a6ffa3 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -79,6 +79,19 @@ at the same time.
Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would
already be spawned at that point. `mode` is `true` if the lightning did cause a trap to spawn.
+### `__on_explosion(pos, power, source, causer, mode, fire)`
+
+Event triggered right before explosion takes place and before has any effect on the world. `source` can be an entity causing
+the explosion, and `causer` the entity triggering it,
+`mode` indicates block effects: `'none'`, `'break'` (drop all blocks), or `'destroy'` - drop few blocks. Event
+is not captured when `create_explosion()` is called.
+
+### `__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities)`
+Triggered during the explosion, before any changes to the blocks are done,
+but the decision to blow up is already made and entities are already affected.
+The parameter `blocks` contains the list of blocks that will blow up (empty if `explosionNoBlockDamage` is set to `true`).
+The parameter `entities` contains the list of entities that have been affected by the explosion. Triggered even with `create_explosion()`.
+
### `__on_carpet_rule_changes(rule, new_value)`
Triggered when a carpet mod rule is changed. It includes extension rules, not using default `/carpet` command,
which will then be namespaced as `namespace:rule`.
@@ -107,7 +120,7 @@ Example events that may cause it to happen is releasing a bow. The event is trig
the request, however the `item_tuple` is provided representing the item that the player started with. You can use that and
compare with the currently held item for a delta.
-### `__on_player_finishes_using_item(player, item_tuple, hand))`
+### `__on_player_finishes_using_item(player, item_tuple, hand)`
Player using of an item is done. This is controlled server side and is responsible for such events as finishing
eating. The event is triggered after confirming that the action is valid, and sending the feedback back
to the client, but before triggering it and its effects in game.
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 42a7bf9e..9190666f 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -18,6 +18,7 @@ chunk_tickets
convert_date
crafting_remaining_item
create_datapack
+create_explosion
create_marker
current_dimension
day_time
diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt
index a921bb46..388185dc 100644
--- a/docs/scarpet/resources/editors/idea/4.txt
+++ b/docs/scarpet/resources/editors/idea/4.txt
@@ -42,4 +42,6 @@ __on_player_deals_damage(player, amount, entity) ->
__on_player_dies(player) ->
__on_player_respawns(player) ->
__on_player_disconnects(player, reason) ->
-__on_player_connects(player) ->
+__on_player_connects(player) ->
+__on_explosion(pos, power, source, causer, mode, fire) ->
+__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) ->
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index b10e15e4..93992ec5 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -55,7 +55,9 @@
set without_updates
blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi
place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets
- structure_eligibility structures structure_references set_structure reset_chunk reload_chunk create_datapack enable_hidden_dimensions
+ plop harvest destroy create_explosion
+ structure_eligibility structures structure_references set_structure
+ reset_chunk reload_chunk create_datapack enable_hidden_dimensions
add_chunk_ticket
convert_date unix_time
@@ -82,7 +84,7 @@
run save tick_time world_time day_time last_tick_times game_tick current_dimension in_dimension view_distance
- plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
+ schedule nbt draw_shape create_marker remove_all_markers
load_app_data store_app_data read_file write_file delete_file list_files
escape_nbt parse_nbt encode_nbt tag_matches statistic
@@ -139,6 +141,8 @@
__on_player_respawns
__on_player_disconnects
__on_player_connects
+ __on_explosion
+ __on_explosion_outcome
$
00' 01\ 02' 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java
index 984b8df7..dc086344 100644
--- a/src/main/java/carpet/helpers/OptimizedExplosion.java
+++ b/src/main/java/carpet/helpers/OptimizedExplosion.java
@@ -10,13 +10,10 @@ import java.util.List;
import carpet.logging.logHelpers.ExplosionLogHelper;
import carpet.mixins.ExplosionAccessor;
import carpet.CarpetSettings;
-import carpet.mixins.ExplosionMixin;
import carpet.utils.Messenger;
-import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
@@ -48,6 +45,8 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
+import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
+
public class OptimizedExplosion
{
private static List entitylist;
@@ -68,9 +67,14 @@ public class OptimizedExplosion
private static ArrayList chances = new ArrayList<>();
private static BlockPos blastChanceLocation;
+ // Creating entity list for scarpet event
+ private static List entityList = new ArrayList<>();
+
public static void doExplosionA(Explosion e, ExplosionLogHelper eLogger) {
ExplosionAccessor eAccess = (ExplosionAccessor) e;
+ entityList.clear();
+ boolean eventNeeded = EXPLOSION_OUTCOME.isNeeded() && !eAccess.getWorld().isClient();
blastCalc(e);
if (!CarpetSettings.explosionNoBlockDamage) {
@@ -156,6 +160,11 @@ public class OptimizedExplosion
densityCache.put(pair, density);
}
+ // If it is needed, it saves the entity
+ if (eventNeeded) {
+ entityList.add(entity);
+ }
+
double d10 = (1.0D - d12) * density;
entity.damage(e.getDamageSource(),
(float) ((int) ((d10 * d10 + d10) / 2.0D * 7.0D * (double) f3 + 1.0D)));
@@ -195,6 +204,11 @@ public class OptimizedExplosion
double posY = eAccess.getY();
double posZ = eAccess.getZ();
+ // If it is needed, calls scarpet event
+ if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient()) {
+ EXPLOSION_OUTCOME.onExplosion((ServerWorld) world, eAccess.getEntity(), e::getCausingEntity, eAccess.getX(), eAccess.getY(), eAccess.getZ(), eAccess.getPower(), eAccess.isCreateFire(), e.getAffectedBlocks(), entityList, eAccess.getDestructionType());
+ }
+
boolean damagesTerrain = eAccess.getDestructionType() != Explosion.DestructionType.NONE;
// explosionSound incremented till disabling the explosion particles and sound
@@ -598,4 +612,4 @@ public class OptimizedExplosion
);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java b/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
new file mode 100644
index 00000000..8bca895d
--- /dev/null
+++ b/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
@@ -0,0 +1,71 @@
+package carpet.mixins;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.damage.DamageSource;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+import net.minecraft.world.explosion.Explosion;
+import net.minecraft.world.explosion.ExplosionBehavior;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
+
+@Mixin(value = Explosion.class, priority = 990)
+public abstract class Explosion_scarpetEventMixin
+{
+ @Shadow @Final private World world;
+ @Shadow @Final private double x;
+ @Shadow @Final private double y;
+ @Shadow @Final private double z;
+ @Shadow @Final private float power;
+ @Shadow @Final private boolean createFire;
+ @Shadow @Final private List affectedBlocks;
+ @Shadow @Final private Explosion.DestructionType destructionType;
+ @Shadow @Final private @Nullable Entity entity;
+
+ @Shadow /*@Nullable*/ public abstract /*@Nullable*/ LivingEntity getCausingEntity();
+
+ private List affectedEntities;
+
+ @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/damage/DamageSource;Lnet/minecraft/world/explosion/ExplosionBehavior;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)V",
+ at = @At(value = "RETURN"))
+ private void onExplosionCreated(World world, Entity entity, DamageSource damageSource, ExplosionBehavior explosionBehavior, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType, CallbackInfo ci)
+ {
+ if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient())
+ {
+ affectedEntities = new ArrayList<>();
+ }
+ }
+
+ @Inject(method = "collectBlocksAndDamageEntities", locals= LocalCapture.CAPTURE_FAILHARD, at=@At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;damage(Lnet/minecraft/entity/damage/DamageSource;F)Z"))
+ private void onExplosion(CallbackInfo ci, Set s1, float f1, int i1, int i2, int i3, int i4, int i5, int i6, List l1, Vec3d v1, int i7, Entity entity)
+ {
+ if (affectedEntities != null)
+ {
+ affectedEntities.add(entity);
+ }
+ }
+
+ @Inject(method = "affectWorld", at = @At("HEAD"))
+ private void onExplosion(boolean spawnParticles, CallbackInfo ci)
+ {
+ if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient())
+ {
+ EXPLOSION_OUTCOME.onExplosion((ServerWorld) world, entity, this::getCausingEntity, x, y, z, power, createFire, affectedBlocks, affectedEntities, destructionType);
+ }
+ }
+}
diff --git a/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
index 7959d320..76c690a5 100644
--- a/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
+++ b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
@@ -6,13 +6,18 @@ import carpet.script.CarpetEventServer;
import net.minecraft.entity.Entity;
import net.minecraft.world.entity.EntityLike;
import net.minecraft.entity.LightningEntity;
+import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.world.ServerEntityManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.chunk.WorldChunk;
+import net.minecraft.world.explosion.Explosion;
+import net.minecraft.world.explosion.ExplosionBehavior;
import net.minecraft.world.level.ServerWorldProperties;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -20,9 +25,11 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
+import static carpet.script.CarpetEventServer.Event.EXPLOSION;
import static carpet.script.CarpetEventServer.Event.LIGHTNING;
@Mixin(ServerWorld.class)
@@ -62,6 +69,13 @@ public class ServerWorld_scarpetMixin implements ServerWorldInterface
return success;
}
+ @Inject(method = "createExplosion", at = @At("HEAD"))
+ private void handleExplosion(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionBehavior explosionBehavior, double d, double e, double f, float g, boolean bl, Explosion.DestructionType destructionType, CallbackInfoReturnable cir)
+ {
+ if (EXPLOSION.isNeeded())
+ EXPLOSION.onExplosion((ServerWorld) (Object)this, entity, null, d, e, f, g, bl, null, null, destructionType);
+ }
+
@Final
@Shadow
private ServerWorldProperties worldProperties;
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 3c1c084d..18bd5df0 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -5,6 +5,7 @@ import carpet.CarpetSettings;
import carpet.helpers.TickSpeed;
import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BlockValue;
+import carpet.script.value.BooleanValue;
import carpet.script.value.EntityValue;
import carpet.script.value.FunctionValue;
import carpet.script.value.ListValue;
@@ -19,8 +20,11 @@ import carpet.utils.Messenger;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityType;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.TntEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.passive.MerchantEntity;
+import net.minecraft.entity.projectile.ProjectileEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.Entity;
@@ -42,6 +46,7 @@ import net.minecraft.village.Merchant;
import net.minecraft.village.TradeOffer;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
+import net.minecraft.world.explosion.Explosion;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
@@ -52,6 +57,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -865,6 +871,59 @@ public class CarpetEventServer
);
}
};
+ //copy of Explosion.getCausingEntity() #TRACK#
+ private static LivingEntity getExplosionCausingEntity(Entity entity)
+ {
+ if (entity == null) return null;
+ else if (entity instanceof TntEntity) return ((TntEntity)entity).getCausingEntity();
+ else if (entity instanceof LivingEntity) return (LivingEntity)entity;
+ else if (entity instanceof ProjectileEntity) {
+ Entity owner = ((ProjectileEntity)entity).getOwner();
+ if (owner instanceof LivingEntity) return (LivingEntity)owner;
+ }
+ return null;
+ }
+
+ public static final Event EXPLOSION_OUTCOME = new Event("explosion_outcome", 8, true)
+ {
+ @Override
+ public void onExplosion(ServerWorld world, Entity e, Supplier attacker, double x, double y, double z, float power, boolean createFire, List affectedBlocks, List affectedEntities, Explosion.DestructionType type)
+ {
+ handler.call(
+ () -> Arrays.asList(
+ ListValue.fromTriple(x, y, z),
+ NumericValue.of(power),
+ EntityValue.of(e),
+ EntityValue.of(attacker!= null?attacker.get():Event.getExplosionCausingEntity(e)),
+ StringValue.of(type.name().toLowerCase(Locale.ROOT)),
+ BooleanValue.of(createFire),
+ new ListValue(affectedBlocks.stream().filter(b -> !world.isAir(b)). map( // da heck they send air blocks
+ b -> new BlockValue(world.getBlockState(b),world,b)
+ ).collect(Collectors.toList())),
+ new ListValue(affectedEntities.stream().map(EntityValue::of).collect(Collectors.toList()))
+ ), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
+ );
+ }
+ };
+
+
+ public static final Event EXPLOSION = new Event("explosion", 6, true)
+ {
+ @Override
+ public void onExplosion(ServerWorld world, Entity e, Supplier attacker, double x, double y, double z, float power, boolean createFire, List affectedBlocks, List affectedEntities, Explosion.DestructionType type)
+ {
+ handler.call(
+ () -> Arrays.asList(
+ ListValue.fromTriple(x, y, z),
+ NumericValue.of(power),
+ EntityValue.of(e),
+ EntityValue.of(attacker!= null?attacker.get():Event.getExplosionCausingEntity(e)),
+ StringValue.of(type.name().toLowerCase(Locale.ROOT)),
+ BooleanValue.of(createFire)
+ ), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
+ );
+ }
+ };
public static String getEntityLoadEventName(EntityType extends Entity> et)
{
@@ -985,7 +1044,7 @@ public class CarpetEventServer
public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {}
-
+ public void onExplosion(ServerWorld world, Entity e, Supplier attacker, double x, double y, double z, float power, boolean createFire, List affectedBlocks, List affectedEntities, Explosion.DestructionType type) { }
public void onWorldEvent(ServerWorld world, BlockPos pos) { }
public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
public void onCarpetRuleChanges(ParsedRule> rule, ServerCommandSource source) { }
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index 4c73f8df..95c399a5 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -50,6 +50,7 @@ import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.FallingBlockEntity;
+import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
@@ -61,6 +62,7 @@ import net.minecraft.item.SwordItem;
import net.minecraft.item.TridentItem;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
+import net.minecraft.network.packet.s2c.play.ExplosionS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketType;
@@ -80,6 +82,7 @@ import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.Heightmap;
import net.minecraft.world.LightType;
@@ -89,6 +92,7 @@ import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.WorldChunk;
+import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.gen.ChunkRandom;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
@@ -97,6 +101,7 @@ import net.minecraft.world.level.ServerWorldProperties;
import net.minecraft.world.poi.PointOfInterest;
import net.minecraft.world.poi.PointOfInterestStorage;
import net.minecraft.world.poi.PointOfInterestType;
+import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -971,6 +976,88 @@ public class WorldAccess {
return success ? LazyValue.TRUE : LazyValue.FALSE;
});
+ expression.addLazyFunction("create_explosion", -1, (c, t, lv) ->
+ {
+ if (lv.isEmpty())
+ throw new InternalExpressionException("'create_explosion' requires at least a position to explode");
+ CarpetContext cc = (CarpetContext)c;
+ float powah = 4.0f;
+ Explosion.DestructionType mode = Explosion.DestructionType.BREAK;
+ boolean createFire = false;
+ Entity source = null;
+ LivingEntity attacker = null;
+ Vector3Argument location = Vector3Argument.findIn(cc, lv, 0, false, true);
+ Vec3d pos = location.vec;
+ if (lv.size() > location.offset)
+ {
+ powah = NumericValue.asNumber(lv.get(location.offset).evalValue(c), "explosion power").getFloat();
+ if (powah < 0) throw new InternalExpressionException("Explosion power cannot be negative");
+ if (lv.size() > location.offset+1)
+ {
+ String strval = lv.get(location.offset+1).evalValue(c).getString();
+ try {
+ mode = Explosion.DestructionType.valueOf(strval.toUpperCase(Locale.ROOT));
+ }
+ catch (IllegalArgumentException ile) { throw new InternalExpressionException("Illegal explosions block behaviour: "+strval); }
+ if (lv.size() > location.offset+2)
+ {
+ createFire = lv.get(location.offset+2).evalValue(c, Context.BOOLEAN).getBoolean();
+ if (lv.size() > location.offset+3)
+ {
+ Value enVal= lv.get(location.offset+3).evalValue(c);
+ if (enVal.isNull()) {} // is null already
+ else if (enVal instanceof EntityValue)
+ {
+ source = ((EntityValue) enVal).getEntity();
+ }
+ else
+ {
+ throw new InternalExpressionException("Fourth parameter of the explosion has to be an entity, not "+enVal.getTypeString());
+ }
+ if (lv.size() > location.offset+4)
+ {
+ enVal = lv.get(location.offset+4).evalValue(c);
+ if (enVal.isNull()) {} // is null already
+ else if (enVal instanceof EntityValue)
+ {
+ Entity attackingEntity = ((EntityValue) enVal).getEntity();
+ if (attackingEntity instanceof LivingEntity)
+ {
+ attacker = (LivingEntity) attackingEntity;
+ }
+ else throw new InternalExpressionException("Attacking entity needs to be a living thing, "+
+ ValueConversions.of(Registry.ENTITY_TYPE.getId(attackingEntity.getType())).getString() +" ain't it.");
+
+ }
+ else
+ {
+ throw new InternalExpressionException("Fifth parameter of the explosion has to be a living entity, not "+enVal.getTypeString());
+ }
+ }
+ }
+ }
+ }
+ }
+ LivingEntity theAttacker = attacker;
+ float thePowah = powah;
+
+ // copy of ServerWorld.createExplosion #TRACK#
+ Explosion explosion = new Explosion(cc.s.getWorld(), source, null, null, pos.x, pos.y, pos.z, powah, createFire, mode){
+ @Override
+ public @Nullable LivingEntity getCausingEntity() {
+ return theAttacker;
+ }
+ };
+ explosion.collectBlocksAndDamageEntities();
+ explosion.affectWorld(false);
+ if (mode == Explosion.DestructionType.NONE) explosion.clearAffectedBlocks();
+ cc.s.getWorld().getPlayers().forEach(spe -> {
+ if (spe.squaredDistanceTo(pos) < 4096.0D)
+ spe.networkHandler.sendPacket(new ExplosionS2CPacket(pos.x, pos.y, pos.z, thePowah, explosion.getAffectedBlocks(), explosion.getAffectedPlayers().get(spe)));
+ });
+ return LazyValue.TRUE;
+ });
+
// TODO rename to use_item
expression.addLazyFunction("place_item", -1, (c, t, lv) ->
{
diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java
index 419fb345..a41cc178 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -7,6 +7,7 @@ import carpet.script.Tokenizer;
import carpet.script.value.FunctionValue;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@@ -26,10 +27,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
{
- super("Error");
- lazyMessage = () -> makeMessage(c, e, t, message);
- token = t;
- context = c;
+ this(c, e, t, message, Collections.emptyList());
}
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List stack)
{
diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 3cdb9f7f..52bb189f 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -101,6 +101,12 @@ public class EntityValue extends Value
entity = e;
}
+ public static Value of(Entity e)
+ {
+ if (e == null) return Value.NULL;
+ return new EntityValue(e);
+ }
+
private static final Map selectorCache = new HashMap<>();
public static Collection extends Entity > getEntitiesFromSelector(ServerCommandSource source, String selector)
{
diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc
index 35fca5de..2d0bfc14 100644
--- a/src/main/resources/assets/carpet/scripts/event_test.sc
+++ b/src/main/resources/assets/carpet/scripts/event_test.sc
@@ -328,3 +328,32 @@ __on_close() ->
print('App is closing for '+player());
logger('App is closing for '+player());
);
+
+__on_explosion(pos, power, source, causer, mode, fire) ->
+(
+ print('');
+ print('__on_explosion(pos, power, source, causer, mode, fire)');
+ print('explosion:');
+ print(' - pos: '+pos);
+ print(' - power: '+power);
+ print(' - source: '+source);
+ print(' - causer: '+causer);
+ print(' - mode: '+mode);
+ print(' - fire: '+fire);
+
+);
+
+__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) ->
+(
+ print('');
+ print('__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities)');
+ print('explosion outcome:');
+ print(' - pos: '+pos);
+ print(' - power: '+power);
+ print(' - source: '+source);
+ print(' - causer: '+causer);
+ print(' - mode: '+mode);
+ print(' - fire: '+fire);
+ print(' - number or affected blocks: '+length(blocks));
+ print(' - number of affected entities: '+length(entities));
+);
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index cff2f2e5..68405701 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -131,6 +131,7 @@
"TntBlockMixin",
"ExplosionMixin",
"ExplosionAccessor",
+ "Explosion_scarpetEventMixin",
"TntEntityMixin",
"ProjectileEntityMixin",