Added multi-block destruction

This commit is contained in:
Sollace 2021-01-26 22:32:19 +02:00
parent f7a2053c78
commit d1f6679882
10 changed files with 286 additions and 7 deletions

View file

@ -0,0 +1,87 @@
package com.minelittlepony.unicopia;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgBlockDestruction;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class BlockDestructionManager {
public static final int UNSET_DAMAGE = -1;
public static final int MAX_DAMAGE = 10;
private final Destruction emptyDestruction = new Destruction(BlockPos.ORIGIN);
private final World world;
private final Long2ObjectMap<Destruction> destructions = new Long2ObjectOpenHashMap<>();
private final Object locker = new Object();
public BlockDestructionManager(World world) {
this.world = world;
}
public int getBlockDestruction(BlockPos pos) {
return destructions.getOrDefault(pos.asLong(), emptyDestruction).amount;
}
public void clearBlockDestruction(BlockPos pos) {
setBlockDestruction(pos, -1);
}
public void setBlockDestruction(BlockPos pos, int amount) {
synchronized (locker) {
destructions.computeIfAbsent(pos.asLong(), p -> new Destruction(pos)).set(amount);
}
}
public int damageBlock(BlockPos pos, int amount) {
amount = Math.max(getBlockDestruction(pos), 0) + amount;
setBlockDestruction(pos, amount);
return amount;
}
public void tick() {
synchronized (locker) {
destructions.long2ObjectEntrySet().removeIf(entry -> entry.getValue().tick());
}
}
private class Destruction {
BlockPos pos;
int amount = -1;
int age = 50;
Destruction(BlockPos pos) {
this.pos = pos;
}
boolean tick() {
if (age-- > 0) {
return false;
}
if (amount >= 0) {
set(amount - 1);
}
return amount < 0 || age-- <= 0;
}
void set(int amount) {
this.age = 50;
this.amount = amount >= 0 && amount < MAX_DAMAGE ? amount : UNSET_DAMAGE;
if (world instanceof ServerWorld) {
Channel.SERVER_BLOCK_DESTRUCTION.send(world, new MsgBlockDestruction(pos, this.amount));
}
}
}
public interface Source {
BlockDestructionManager getDestructionManager();
}
}

View file

@ -33,7 +33,10 @@ public class Unicopia implements ModInitializer {
UTags.bootstrap();
Commands.bootstrap();
ServerTickEvents.END_WORLD_TICK.register(AwaitTickQueue::tick);
ServerTickEvents.END_WORLD_TICK.register(w -> {
AwaitTickQueue.tick(w);
((BlockDestructionManager.Source)w).getDestructionManager().tick();
});
UItems.bootstrap();
UPotions.bootstrap();

View file

@ -10,7 +10,7 @@ public class WorldTribeManager extends PersistentState {
private Race defaultRace = Unicopia.getConfig().getPrefferedRace();
public WorldTribeManager(ServerWorld world) {
super("unicopia:tribes" + world.getDimension().getSuffix());
super(nameFor(world.getDimension()));
}
public Race getDefaultRace() {

View file

@ -0,0 +1,83 @@
package com.minelittlepony.unicopia.client;
import java.util.SortedSet;
import com.google.common.collect.Sets;
import com.minelittlepony.unicopia.BlockDestructionManager;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.client.render.BlockBreakingInfo;
import net.minecraft.util.math.BlockPos;
public class ClientBlockDestructionManager {
private final Long2ObjectMap<Destruction> destructions = new Long2ObjectOpenHashMap<>();
private final Long2ObjectMap<SortedSet<BlockBreakingInfo>> combined = new Long2ObjectOpenHashMap<>();
private final Object locker = new Object();
public void setBlockDestruction(BlockPos pos, int amount) {
synchronized (locker) {
if (amount <= 0 || amount > BlockDestructionManager.MAX_DAMAGE) {
destructions.remove(pos.asLong());
} else {
destructions.computeIfAbsent(pos.asLong(), p -> new Destruction(pos)).set(amount);
}
}
}
public void tick(Long2ObjectMap<SortedSet<BlockBreakingInfo>> vanilla) {
synchronized (locker) {
destructions.long2ObjectEntrySet().removeIf(entry -> entry.getValue().tick());
combined.clear();
if (!destructions.isEmpty()) {
destructions.forEach((pos, value) -> {
combined.computeIfAbsent(pos.longValue(), p -> Sets.newTreeSet()).add(value.info);
});
vanilla.forEach((pos, value) -> {
combined.computeIfAbsent(pos.longValue(), p -> Sets.newTreeSet()).addAll(value);
});
}
}
}
public Long2ObjectMap<SortedSet<BlockBreakingInfo>> getCombinedDestructions(Long2ObjectMap<SortedSet<BlockBreakingInfo>> vanilla) {
return destructions.isEmpty() ? vanilla : combined;
}
private class Destruction {
int age = 50;
BlockBreakingInfo info;
Destruction(BlockPos pos) {
this.info = new BlockBreakingInfo(0, pos);
}
boolean tick() {
if (age-- > 0) {
return false;
}
int amount = info.getStage();
if (amount >= 0) {
amount--;
set(amount);
}
return amount < 0 || age-- <= 0;
}
void set(int amount) {
this.age = 50;
info.setStage(amount >= 0 && amount < BlockDestructionManager.MAX_DAMAGE ? amount : BlockDestructionManager.UNSET_DAMAGE);
}
}
public interface Source {
ClientBlockDestructionManager getDestructionManager();
int getTicks();
}
}

View file

@ -179,7 +179,7 @@ public class Pony implements Caster<PlayerEntity>, Equine<PlayerEntity>, Transmi
if (entity instanceof ServerPlayerEntity) {
MsgOtherPlayerCapabilities packet = new MsgOtherPlayerCapabilities(full, this);
Channel.SERVER_PLAYER_CAPABILITIES.send(entity, packet);
Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity, packet);
Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity.world, packet);
}
}

