Merge branch 'master' into 1.17

# Conflicts:
#	src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
#	src/main/java/carpet/script/api/WorldAccess.java
This commit is contained in:
gnembon committed 2021-04-09 01:40:38 -03:00
commit 388a3de577
15 files changed
+324 -17

No files matched your search

+7 -6
View File
@@ -910,11 +910,12 @@ length('foo') => 3
### `rand(expr), rand(expr, seed)` ### `rand(expr), rand(expr, seed)`
returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions, 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 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 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. If seed is provided, each consecutive call to rand() will act like 'next' call to the provided, uses a random seed that's shared across all scarpet apps.
same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number If seed is provided, each consecutive call to rand() will act like 'next' call to the
(per app), then your random sequence will revert to the beginning. 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.
<pre> <pre>
map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8] 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_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_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_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 Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'` * `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'`
+7
View File
@@ -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 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. 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)` ### `weather()`,`weather(type)`,`weather(type, ticks)`
If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will
+14 -1
View File
@@ -79,6 +79,19 @@ at the same time.
Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would 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. 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)` ### `__on_carpet_rule_changes(rule, new_value)`
Triggered when a carpet mod rule is changed. It includes extension rules, not using default `/carpet` command, 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`. 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 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. 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 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 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. to the client, but before triggering it and its effects in game.
@@ -18,6 +18,7 @@ chunk_tickets
convert_date convert_date
crafting_remaining_item crafting_remaining_item
create_datapack create_datapack
create_explosion
create_marker create_marker
current_dimension current_dimension
day_time day_time
@@ -43,3 +43,5 @@ __on_player_dies(player) ->
__on_player_respawns(player) -> __on_player_respawns(player) ->
__on_player_disconnects(player, reason) -> __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) ->
@@ -55,7 +55,9 @@
set without_updates set without_updates
blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi 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 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 add_chunk_ticket
convert_date unix_time 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 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 load_app_data store_app_data read_file write_file delete_file list_files
escape_nbt parse_nbt encode_nbt tag_matches statistic escape_nbt parse_nbt encode_nbt tag_matches statistic
@@ -139,6 +141,8 @@
__on_player_respawns __on_player_respawns
__on_player_disconnects __on_player_disconnects
__on_player_connects __on_player_connects
__on_explosion
__on_explosion_outcome
</Keywords> </Keywords>
<Keywords name="Keywords8">$</Keywords> <Keywords name="Keywords8">$</Keywords>
<Keywords name="Delimiters">00&apos; 01\ 02&apos; 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords> <Keywords name="Delimiters">00&apos; 01\ 02&apos; 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
@@ -10,13 +10,10 @@ import java.util.List;
import carpet.logging.logHelpers.ExplosionLogHelper; import carpet.logging.logHelpers.ExplosionLogHelper;
import carpet.mixins.ExplosionAccessor; import carpet.mixins.ExplosionAccessor;
import carpet.CarpetSettings; import carpet.CarpetSettings;
import carpet.mixins.ExplosionMixin;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@@ -48,6 +45,8 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.Chunk;
import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
public class OptimizedExplosion public class OptimizedExplosion
{ {
private static List<Entity> entitylist; private static List<Entity> entitylist;
@@ -68,9 +67,14 @@ public class OptimizedExplosion
private static ArrayList<Float> chances = new ArrayList<>(); private static ArrayList<Float> chances = new ArrayList<>();
private static BlockPos blastChanceLocation; private static BlockPos blastChanceLocation;
// Creating entity list for scarpet event
private static List<Entity> entityList = new ArrayList<>();
public static void doExplosionA(Explosion e, ExplosionLogHelper eLogger) { public static void doExplosionA(Explosion e, ExplosionLogHelper eLogger) {
ExplosionAccessor eAccess = (ExplosionAccessor) e; ExplosionAccessor eAccess = (ExplosionAccessor) e;
entityList.clear();
boolean eventNeeded = EXPLOSION_OUTCOME.isNeeded() && !eAccess.getWorld().isClient();
blastCalc(e); blastCalc(e);
if (!CarpetSettings.explosionNoBlockDamage) { if (!CarpetSettings.explosionNoBlockDamage) {
@@ -156,6 +160,11 @@ public class OptimizedExplosion
densityCache.put(pair, density); densityCache.put(pair, density);
} }
// If it is needed, it saves the entity
if (eventNeeded) {
entityList.add(entity);
}
double d10 = (1.0D - d12) * density; double d10 = (1.0D - d12) * density;
entity.damage(e.getDamageSource(), entity.damage(e.getDamageSource(),
(float) ((int) ((d10 * d10 + d10) / 2.0D * 7.0D * (double) f3 + 1.0D))); (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 posY = eAccess.getY();
double posZ = eAccess.getZ(); 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; boolean damagesTerrain = eAccess.getDestructionType() != Explosion.DestructionType.NONE;
// explosionSound incremented till disabling the explosion particles and sound // explosionSound incremented till disabling the explosion particles and sound
@@ -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<BlockPos> affectedBlocks;
@Shadow @Final private Explosion.DestructionType destructionType;
@Shadow @Final private @Nullable Entity entity;
@Shadow /*@Nullable*/ public abstract /*@Nullable*/ LivingEntity getCausingEntity();
private List<Entity> affectedEntities;
@Inject(method = "<init>(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<BlockPos> s1, float f1, int i1, int i2, int i3, int i4, int i5, int i6, List<Entity> 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);
}
}
}
@@ -6,13 +6,18 @@ import carpet.script.CarpetEventServer;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.world.entity.EntityLike; import net.minecraft.world.entity.EntityLike;
import net.minecraft.entity.LightningEntity; import net.minecraft.entity.LightningEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.world.ServerEntityManager; import net.minecraft.server.world.ServerEntityManager;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.profiler.Profiler; import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.chunk.WorldChunk; 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 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.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; 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.Inject;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD; import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
import static carpet.script.CarpetEventServer.Event.EXPLOSION;
import static carpet.script.CarpetEventServer.Event.LIGHTNING; import static carpet.script.CarpetEventServer.Event.LIGHTNING;
@Mixin(ServerWorld.class) @Mixin(ServerWorld.class)
@@ -62,6 +69,13 @@ public class ServerWorld_scarpetMixin implements ServerWorldInterface
return success; 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<Explosion> cir)
{
if (EXPLOSION.isNeeded())
EXPLOSION.onExplosion((ServerWorld) (Object)this, entity, null, d, e, f, g, bl, null, null, destructionType);
}
@Final @Final
@Shadow @Shadow
private ServerWorldProperties worldProperties; private ServerWorldProperties worldProperties;
@@ -5,6 +5,7 @@ import carpet.CarpetSettings;
import carpet.helpers.TickSpeed; import carpet.helpers.TickSpeed;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BlockValue; import carpet.script.value.BlockValue;
import carpet.script.value.BooleanValue;
import carpet.script.value.EntityValue; import carpet.script.value.EntityValue;
import carpet.script.value.FunctionValue; import carpet.script.value.FunctionValue;
import carpet.script.value.ListValue; import carpet.script.value.ListValue;
@@ -19,8 +20,11 @@ import carpet.utils.Messenger;
import com.mojang.brigadier.exceptions.CommandSyntaxException; 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.LivingEntity;
import net.minecraft.entity.TntEntity;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.passive.MerchantEntity; import net.minecraft.entity.passive.MerchantEntity;
import net.minecraft.entity.projectile.ProjectileEntity;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -42,6 +46,7 @@ import net.minecraft.village.Merchant;
import net.minecraft.village.TradeOffer; import net.minecraft.village.TradeOffer;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.explosion.Explosion;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList; import java.util.ArrayList;
@@ -52,6 +57,7 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; 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<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> 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<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> 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) 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 onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {} public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {}
public void onExplosion(ServerWorld world, Entity e, Supplier<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> affectedEntities, Explosion.DestructionType type) { }
public void onWorldEvent(ServerWorld world, BlockPos pos) { } public void onWorldEvent(ServerWorld world, BlockPos pos) { }
public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { } public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
public void onCarpetRuleChanges(ParsedRule<?> rule, ServerCommandSource source) { } public void onCarpetRuleChanges(ParsedRule<?> rule, ServerCommandSource source) { }
@@ -50,6 +50,7 @@ import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.FallingBlockEntity; import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.pathing.NavigationType; import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItem;
import net.minecraft.item.Item; import net.minecraft.item.Item;
@@ -61,6 +62,7 @@ import net.minecraft.item.SwordItem;
import net.minecraft.item.TridentItem; import net.minecraft.item.TridentItem;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtElement;
import net.minecraft.network.packet.s2c.play.ExplosionS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkTicket; import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketType; 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.BlockPos;
import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.Registry;
import net.minecraft.world.Heightmap; import net.minecraft.world.Heightmap;
import net.minecraft.world.LightType; 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.Chunk;
import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.gen.ChunkRandom; import net.minecraft.world.gen.ChunkRandom;
import net.minecraft.world.gen.chunk.ChunkGenerator; import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.feature.ConfiguredStructureFeature; 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.PointOfInterest;
import net.minecraft.world.poi.PointOfInterestStorage; import net.minecraft.world.poi.PointOfInterestStorage;
import net.minecraft.world.poi.PointOfInterestType; import net.minecraft.world.poi.PointOfInterestType;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -971,6 +976,88 @@ public class WorldAccess {
return success ? LazyValue.TRUE : LazyValue.FALSE; 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 // TODO rename to use_item
expression.addLazyFunction("place_item", -1, (c, t, lv) -> expression.addLazyFunction("place_item", -1, (c, t, lv) ->
{ {
@@ -7,6 +7,7 @@ import carpet.script.Tokenizer;
import carpet.script.value.FunctionValue; import carpet.script.value.FunctionValue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier; 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) public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
{ {
super("Error"); this(c, e, t, message, Collections.emptyList());
lazyMessage = () -> makeMessage(c, e, t, message);
token = t;
context = c;
} }
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List<FunctionValue> stack) public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List<FunctionValue> stack)
{ {
@@ -101,6 +101,12 @@ public class EntityValue extends Value
entity = e; entity = e;
} }
public static Value of(Entity e)
{
if (e == null) return Value.NULL;
return new EntityValue(e);
}
private static final Map<String, EntitySelector> selectorCache = new HashMap<>(); private static final Map<String, EntitySelector> selectorCache = new HashMap<>();
public static Collection<? extends Entity > getEntitiesFromSelector(ServerCommandSource source, String selector) public static Collection<? extends Entity > getEntitiesFromSelector(ServerCommandSource source, String selector)
{ {
@@ -328,3 +328,32 @@ __on_close() ->
print('App is closing for '+player()); print('App is closing for '+player());
logger('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));
);
+1
View File
@@ -131,6 +131,7 @@
"TntBlockMixin", "TntBlockMixin",
"ExplosionMixin", "ExplosionMixin",
"ExplosionAccessor", "ExplosionAccessor",
"Explosion_scarpetEventMixin",
"TntEntityMixin", "TntEntityMixin",
"ProjectileEntityMixin", "ProjectileEntityMixin",