mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-27 15:17:59 +01:00
Added multi-block destruction
This commit is contained in:
parent
f7a2053c78
commit
d1f6679882
10 changed files with 286 additions and 7 deletions
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,8 @@
|
|||
"client.MixinItemModels",
|
||||
"client.MixinKeyboardInput",
|
||||
"client.MixinLightmapTextureManager",
|
||||
"client.MixinMouse"
|
||||
"client.MixinMouse",
|
||||
"client.MixinWorldRenderer"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
Loading…
Reference in a new issue