View file

@ -7,6 +7,8 @@ import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import com.minelittlepony.unicopia.BlockDestructionManager;
import com.minelittlepony.unicopia.entity.behaviour.Disguise;
import net.minecraft.entity.Entity;
@ -16,7 +18,15 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
@Mixin(World.class)
abstract class MixinWorld implements WorldAccess {
abstract class MixinWorld implements WorldAccess, BlockDestructionManager.Source {
private final BlockDestructionManager destructions = new BlockDestructionManager((World)(Object)this);
@Override
public BlockDestructionManager getDestructionManager() {
return destructions;
}
@Override
public Stream<VoxelShape> getEntityCollisions(@Nullable Entity entity, Box box, Predicate<Entity> predicate) {
if (box.getAverageSideLength() >= 1.0E-7D) {

View file

@ -0,0 +1,56 @@
package com.minelittlepony.unicopia.mixin.client;
import java.util.SortedSet;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
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 com.minelittlepony.unicopia.client.ClientBlockDestructionManager;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.client.render.BlockBreakingInfo;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.resource.SynchronousResourceReloadListener;
@Mixin(WorldRenderer.class)
abstract class MixinWorldRenderer implements SynchronousResourceReloadListener, AutoCloseable, ClientBlockDestructionManager.Source {
private final ClientBlockDestructionManager destructions = new ClientBlockDestructionManager();
@Shadow
private @Final Long2ObjectMap<SortedSet<BlockBreakingInfo>> blockBreakingProgressions;
@Override
public ClientBlockDestructionManager getDestructionManager() {
return destructions;
}
@Override
@Accessor("ticks")
public abstract int getTicks();
@Redirect(method = "render("
+ "Lnet/minecraft/client/util/math/MatrixStack;"
+ "FLZ"
+ "Lnet/minecraft/client/render/Camera;"
+ "Lnet/minecraft/client/render/GameRenderer;"
+ "Lnet/minecraft/client/render/LightmapTextureManager;"
+ "Lnet/minecraft/util/math/Matrix4f;"
+ ")V",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/WorldRenderer;blockBreakingProgressions:Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;")
)
private Long2ObjectMap<SortedSet<BlockBreakingInfo>> redirectGetDamagesMap(WorldRenderer sender) {
return destructions.getCombinedDestructions(blockBreakingProgressions);
}
@Inject(method = "tick()V", at = @At("RETURN"))
private void onTick(CallbackInfo info) {
destructions.tick(blockBreakingProgressions);
}
}

View file

@ -10,6 +10,7 @@ import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
import net.minecraft.network.PacketByteBuf;
public interface Channel {
@ -21,6 +22,8 @@ public interface Channel {
MPacketType<MsgOtherPlayerCapabilities> SERVER_OTHER_PLAYER_CAPABILITIES = serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new);
CPacketType<MsgSpawnProjectile> SERVER_SPAWN_PROJECTILE = serverToClient(new Identifier("unicopia", "projectile_entity"), MsgSpawnProjectile::new);
MPacketType<MsgBlockDestruction> SERVER_BLOCK_DESTRUCTION = serverToClients(new Identifier("unicopia", "block_destruction"), MsgBlockDestruction::new);
static void bootstrap() { }
static <T extends Packet> SPacketType<T> clientToServer(Identifier id, Function<PacketByteBuf, T> factory) {
@ -45,8 +48,8 @@ public interface Channel {
interface MPacketType<T extends Packet> {
Identifier getId();
default void send(PlayerEntity sender, T packet) {
sender.world.getPlayers().forEach(player -> {
default void send(World world, T packet) {
world.getPlayers().forEach(player -> {
if (player != null) {
ServerSidePacketRegistry.INSTANCE.sendToPlayer(player, getId(), packet.toBuffer());
}

View file

@ -0,0 +1,36 @@
package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.client.ClientBlockDestructionManager;
import net.fabricmc.fabric.api.network.PacketContext;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.math.BlockPos;
import net.minecraft.client.MinecraftClient;
public class MsgBlockDestruction implements Channel.Packet {
private final BlockPos pos;
private final int amount;
MsgBlockDestruction(PacketByteBuf buffer) {
pos = buffer.readBlockPos();
amount = buffer.readByte();
}
public MsgBlockDestruction(BlockPos pos, int amount) {
this.pos = pos;
this.amount = amount;
}
@Override
public void toBuffer(PacketByteBuf buffer) {
buffer.writeBlockPos(pos);
buffer.writeByte(amount);
}
@Override
public void handle(PacketContext context) {
((ClientBlockDestructionManager.Source)MinecraftClient.getInstance().worldRenderer).getDestructionManager().setBlockDestruction(pos, amount);
}
}

View file

@ -31,7 +31,8 @@
"client.MixinItemModels",
"client.MixinKeyboardInput",
"client.MixinLightmapTextureManager",
"client.MixinMouse"
"client.MixinMouse",
"client.MixinWorldRenderer"
],
"injectors": {
"defaultRequire": 1