first commit

This commit is contained in:
Patrick
2026-05-01 19:42:33 +02:00
commit e448a542dc
402 changed files with 23697 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
val worldEditVersion: String by project
val includeImplementation: Configuration by configurations.creating {
configurations.implementation.configure { extendsFrom(this@creating) }
}
dependencies {
api(project(":hero-api", configuration = "namedElements"))
api(project(":datatracker", configuration = "namedElements"))
api(project(":katara", configuration = "namedElements"))
api(project(":aang", configuration = "namedElements"))
api(project(":toph", configuration = "namedElements"))
modApi(libs.bundles.fabric)
modApi(libs.bundles.silk)
modApi(libs.bundles.nrc)
modApi(libs.bundles.performance)
modApi(libs.owolib)
modApi(libs.npcLibApi)
modApi(libs.npcLibCommon)
modApi(libs.geckolib)
modApi(libs.emoteLib)
modImplementation(libs.bundles.cloudnet)
modCompileOnly(libs.worldedit)
includeImplementation(libs.bundles.mongodb)
includeImplementation(libs.geantyref)
modImplementation(libs.hglabor.database.utils) {
exclude(module = "fabric-api")
exclude(module = "hglabor-utils-events")
}
include(libs.hglabor.database.utils)
//includeImplementation(libs.bundles.hglaborutils)
modImplementation(files("../libs/npc-lib-fabric-3.0.0-SNAPSHOT.jar"))
// modCompileOnly("com.sk89q.worldedit:worldedit-fabric-mc${worldEditVersion}") // Ändere die Versionsnummer entsprechend der gewünschten Version
handleIncludes(includeImplementation)
}
fun DependencyHandlerScope.includeTransitive(
dependencies: Set<ResolvedDependency>,
fabricLanguageKotlinDependency: ResolvedDependency?,
checkedDependencies: MutableSet<ResolvedDependency> = HashSet()
) {
val minecraftDependencies = listOf(
"slf4j-api",
"commons-logging",
"oshi-core",
"jna",
"jna-platform",
"gson",
"commons-lang3",
"jackson-annotations",
"jackson-core",
"jackson-databind",
)
dependencies.forEach {
if (checkedDependencies.contains(it) /*|| it.moduleGroup == "org.jetbrains.kotlin" || it.moduleGroup == "org.jetbrains.kotlinx"*/) return@forEach
if (it.name.startsWith("net.fabric")) {
checkedDependencies += it
return@forEach
}
if (it.name.startsWith("net.silkmc")) {
checkedDependencies += it
return@forEach
}
if (fabricLanguageKotlinDependency?.children?.any { kotlinDep -> kotlinDep.name == it.name } == true) {
println("Skipping -> ${it.name} (already in fabric-language-kotlin)")
} else if (minecraftDependencies.any { dep -> dep == it.moduleName }) {
println("Skipping -> ${it.name} (already in minecraft)")
} else {
include(it.name)
println("Including -> ${it.name}")
}
checkedDependencies += it
includeTransitive(it.children, fabricLanguageKotlinDependency, checkedDependencies)
}
}
fun DependencyHandlerScope.implementAndInclude(dep: Any) {
modImplementation(dep)
include(dep)
}
fun DependencyHandlerScope.handleIncludes(configuration: Configuration) {
includeTransitive(
configuration.resolvedConfiguration.firstLevelModuleDependencies,
configurations.modImplementation.get().resolvedConfiguration.firstLevelModuleDependencies
.firstOrNull() { it.moduleGroup == "net.fabricmc" && it.moduleName == "fabric-language-kotlin" },
)
}
loom {
accessWidenerPath.set(file("src/main/resources/ffa-server.accesswidener"))
}
@@ -0,0 +1,17 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.mechanics.lootdrop.Lootdrop;
import net.minecraft.block.entity.BarrelBlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(BarrelBlockEntity.class)
public class BarrelBlockEntityMixin {
@Inject(method = "onOpen", at = @At("HEAD"))
private void onBarrelOpen(PlayerEntity playerEntity, CallbackInfo ci) {
Lootdrop.Companion.barrelOpened((BarrelBlockEntity) (Object) this, playerEntity);
}
}
@@ -0,0 +1,101 @@
package gg.norisk.ffa.server.mixin;
import net.minecraft.advancement.criterion.Criteria;
import net.minecraft.block.BlockState;
import net.minecraft.block.FluidDrainable;
import net.minecraft.block.FluidFillable;
import net.minecraft.block.LeavesBlock;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.stat.Stats;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.*;
//Thanks to https://github.com/K0LALA/WaterProofLeaves
@Mixin(BucketItem.class)
public abstract class BucketItemMixin extends Item {
@Shadow @Final private Fluid fluid;
@Shadow public abstract boolean placeFluid(@Nullable PlayerEntity player, World world, BlockPos pos, @Nullable BlockHitResult hitResult);
@Shadow public abstract void onEmptied(@Nullable PlayerEntity player, World world, ItemStack stack, BlockPos pos);
public BucketItemMixin(Settings settings) {
super(settings);
}
@Unique
private static ItemStack getEmptiedStack(ItemStack stack, PlayerEntity player) {
return !player.isInCreativeMode() ? new ItemStack(Items.BUCKET) : stack;
}
/**
* @author Kolala
* @reason Avoid placing water in leaves without sneaking
*/
@Overwrite
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
ItemStack itemStack = user.getStackInHand(hand);
BlockHitResult blockHitResult = raycast(
world, user, this.fluid == Fluids.EMPTY ? RaycastContext.FluidHandling.SOURCE_ONLY : RaycastContext.FluidHandling.NONE
);
if (blockHitResult.getType() == HitResult.Type.MISS) {
return TypedActionResult.pass(itemStack);
} else if (blockHitResult.getType() != HitResult.Type.BLOCK) {
return TypedActionResult.pass(itemStack);
} else {
BlockPos blockPos = blockHitResult.getBlockPos();
Direction direction = blockHitResult.getSide();
BlockPos blockPos2 = blockPos.offset(direction);
if (!world.canPlayerModifyAt(user, blockPos) || !user.canPlaceOn(blockPos2, direction, itemStack)) {
return TypedActionResult.fail(itemStack);
} else if (this.fluid == Fluids.EMPTY) {
BlockState blockState = world.getBlockState(blockPos);
if (blockState.getBlock() instanceof FluidDrainable fluidDrainable) {
ItemStack itemStack2 = fluidDrainable.tryDrainFluid(user, world, blockPos, blockState);
if (!itemStack2.isEmpty()) {
user.incrementStat(Stats.USED.getOrCreateStat(this));
fluidDrainable.getBucketFillSound().ifPresent(sound -> user.playSound(sound, 1.0F, 1.0F));
world.emitGameEvent(user, GameEvent.FLUID_PICKUP, blockPos);
ItemStack itemStack3 = ItemUsage.exchangeStack(itemStack, user, itemStack2);
if (!world.isClient) {
Criteria.FILLED_BUCKET.trigger((ServerPlayerEntity)user, itemStack2);
}
return TypedActionResult.success(itemStack3, world.isClient());
}
}
return TypedActionResult.fail(itemStack);
} else {
BlockState blockState = world.getBlockState(blockPos);
BlockPos blockPos3 = blockState.getBlock() instanceof FluidFillable && this.fluid == Fluids.WATER && !(blockState.getBlock() instanceof LeavesBlock && !user.isSneaking()) ? blockPos : blockPos2;
if (this.placeFluid(user, world, blockPos3, blockHitResult)) {
this.onEmptied(user, world, itemStack, blockPos3);
if (user instanceof ServerPlayerEntity) {
Criteria.PLACED_BLOCK.trigger((ServerPlayerEntity)user, blockPos3, itemStack);
}
user.incrementStat(Stats.USED.getOrCreateStat(this));
ItemStack itemStack2 = ItemUsage.exchangeStack(itemStack, user, getEmptiedStack(itemStack, user));
return TypedActionResult.success(itemStack2, world.isClient());
} else {
return TypedActionResult.fail(itemStack);
}
}
}
}
}
@@ -0,0 +1,19 @@
package gg.norisk.ffa.server.mixin;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(CommandManager.class)
public abstract class CommandManagerMixin {
@WrapWithCondition(
method = "<init>",
at = @At(value = "INVOKE", target = "Lnet/minecraft/server/command/KillCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V")
)
private boolean dontAllowKillCommand(CommandDispatcher<ServerCommandSource> commandDispatcher) {
return false;
}
}
@@ -0,0 +1,75 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.ext.IDamageTrackerExt;
import gg.norisk.ffa.server.mechanics.CombatTag;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.damage.DamageTracker;
import net.minecraft.entity.damage.DamageTypes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.Text;
import net.silkmc.silk.core.server.ServerExtensionsKt;
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.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DamageTracker.class)
public abstract class DamageTrackerMixin implements IDamageTrackerExt {
@Shadow
@Final
private LivingEntity entity;
@Unique
private PlayerEntity lastPlayer;
@Inject(
method = "onDamage",
at = @At("HEAD"),
cancellable = true
)
private void dontApplyKillDamage(DamageSource damageSource, float f, CallbackInfo ci) {
PlayerEntity attacker = null;
if (damageSource.getAttacker() instanceof PlayerEntity player && entity != player) {
lastPlayer = player;
attacker = player;
}
//System.out.println("### APPLYING " + damageSource);
if (damageSource.isOf(DamageTypes.GENERIC_KILL)) {
ci.cancel();
} else if (attacker == null) {
//only player combat should trigger combat logger
ci.cancel();
}
}
@Inject(method = "update", at = @At(value = "INVOKE", target = "Ljava/util/List;clear()V"))
private void ffa$updateEnd(CallbackInfo ci) {
lastPlayer = null;
}
@ModifyConstant(method = "update", constant = @Constant(intValue = 300))
private int modifyCombatTagTime(int constant) {
return CombatTag.INSTANCE.getTicks();
}
@ModifyConstant(method = "update", constant = @Constant(intValue = 100))
private int modifyCombatTagTime2(int constant) {
return CombatTag.INSTANCE.getTicks();
}
@Override
public @Nullable PlayerEntity getFfa_lastPlayer() {
return lastPlayer;
}
@Override
public void setFfa_lastPlayer(@Nullable PlayerEntity player) {
lastPlayer = player;
}
}
@@ -0,0 +1,18 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.mechanics.lootdrop.Lootdrop;
import net.minecraft.block.Block;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(FallingBlockEntity.class)
public class FallingBlockEntityMixin {
@Inject(method = "onDestroyedOnLanding", at = @At("HEAD"))
private void onDestroyedOnLanding(Block block, BlockPos blockPos, CallbackInfo ci) {
Lootdrop.Companion.fallingBlockLanded((FallingBlockEntity) (Object) this);
}
}
@@ -0,0 +1,18 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.mechanics.KitEditor;
import net.minecraft.entity.player.HungerManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(HungerManager.class)
public abstract class HungerManagerMixin {
@ModifyConstant(method = "update", constant = @Constant(floatValue = 1f, ordinal = 0))
private float injected(float constant) {
if (KitEditor.INSTANCE.isUHC()) {
return constant;
}
return 0.25f;
}
}
@@ -0,0 +1,30 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.mechanics.KitEditor;
import gg.norisk.ffa.server.mechanics.SoupHealing;
import gg.norisk.ffa.server.mechanics.Tracker;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemStack.class)
public abstract class ItemStackMixin {
@Inject(
method = "use",
at = @At("HEAD"),
cancellable = true
)
public void onUse(World world, PlayerEntity playerEntity, Hand hand, CallbackInfoReturnable<TypedActionResult<ItemStack>> cir) {
ItemStack itemStack = (ItemStack) (Object) this;
if (!KitEditor.INSTANCE.isUHC()) {
SoupHealing.INSTANCE.onPotentialSoupUse(playerEntity, itemStack.getItem(), cir, world, hand);
}
Tracker.INSTANCE.onTrackerUse(playerEntity, itemStack, cir, world, hand);
}
}
@@ -0,0 +1,21 @@
package gg.norisk.ffa.server.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(MinecraftDedicatedServer.class)
public abstract class MinecraftDedicatedServerMixin {
@ModifyReturnValue(
method = "isWorldAllowed",
at = @At("RETURN")
)
private boolean disableNether(boolean original, World world) {
if (world.getRegistryKey() == World.NETHER) {
return false;
}
return original;
}
}
@@ -0,0 +1,8 @@
package gg.norisk.ffa.server.mixin;
import net.minecraft.item.MiningToolItem;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(MiningToolItem.class)
public abstract class MiningToolItemMixin {
}
@@ -0,0 +1,18 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.mechanics.lootdrop.Lootdrop;
import net.minecraft.entity.projectile.PersistentProjectileEntity;
import net.minecraft.util.hit.EntityHitResult;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PersistentProjectileEntity.class)
public class PersistentProjectileEntityMixin {
@Inject(method = "onEntityHit", at = @At("HEAD"))
private void onDestroyedOnLanding(EntityHitResult entityHitResult, CallbackInfo ci) {
Lootdrop.Companion.projectileHit((PersistentProjectileEntity) (Object) this, entityHitResult.getEntity());
}
}
@@ -0,0 +1,71 @@
package gg.norisk.ffa.server.mixin;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import gg.norisk.ffa.server.mechanics.CombatTag;
import gg.norisk.ffa.server.mechanics.KitEditor;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.world.World;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin extends LivingEntity implements CombatTag.ICombatPlayer {
@Shadow
@NotNull
public abstract ItemStack getWeaponStack();
@Shadow public abstract void remove(RemovalReason reason);
@Unique
private int ffaCombatTicks;
protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
super(entityType, world);
}
@ModifyConstant(method = "attack", constant = @Constant(floatValue = 1.5f))
private float injected(float constant) {
if (KitEditor.INSTANCE.isUHC()) {
return constant;
}
return 1.18f;
}
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getAttributeValue(Lnet/minecraft/registry/entry/RegistryEntry;)D", ordinal = 0)
)
private double axeDamageNerf(PlayerEntity instance, RegistryEntry<?> registryEntry, Operation<Double> original) {
var originalValue = original.call(instance, registryEntry);
if (KitEditor.INSTANCE.isUHC()) {
return originalValue;
} else {
if (getWeaponStack().isIn(ItemTags.AXES)) {
return originalValue / 4;
} else {
return originalValue;
}
}
}
@Override
public int getFfa_combatTicks() {
return ffaCombatTicks;
}
@Override
public void setFfa_combatTicks(int i) {
this.ffaCombatTicks = i;
}
}
@@ -0,0 +1,62 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.world.WorldManager;
import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.silkmc.silk.core.Silk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayNetworkHandler.class)
public abstract class ServerPlayNetworkHandlerMixin {
@Shadow
public ServerPlayerEntity player;
@Inject(method = "onPlayerAction", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V", shift = At.Shift.AFTER), cancellable = true)
private void cancelOnPlayerAction(PlayerActionC2SPacket playerActionC2SPacket, CallbackInfo ci) {
if (!WorldManager.INSTANCE.isInKitEditorWorld(player)) {
return;
}
switch (playerActionC2SPacket.getAction()) {
case DROP_ITEM, DROP_ALL_ITEMS -> {
updateInv();
ci.cancel();
}
}
}
@Inject(method = "onClickSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V", shift = At.Shift.AFTER), cancellable = true)
private void cancelOnClickSlot(ClickSlotC2SPacket clickSlotC2SPacket, CallbackInfo ci) {
if (!WorldManager.INSTANCE.isInKitEditorWorld(player)) {
return;
}
if (clickSlotC2SPacket.getActionType() == SlotActionType.THROW) {
updateInv();
ci.cancel();
return;
}
if (clickSlotC2SPacket.getSlot() == -999) {
//player.sendMessage(Text.of("Action" + clickSlotC2SPacket.getActionType()));
// player.sendMessage(Text.of("Button" + clickSlotC2SPacket.getButton()));
// player.sendMessage(Text.of("Slot: " + clickSlotC2SPacket.getSlot()));
if (clickSlotC2SPacket.getActionType() == SlotActionType.QUICK_CRAFT) {
return;
}
updateInv();
ci.cancel();
}
}
@Unique
private void updateInv() {
player.getInventory().updateItems();
Silk.INSTANCE.getServerOrThrow().getPlayerManager().sendPlayerStatus(player);
}
}
@@ -0,0 +1,16 @@
package gg.norisk.ffa.server.mixin;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ServerPlayerEntity.class)
public abstract class ServerPlayerEntityMixin extends PlayerEntity {
public ServerPlayerEntityMixin(World world, BlockPos blockPos, float f, GameProfile gameProfile) {
super(world, blockPos, f, gameProfile);
}
}
@@ -0,0 +1,24 @@
package gg.norisk.ffa.server.mixin;
import gg.norisk.ffa.server.mechanics.KitEditor;
import kotlin.random.Random;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.SwordItem;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(SwordItem.class)
public abstract class SwordItemMixin {
@Inject(method = "postDamageEntity", at = @At("HEAD"), cancellable = true)
public void breakReduction(ItemStack stack, LivingEntity target, LivingEntity attacker, CallbackInfo ci) {
if (KitEditor.INSTANCE.isUHC()) {
return;
}
if (Random.Default.nextBoolean()) {
ci.cancel();
}
}
}
@@ -0,0 +1,18 @@
package gg.norisk.ffa.server.mixin;
import net.minecraft.block.WitherSkullBlock;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WitherSkullBlock.class)
public abstract class WitherSkullBlockMixin {
@Inject(method = "onPlaced(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/SkullBlockEntity;)V", at = @At("HEAD"), cancellable = true)
private static void cancelWitherSpawning(World world, BlockPos pos, SkullBlockEntity blockEntity, CallbackInfo ci) {
ci.cancel();
}
}
@@ -0,0 +1,22 @@
package gg.norisk.ffa.server.mixin.accessor;
import net.minecraft.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import javax.annotation.Nullable;
@Mixin(LivingEntity.class)
public interface LivingEntityAccessor {
@Accessor("lastAttackTime")
public int getLastAttackTime();
@Accessor("lastAttackTime")
public void setLastAttackTime(int value);
@Accessor("attacking")
public @Nullable LivingEntity getAttacking();
@Accessor("attacking")
public void setAttacking(@Nullable LivingEntity value);
}
@@ -0,0 +1,42 @@
package gg.norisk.ffa.server
import gg.norisk.datatracker.entity.getSyncedData
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.ffa.server.command.LootdropCommand
import gg.norisk.ffa.server.command.MeCommand
import gg.norisk.ffa.server.mechanics.Bounty
import gg.norisk.ffa.server.mechanics.CombatTag
import gg.norisk.ffa.server.mechanics.KillManager
import gg.norisk.ffa.server.mechanics.KitEditor
import gg.norisk.ffa.server.selector.SelectorServerManager
import gg.norisk.ffa.server.world.MapPlacer
import gg.norisk.ffa.server.world.WorldManager
import gg.norisk.heroes.common.HeroesManager.isServer
import net.fabricmc.api.ModInitializer
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.Identifier
import org.apache.logging.log4j.LogManager
object FFAServer : ModInitializer {
private const val MOD_ID = "ffa-server"
val logger = LogManager.getLogger(MOD_ID)
fun String.toId(): Identifier = Identifier.of(MOD_ID, this)
override fun onInitialize() {
if (!isServer) return
SelectorServerManager.initServer()
WorldManager.initServer()
MeCommand.init()
MapPlacer.init()
LootdropCommand.init()
KitEditor.initServer()
Bounty.init()
CombatTag.init()
KillManager.init()
}
const val FFA_KEY = "hero-ffa"
var PlayerEntity.isFFA: Boolean
get() = this.getSyncedData<Boolean>(FFA_KEY) ?: false
set(value) = this.setSyncedData(FFA_KEY, value)
}
@@ -0,0 +1,18 @@
package gg.norisk.ffa.server.command
import gg.norisk.ffa.server.mechanics.lootdrop.Lootdrop
import net.minecraft.server.world.ServerWorld
import net.silkmc.silk.commands.PermissionLevel
import net.silkmc.silk.commands.command
object LootdropCommand {
fun init() {
command("lootdrop") {
requires { it.playerOrThrow.hasPermissionLevel(PermissionLevel.OWNER.level) }
runs {
Lootdrop(source.playerOrThrow.world as ServerWorld, source.playerOrThrow.blockPos).drop()
}
}
}
}
@@ -0,0 +1,21 @@
package gg.norisk.ffa.server.command
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.text.literalText
object MeCommand {
fun init() {
command("me") {
argument<String>("action") { action ->
runs {
val player = this.source.playerOrThrow
this.source.playerOrThrow.sendMessage(literalText {
text(player.name)
text(": ")
text("Ich bin ein kompletter Versager")
})
}
}
}
}
}
@@ -0,0 +1,11 @@
package gg.norisk.ffa.server.event
import net.minecraft.entity.Entity
import net.minecraft.entity.damage.DamageSource
import net.silkmc.silk.core.event.Event
object FFAEvents {
open class EntityKilledOtherEntityEvent(val killer: Entity, val killed: Entity, val source: DamageSource)
val entityKilledOtherEntityEvent = Event.onlySync<EntityKilledOtherEntityEvent>()
}
@@ -0,0 +1,7 @@
package gg.norisk.ffa.server.ext
import net.minecraft.entity.player.PlayerEntity
interface IDamageTrackerExt {
var ffa_lastPlayer: PlayerEntity?
}
@@ -0,0 +1,89 @@
package gg.norisk.ffa.server.mechanics
import com.mojang.brigadier.arguments.IntegerArgumentType
import gg.norisk.ffa.server.FFAServer.isFFA
import gg.norisk.heroes.common.player.ffaPlayer
import gg.norisk.heroes.common.player.ffaBounty
import gg.norisk.heroes.server.database.player.PlayerProvider
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
import net.minecraft.command.argument.EntityArgumentType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.server.players
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.broadcastText
object Bounty {
fun init() {
command("bounty") {
alias("kopfgeld")
argument("player", EntityArgumentType.player()) {
runsAsync {
val player = EntityArgumentType.getPlayer(this, "player")
val source = this.source.playerOrThrow
val ffaPlayer = PlayerProvider.get(player.uuid)
source.sendMessage(Text.translatable("ffa.mechanic.bounty.info", player.name, ffaPlayer.bounty))
}
argument<Int>("bounty", IntegerArgumentType.integer(100)) { bountyToGive ->
runsAsync {
val player = EntityArgumentType.getPlayer(this, "player")
val source = this.source.playerOrThrow
val ffaPlayer = PlayerProvider.get(player.uuid)
val sourceFfaPlayer = PlayerProvider.get(source.uuid)
println("Player: $sourceFfaPlayer")
if (bountyToGive() > sourceFfaPlayer.xp) {
source.sendMessage(Text.translatable("ffa.mechanic.bounty.not_enough_xp"))
return@runsAsync
}
sourceFfaPlayer.xp -= bountyToGive()
source.ffaPlayer = sourceFfaPlayer
ffaPlayer.bounty += bountyToGive()
player.ffaPlayer = ffaPlayer
PlayerProvider.save(sourceFfaPlayer)
PlayerProvider.save(ffaPlayer)
this.source.server.broadcastText(Text.translatable("ffa.mechanic.bounty.placed", source.name, bountyToGive().toString(), player.name))
}
}
}
}
ServerTickEvents.END_SERVER_TICK.register { server ->
mcCoroutineTask(sync = false, client = false) {
for (player in server.players) {
if (!player.isFFA) continue
updateBountyScoreboard(player)
}
}
}
}
suspend fun receiveBounty(receiver: ServerPlayerEntity, target: ServerPlayerEntity) {
val targetDb = PlayerProvider.get(target.uuid)
val receiverDb = PlayerProvider.get(receiver.uuid)
if (targetDb.bounty > 0) {
val bounty = targetDb.bounty
targetDb.bounty = 0
receiverDb.xp += bounty
receiver.server.broadcastText(Text.translatable("ffa.mechanic.bounty.claimed", receiver.name, bounty, target.name))
receiver.ffaPlayer = receiverDb
target.ffaPlayer = targetDb
PlayerProvider.save(receiverDb)
PlayerProvider.save(targetDb)
}
}
private suspend fun updateBountyScoreboard(player: ServerPlayerEntity) {
val ffaPlayer = PlayerProvider.get(player.uuid)
if (ffaPlayer.bounty != player.ffaBounty) {
player.ffaBounty = ffaPlayer.bounty
}
}
}
@@ -0,0 +1,51 @@
package gg.norisk.ffa.server.mechanics
import gg.norisk.heroes.common.events.HeroEvents
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents
import net.minecraft.entity.player.PlayerEntity
import net.silkmc.silk.core.kotlin.ticks
import net.silkmc.silk.core.task.mcCoroutineTask
object CombatTag {
interface ICombatPlayer {
var ffa_combatTicks: Int
}
var ticks = 15 * 20
fun init() {
ServerPlayConnectionEvents.DISCONNECT.register(ServerPlayConnectionEvents.Disconnect { handler, server ->
mcCoroutineTask(sync = true, client = false) {
val player = handler.player
player.kill()
}
})
HeroEvents.heroDeathEvent.listen { event ->
/*if (event.player.isInCombat()) {
println("LAST DAMAGE: " + event.player.damageTracker.recentDamage)
event.isValidDeath = true
event.player.sendMessage("Du bekommst einen Tod dazugeschrieben weil du in combat warst!".literal)
} else {
event.isValidDeath = false
event.player.sendMessage("Du bekommst keinen Tod dazugeschrieben weil du nicht in combat warst!".literal)
}*/
}
}
fun getCombatTimeAsString(value: Int): String {
val builder = StringBuilder()
value.ticks.toComponents { days, hours, minutes, seconds, _ ->
if (days > 0) builder.append(days).append("d ")
if (hours > 0) builder.append(hours).append("h ")
if (minutes > 0) builder.append(minutes).append("m ")
builder.append(seconds).append("s")
}
return builder.toString()
}
fun PlayerEntity.isInCombat(): Boolean {
val damageTracker = damageTracker
val lastAttackTime = ticks - (age - lastAttackTime)
return damageTracker.hasDamage || lastAttackTime > 0
}
}
@@ -0,0 +1,201 @@
package gg.norisk.ffa.server.mechanics
import gg.norisk.ffa.server.event.FFAEvents
import gg.norisk.ffa.server.ext.IDamageTrackerExt
import gg.norisk.ffa.server.mechanics.CombatTag.isInCombat
import gg.norisk.ffa.server.mixin.accessor.LivingEntityAccessor
import gg.norisk.ffa.server.selector.SelectorServerManager.setSelectorReady
import gg.norisk.heroes.common.events.HeroEvents
import gg.norisk.heroes.common.ffa.experience.ExperienceReason
import gg.norisk.heroes.common.ffa.experience.ExperienceRegistry
import gg.norisk.heroes.common.ffa.experience.addXp
import gg.norisk.heroes.common.player.FFAPlayer
import gg.norisk.heroes.common.player.ffaPlayer
import gg.norisk.heroes.server.database.player.PlayerProvider
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents
import net.minecraft.entity.ItemEntity
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.damage.DamageTypes
import net.minecraft.entity.passive.ChickenEntity
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.silkmc.silk.commands.PermissionLevel
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.Silk
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.broadcastText
import net.silkmc.silk.core.text.literalText
import java.awt.Color
import kotlin.math.min
object KillManager {
fun init() {
killCommand()
ServerLivingEntityEvents.ALLOW_DEATH.register { entity, source, _ ->
val player = entity as? ChickenEntity ?: return@register true
val attacker =
source.attacker as? ServerPlayerEntity? ?: (player.damageTracker as IDamageTrackerExt).ffa_lastPlayer
if (attacker != null) {
FFAEvents.entityKilledOtherEntityEvent.invoke(
FFAEvents.EntityKilledOtherEntityEvent(
attacker,
player,
source
)
)
}
return@register true
}
ServerLivingEntityEvents.ALLOW_DEATH.register { entity, source, _ ->
val player = entity as? ServerPlayerEntity ?: return@register true
(player as LivingEntityAccessor).lastAttackTime = -10000
(player as LivingEntityAccessor).attacking = null
player.damageTracker.hasDamage = false
val attacker =
source.attacker as? ServerPlayerEntity? ?: (player.damageTracker as IDamageTrackerExt).ffa_lastPlayer
if (attacker != null && attacker != player) {
FFAEvents.entityKilledOtherEntityEvent.invoke(
FFAEvents.EntityKilledOtherEntityEvent(
attacker,
player,
source
)
)
}
(player.damageTracker as IDamageTrackerExt).ffa_lastPlayer = null
player.setSelectorReady()
return@register false
}
ServerEntityEvents.ENTITY_LOAD.register { entity, world ->
val item = entity as? ItemEntity? ?: return@register
item.itemAge = 4800
}
FFAEvents.entityKilledOtherEntityEvent.listen { event ->
val wasCombatLog = event.source.isOf(DamageTypes.GENERIC_KILL)
val killer = event.killer as? ServerPlayerEntity?
val killed = event.killed as? ServerPlayerEntity?
killed?.inventory?.dropAll()
Silk.server?.broadcastText(literalText {
text(event.killer.name)
text(" hat ") {
color = Color.YELLOW.rgb
}
text(event.killed.name)
text(" getötet") {
color = Color.YELLOW.rgb
}
if (wasCombatLog) {
text(" ")
text("[Combat Log]") {
color = Color.RED.rgb
}
}
})
mcCoroutineTask(false, false) {
if (killer != null) {
increaseKillsForPlayer(killer)
}
if (killed != null) {
increaseDeathForPlayer(killed, event.source)
}
if (killer != killed && killer != null && killed != null) {
Bounty.receiveBounty(killer, killed)
}
}
}
}
private fun provideExtraXpForKillStreak(player: ServerPlayerEntity, ffaPlayer: FFAPlayer) {
val currentKillStreak = ffaPlayer.currentKillStreak
val killStreakXp = min(3000, ExperienceRegistry.KILLED_PLAYER.value * currentKillStreak * 10)
player.addXp(ExperienceReason("kill_streak", killStreakXp))
}
private fun provideExtraBountyForKillStreak(player: ServerPlayerEntity, ffaPlayer: FFAPlayer) {
val currentKillStreak = ffaPlayer.currentKillStreak
val bountyXp = when (currentKillStreak) {
10 -> 1000
20 -> 2000
else -> return
}
ffaPlayer.bounty += bountyXp
}
private suspend fun increaseKillsForPlayer(attacker: ServerPlayerEntity) {
val cachedAttacker = PlayerProvider.get(attacker.uuid)
cachedAttacker.kills++
cachedAttacker.currentKillStreak++
attacker.ffaPlayer = cachedAttacker
if (cachedAttacker.currentKillStreak.mod(10) == 0 || cachedAttacker.currentKillStreak == 5) {
attacker.server.broadcastText {
text(attacker.name)
text(" hat eine Killstreak von ") {
color = Color.YELLOW.rgb
}
text(cachedAttacker.currentKillStreak.toString()) {
color = Color.RED.rgb
}
}
provideExtraXpForKillStreak(attacker, cachedAttacker)
provideExtraBountyForKillStreak(attacker, cachedAttacker)
}
PlayerProvider.save(cachedAttacker)
attacker.addXp(ExperienceRegistry.KILLED_PLAYER, true)
}
private suspend fun increaseDeathForPlayer(player: ServerPlayerEntity, source: DamageSource) {
val heroDeathEvent = HeroEvents.HeroDeathEvent(player, true)
player.sendMessage(Text.translatable("ffa.died"))
HeroEvents.heroDeathEvent.invoke(heroDeathEvent)
val cachedEntity = PlayerProvider.get(player.uuid)
if (heroDeathEvent.isValidDeath) {
cachedEntity.deaths++
if (cachedEntity.currentKillStreak > cachedEntity.highestKillStreak) {
cachedEntity.highestKillStreak = cachedEntity.currentKillStreak
}
if (cachedEntity.currentKillStreak >= 5) {
player.sendMessage(Text.translatable("ffa.mechanic.killstreak.lost", player.name, cachedEntity.currentKillStreak))
}
cachedEntity.currentKillStreak = 0
player.ffaPlayer = cachedEntity
mcCoroutineTask(sync = false, client = false) {
PlayerProvider.save(cachedEntity)
}
}
if (!source.isOf(DamageTypes.GENERIC_KILL)) {
player.addXp(ExperienceRegistry.PLAYER_DEATH, true)
}
}
private fun killCommand() {
command("kill") {
alias("spawn")
requiresPermissionLevel(PermissionLevel.NONE)
runs {
val player = this.source.playerOrThrow
if (player.isInCombat()) {
player.kill()
} else {
player.setSelectorReady()
}
}
}
}
}
@@ -0,0 +1,163 @@
package gg.norisk.ffa.server.mechanics
import com.github.juliarn.npclib.api.Npc
import com.github.juliarn.npclib.api.Position
import com.github.juliarn.npclib.api.event.AttackNpcEvent
import com.github.juliarn.npclib.api.event.ShowNpcEvent
import com.github.juliarn.npclib.api.profile.Profile
import com.github.juliarn.npclib.api.profile.ProfileProperty
import com.github.juliarn.npclib.api.protocol.meta.EntityMetadataFactory
import com.github.juliarn.npclib.common.event.DefaultInteractNpcEvent
import com.github.juliarn.npclib.fabric.FabricPlatform
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.ffa.server.FFAServer.isFFA
import gg.norisk.ffa.server.FFAServer.logger
import gg.norisk.ffa.server.selector.SelectorServerManager.setSelectorReady
import gg.norisk.ffa.server.selector.SelectorServerManager.setSoupItems
import gg.norisk.ffa.server.selector.SelectorServerManager.setUHCItems
import gg.norisk.ffa.server.world.WorldManager.isInKitEditorWorld
import gg.norisk.heroes.common.events.HeroEvents
import gg.norisk.heroes.common.ffa.KitEditorManager
import gg.norisk.heroes.common.ffa.KitEditorManager.onBack
import gg.norisk.heroes.common.ffa.KitEditorManager.onReset
import gg.norisk.heroes.common.ffa.KitEditorManager.world
import gg.norisk.heroes.common.player.InventorySorting
import gg.norisk.heroes.common.player.InventorySorting.Companion.CURRENT_VERSION
import gg.norisk.heroes.common.utils.PlayStyle
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents.AllowDamage
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.world.World
import net.silkmc.silk.core.event.ServerEvents
import net.silkmc.silk.core.text.literal
import java.util.*
object KitEditor {
val platform by lazy { FabricPlatform.minestomNpcPlatformBuilder().extension(this).actionController({}).build() }
lateinit var backNpc: Npc<World, ServerPlayerEntity, ItemStack, Any>
lateinit var resetNpc: Npc<World, ServerPlayerEntity, ItemStack, Any>
fun initServer() {
logger.info("Initializing Mode: ${PlayStyle.current}")
HeroEvents.preKitEditorEvent.listen { event ->
if (event.player.isFFA) {
event.isCancelled.set(true)
}
}
ServerLivingEntityEvents.ALLOW_DAMAGE.register(AllowDamage { entity, source, amount ->
if ((entity as? ServerPlayerEntity?)?.isInKitEditorWorld() == true) {
return@AllowDamage false
}
return@AllowDamage true
})
KitEditorManager.onBack = {
it.setSelectorReady()
}
KitEditorManager.resetInventory = {
handleKit(it, PlayStyle.current)
}
ServerEvents.postStop.listen { event ->
world = null
}
ServerEvents.postStart.listen { event ->
if (world != null) {
spawnNpcs()
registerNpcEvents()
}
}
}
fun isUHC(): Boolean {
return PlayStyle.current == PlayStyle.UHC
}
fun handleKit(player: PlayerEntity, mode: PlayStyle = PlayStyle.current) {
when (PlayStyle.current) {
PlayStyle.SOUP -> handleSoupKit(player)
PlayStyle.UHC -> handleUHCKit(player)
}
}
fun handleSoupKit(player: PlayerEntity) {
player.inventory.clear()
player.setSoupItems()
}
fun handleUHCKit(player: PlayerEntity) {
player.inventory.clear()
player.setUHCItems()
}
private fun registerNpcEvents() {
val eventManager = platform.eventManager()
eventManager.registerEventHandler(ShowNpcEvent.Post::class.java) { showEvent: ShowNpcEvent.Post ->
val npc = showEvent.npc<World, ServerPlayerEntity, ItemStack, Any>()
val player = showEvent.player<ServerPlayerEntity>()
npc.changeMetadata(EntityMetadataFactory.skinLayerMetaFactory(), true).schedule(player)
}
eventManager.registerEventHandler(DefaultInteractNpcEvent::class.java) { showEvent: DefaultInteractNpcEvent ->
val npc = showEvent.npc<World, ServerPlayerEntity, ItemStack, Any>()
val player = showEvent.player<ServerPlayerEntity>()
if (npc.entityId() == resetNpc.entityId()) {
onReset(player)
} else if (npc.entityId() == backNpc.entityId()) {
onBack(player)
}
}
eventManager.registerEventHandler(AttackNpcEvent::class.java) { showEvent: AttackNpcEvent ->
val npc = showEvent.npc<World, ServerPlayerEntity, ItemStack, Any>()
val player = showEvent.player<ServerPlayerEntity>()
if (npc.entityId() == resetNpc.entityId()) {
onReset(player)
} else if (npc.entityId() == backNpc.entityId()) {
onBack(player)
}
}
}
private fun spawnNpcs() {
backNpc = platform
.newNpcBuilder()
.flag(Npc.LOOK_AT_PLAYER, true)
.flag(Npc.HIT_WHEN_PLAYER_HITS, true)
.flag(Npc.SNEAK_WHEN_PLAYER_SNEAKS, true)
.position(Position.position(1.5, 90.00, 5.5, "hero-api:kit-editor"))
.profile(
Profile.resolved(
"FFA", UUID.randomUUID(), setOf(
ProfileProperty.property(
"textures",
"eyJ0aW1lc3RhbXAiOjE1ODQ0NjA2NDI0OTEsInByb2ZpbGVJZCI6ImIwZDRiMjhiYzFkNzQ4ODlhZjBlODY2MWNlZTk2YWFiIiwicHJvZmlsZU5hbWUiOiJNaW5lU2tpbl9vcmciLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2Q5MzEzZjM2OTdhMGZmZjk1MzE1NDlmMzNhNjIyZmUxOWY2MTZhN2Q1OTA3MTY2NDY1Y2EzMDYyOGMzYzFjZWEifX19",
"cdjJcFcAQtn6GtdJSkaLrQl2IlzUpkbDSSLl/a6/IGoJWJu7SDjZeXRKSJ55MYo5KZu38dG1dmlxiEhlF9pRfWtxW4+NXm7EI5fpKeoHBfXyxR3wJC5Yujo+9T+5TQkjAc4zGvgSQS4cRlqa231W4T77YLHCmV+E4rOVqvcXBsPomhtwckDwoD+NjfLH+PBcNkgYULgyUKSOvQVgbetgwjqrw8ZXt5LK9KWZsYKJZdUirapKwmXi/ZgD8h8z6i/K/3Qc4URjPTeqPahsr/hN/TWAGtr9TWf+iIgq91H8pau7FEMxuRgqayMlCLJD+JWjgkbK9Z6/HHJp7s7oGznn3MQy4Sj9vytRN0mLb+MsRwZ3ejOTopFfCynr7EdNSANcdJQUKk2/kjHwNSz067PSW4I+nzQA3tbFcohRkdUyDwZPs7Ajc9OadhS8W6AsQTPsNrxpNxf8yoO/vMvcIgwr/0PLI2VHUEWDVaDNUqzGDwHXn8O55ehje1ECFv5e48qFAC50xXrVJjN4Rtkq8OrjTamOSrHnm2PxlJUgthjqu6fxZZ1dBoKzMBlE56mIy9PLm0HjCS08zcQUvsK+IDW4l7ECWi1oRWrhPDt1wXD4AOlOeYln1C+KSlrBfdRNIW8bgx3pAaeI9Dm0qFpWjDZAKT/uxCs0Lwx0nySUYjM3yvo="
)
)
)
).buildAndTrack()
resetNpc = platform
.newNpcBuilder()
.flag(Npc.LOOK_AT_PLAYER, true)
.flag(Npc.HIT_WHEN_PLAYER_HITS, true)
.flag(Npc.SNEAK_WHEN_PLAYER_SNEAKS, true)
.position(Position.position(-0.5, 90.00, 5.5, "hero-api:kit-editor"))
.profile(
Profile.resolved(
"RESET", UUID.randomUUID(), setOf(
ProfileProperty.property(
"textures",
"ewogICJ0aW1lc3RhbXAiIDogMTYxNjgzMjkxMTE4NSwKICAicHJvZmlsZUlkIiA6ICI1N2IzZGZiNWY4YTY0OWUyOGI1NDRlNGZmYzYzMjU2ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJYaWthcm8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGE5OWIwNWI5YTFkYjRkMjliNWU2NzNkNzdhZTU0YTc3ZWFiNjY4MTg1ODYwMzVjOGEyMDA1YWViODEwNjAyYSIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9",
"TUpkJfSfrwykKlZAOWnURNM17wX5P+S7OCJDQkeeQyLqETA96DzqK25JwMpTeJNFYcSrkDYGM6ba7nPAewhBkB3U8JaI2hdNvOHw0rQk2bxMAfg+B3Tp+VxqQb2YPQL0z8hqrJOKjzjINSkAhI2g2/rYXfNizXjiUn4f1WUejvKzsYrTcGV0TlqJeJeydJ0nVpo9ssLVu5ksr+6mOKElwvVEMgPV+0VdWC5XH3jOVSQUX1rjIW5aS+nf0A90GKu7ENxv0j0Cj03IsrL1ytx+ZguFk2vxywr49i2l5iXAOwo/qO7+3mHyzYkEyl/so2zbo9VTTGkVLJ/bmQPcbBEF0HLxl3v/m0QoGy2x/cMR2BlITtAKRQOO2zSzDLZmScYSFr0aOnGmO1qvQexn6/JLrZrDqqXFsjFTuATVwHnEXiHSb4DJ5kZds6X1Fy/4UdYLxry423sMfXZeg+49+qvfNJlsg4v+gbcPtQIBMoBKEq+wexa0PBnH7WxpJbKQhyyiQG1tzrcmhZmbA8d2eD8zmsGammI+DCQJmF+Cu3J2ftbkjON0hj09Ow4uy56RCbLkwJigbXf8v6vpBSG7QzxIxKvhwiQHeaku4CyQ6VjxzIMGowM5v5O4x7FZZcRQh0N70/GjMnTWDaVK6htQ+OixHNJ14ju10zbkpyrq484ZmRQ="
)
)
)
).buildAndTrack()
}
}
@@ -0,0 +1,110 @@
package gg.norisk.ffa.server.mechanics
import gg.norisk.ffa.server.mechanics.CombatTag.isInCombat
import gg.norisk.heroes.common.player.ffaPlayer
import net.minecraft.server.network.ServerPlayerEntity
import net.silkmc.silk.core.text.literalText
import net.silkmc.silk.game.sideboard.Sideboard
import net.silkmc.silk.game.sideboard.sideboard
import java.awt.Color
import kotlin.time.Duration.Companion.seconds
object Scoreboard {
fun getScoreboardForPlayer(player: ServerPlayerEntity): Sideboard {
val mainColor = Color(36, 173, 227).rgb
val secondaryColor = Color(150, 198, 207).rgb
return sideboard(literalText {
text("Lunaris-") {
color = mainColor
bold = true
}
text("MC.de") {
bold = true
}
text(" | ") {
color = secondaryColor
}
text("HeroFFA")
}) {
line(literalText(" ") {
strikethrough = true
color = Color.LIGHT_GRAY.rgb
})
emptyLine()
updatingLine(1.seconds) {
literalText {
text("Kills") {
color = mainColor
}
text(": ") {
color = secondaryColor
}
text(player.ffaPlayer.kills.toString())
}
}
updatingLine(1.seconds) {
literalText {
text("Deaths") {
color = mainColor
}
text(": ") {
color = secondaryColor
}
text(player.ffaPlayer.deaths.toString())
}
}
updatingLine(1.seconds) {
literalText {
text("Streak") {
color = mainColor
}
text(": ") {
color = secondaryColor
}
text(player.ffaPlayer.currentKillStreak.toString())
}
}
updatingLine(1.seconds) {
val lastAttackTime = CombatTag.ticks - (player.age - player.lastAttackTime)
if (player.isInCombat()) {
val inDamage = CombatTag.ticks - (player.age - player.damageTracker.ageOnLastDamage)
literalText {
text("Combat Tag") {
color = Color.RED.rgb
}
text(": ") {
color = secondaryColor
}
text(CombatTag.getCombatTimeAsString(Math.max(lastAttackTime, inDamage)))
}
} else {
literalText {
text("Bounty") {
color = mainColor
}
text(": ") {
color = secondaryColor
}
text(player.ffaPlayer.bounty.toString())
}
}
}
updatingLine(1.seconds) {
literalText {
text("Xp") {
color = mainColor
}
text(": ") {
color = secondaryColor
}
text(player.ffaPlayer.xp.toString())
}
}
emptyLine()
line(literalText(" ") {
strikethrough = true
color = Color.LIGHT_GRAY.rgb
})
}
}
}
@@ -0,0 +1,73 @@
package gg.norisk.ffa.server.mechanics
import gg.norisk.heroes.common.ffa.experience.ExperienceRegistry
import gg.norisk.heroes.common.ffa.experience.addXp
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
object SoupHealing {
const val SOUP_HEAL = 7F
fun onPotentialSoupUse(
player: PlayerEntity, item: Item,
cir: CallbackInfoReturnable<TypedActionResult<ItemStack>>,
world: World,
hand: Hand
) {
val foodData = player.hungerManager
if (!item.isStew || player.health >= player.maxHealth && !foodData.isNotFull) return
var consumedSoup = false
if (player.health < player.maxHealth) {
player.heal(item.soupHealing)
if (item == Items.SUSPICIOUS_STEW) {
//player.addEffect(MobEffectInstance(MobEffects.BLINDNESS, 60, 1))
//player.addEffect(MobEffectInstance(MobEffects.WEAKNESS, 60, 1))
}
consumedSoup = true
} else if (foodData.isNotFull) {
foodData.foodLevel += item.restoredFood
consumedSoup = true
}
if (consumedSoup) {
(player as? ServerPlayerEntity?)?.apply {
this.addXp(ExperienceRegistry.SOUP_EATEN)
}
cir.returnValue = TypedActionResult.pass(ItemStack(Items.BOWL))
}
}
private val Item.isStew: Boolean
get() = when (this) {
Items.MUSHROOM_STEW -> true
Items.BEETROOT_SOUP -> true
Items.RABBIT_STEW -> true
Items.SUSPICIOUS_STEW -> true
else -> false
}
val Item.soupHealing: Float
get() = when (this) {
Items.MUSHROOM_STEW -> 7.0f
Items.BEETROOT_SOUP -> 3.0f // mushroom cow nerf
Items.RABBIT_STEW -> 8.0f // used in perfect kit
Items.SUSPICIOUS_STEW -> 2.0f // spit
else -> 0f
}
private val Item.restoredFood: Int
get() = 7 // this.foodProperties?.nutrition ?: 0
private val Item.restoredSaturation: Float
get() = 3f //this.foodProperties?.saturationModifier ?: 0f
}
@@ -0,0 +1,80 @@
package gg.norisk.ffa.server.mechanics
import gg.norisk.ffa.server.FFAServer.isFFA
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.network.packet.s2c.play.PlayerSpawnPositionS2CPacket
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import net.silkmc.silk.core.item.itemStack
import net.silkmc.silk.core.item.setCustomName
import net.silkmc.silk.core.server.players
import net.silkmc.silk.core.text.sendText
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
import kotlin.math.sqrt
object Tracker {
val tracker
get() = itemStack(Items.COMPASS) {
setCustomName {
text("Tracker") {
bold = true
italic = false
}
}
}
fun onTrackerUse(
playerEntity: PlayerEntity,
stack: ItemStack,
cir: CallbackInfoReturnable<TypedActionResult<ItemStack>>,
world: World,
hand: Hand
) {
val player = playerEntity as? ServerPlayerEntity ?: return
if (ItemStack.areItemsAndComponentsEqual(tracker, stack)) {
val nearestPlayer = player.nearestPlayerInfo()?.first
if (nearestPlayer != null) {
val distance = player.nearestPlayerInfo()?.second?.toInt()
player.sendText {
text(nearestPlayer.name.string)
text(" ist ")
text(distance.toString())
text(" Blöcke entfernt")
}
player.networkHandler.sendPacket(
PlayerSpawnPositionS2CPacket(
BlockPos(
nearestPlayer.x.toInt(),
nearestPlayer.y.toInt(),
nearestPlayer.z.toInt()
), 0.0F
)
)
} else {
player.sendText("Es konnte kein Spieler gefunden werden") {
color = 0xFF4B4B
}
}
}
}
private fun ServerPlayerEntity.nearestPlayerInfo(): Pair<ServerPlayerEntity, Double>? {
val playerDistances: MutableMap<ServerPlayerEntity, Double> = mutableMapOf()
for (player in server.players) {
if (!player.isFFA) continue
if (world != player.world) continue
val distance = sqrt(this.squaredDistanceTo(Vec3d(player.x, this.y, player.z)))
if (distance > 10) {
playerDistances[player] = distance
}
}
return playerDistances.minByOrNull { it.value }?.toPair()
}
}
@@ -0,0 +1,497 @@
package gg.norisk.ffa.server.mechanics.lootdrop
import gg.norisk.ffa.server.mechanics.KitEditor
import gg.norisk.ffa.server.mechanics.lootdrop.loottable.ExperienceLootdropItem
import gg.norisk.ffa.server.mechanics.lootdrop.loottable.ItemStackLootdropItem
import gg.norisk.ffa.server.mechanics.lootdrop.loottable.SoupLootdropLoottable
import gg.norisk.ffa.server.mechanics.lootdrop.loottable.UHCLootdropLoottable
import gg.norisk.heroes.common.ffa.experience.ExperienceReason
import gg.norisk.heroes.common.ffa.experience.addXp
import kotlinx.coroutines.*
import net.minecraft.block.BarrelBlock
import net.minecraft.block.Blocks
import net.minecraft.block.entity.BarrelBlockEntity
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.FallingBlockEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.decoration.DisplayEntity
import net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity
import net.minecraft.entity.effect.StatusEffectInstance
import net.minecraft.entity.effect.StatusEffects
import net.minecraft.entity.passive.ChickenEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.projectile.FireworkRocketEntity
import net.minecraft.entity.projectile.PersistentProjectileEntity
import net.minecraft.particle.ParticleTypes
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
import net.minecraft.text.Text
import net.minecraft.util.math.AffineTransformation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
import net.silkmc.silk.core.Silk
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.literal
import net.silkmc.silk.core.text.literalText
import org.joml.Vector3f
import java.awt.Color
import kotlin.math.cos
import kotlin.math.sin
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class Lootdrop(private val world: ServerWorld, private val blockPos: BlockPos) {
companion object {
private val mainColor = Color(36, 173, 227).rgb
private val secondaryColor = Color(150, 198, 207).rgb
private val EXPIRATION_TIME = 3.minutes
private val ITEMS_PER_AIR_DROP = 4..8
private val BARREL_SLOTS = 0..26
private val entityIdLootdropMap = HashMap<Int, Lootdrop>()
private val posLootdropMap = HashMap<BlockPos, Lootdrop>()
private val lootTable = if (KitEditor.isUHC()) UHCLootdropLoottable().init() else SoupLootdropLoottable().init()
fun fallingBlockLanded(fallingBlock: FallingBlockEntity) {
val lootdrop = entityIdLootdropMap[fallingBlock.id] ?: return
lootdrop.onFallingBlockLanding(fallingBlock)
}
fun barrelOpened(barrelBlockEntity: BarrelBlockEntity, player: PlayerEntity) {
val lootdrop = posLootdropMap[barrelBlockEntity.pos] ?: return
lootdrop.onBarrelOpen(player)
}
fun projectileHit(projectile: PersistentProjectileEntity, entity: Entity) {
val lootdrop = entityIdLootdropMap[entity.id] ?: return
lootdrop.onProjectileHit(projectile, entity)
}
}
private val lootdropCoroutine = CoroutineScope(Dispatchers.IO) + SupervisorJob()
private var state: LootdropState = LootdropState.SPAWNING
private val allEntities = mutableListOf<Entity>()
private var barrelEntity = createBarrelEntity()
private var balloonEntity = createBalloon()
private var leashEntities = createLeashChickens()
private var timerTextEntity = createTimerTextEntity()
private var xpReward = 0
private var landingTime: Long? = null
private lateinit var landingPos: BlockPos
fun drop() {
startFallingAnimation()
}
private fun end() {
mcCoroutineTask(sync = true, client = false) {
allEntities.toList().forEach(::unregisterEntity)
posLootdropMap.remove(landingPos)
}
}
private fun startFallingAnimation() {
allEntities.onEach { entity ->
entity.setNoGravity(false)
world.spawnEntity(entity)
}
leashEntities.first().attachLeash(leashEntities.last(), true)
state = LootdropState.GLIDING
lootdropCoroutine.launch {
while (state == LootdropState.GLIDING) {
val time = System.currentTimeMillis() / 500.0
val swayX = sin(time) * 0.05
val swayZ = cos(time) * 0.05
allEntities.forEach {
val a = if (it is ChickenEntity) -0.12 else -0.08
it.velocity = Vec3d(swayX, a, swayZ)
it.velocityDirty = true
}
val particleLocation = barrelEntity.blockPos.toCenterPos()
world.spawnParticles(
ParticleTypes.CLOUD,
particleLocation.x,
particleLocation.y,
particleLocation.z,
1,
0.1,
0.1,
0.1,
0.05
)
delay(50.milliseconds)
}
}
lootdropCoroutine.launch {
while (state == LootdropState.GLIDING || state == LootdropState.FREE_FALL) {
barrelEntity.playSound(SoundEvents.ENTITY_PHANTOM_FLAP, 1.3f, 1.4f)
if (System.currentTimeMillis().milliseconds.inWholeSeconds % 3 == 0L) {
world.server.executeSync {
val firework = FireworkRocketEntity(EntityType.FIREWORK_ROCKET, world)
firework.setPosition(barrelEntity.pos)
world.spawnEntity(firework)
}
}
delay(1.seconds)
}
}
}
fun onFallingBlockLanding(fallingBlock: FallingBlockEntity) {
when (fallingBlock.id) {
barrelEntity.id -> onBarrelLanding()
balloonEntity.id -> onBalloonLanding()
}
}
private fun onBarrelLanding() {
val hardFall = state == LootdropState.FREE_FALL
state = LootdropState.LANDED
landingPos = barrelEntity.blockPos
landingTime = System.currentTimeMillis()
posLootdropMap[landingPos] = this
playLandingSoundsAndParticles(hardFall)
setBarrelAndContents()
startExpirationTimer()
}
private fun onBalloonLanding() {
leashEntities.forEach(::unregisterEntity)
if (!balloonEntity.isRemoved) {
balloonEntity.discard()
}
}
fun onProjectileHit(projectile: PersistentProjectileEntity, entity: Entity) {
if (entity.id == balloonEntity.id) {
state = LootdropState.FREE_FALL
projectile.onLanding()
onBalloonLanding()
}
}
fun onBarrelOpen(player: PlayerEntity) {
state = LootdropState.OPENED
if (xpReward > 0) {
player.sendMessage(Text.translatable("ffa.mechanic.lootdrop.found_xp", xpReward))
player.addXp(ExperienceReason("lootdrop_secured", xpReward))
}
end()
}
private fun onExpired() {
state = LootdropState.EXPIRED
world.setBlockState(landingPos, Blocks.AIR.defaultState)
end()
}
private fun playLandingSoundsAndParticles(hardFall: Boolean) {
val landingPos = landingPos
world.spawnParticles(
ParticleTypes.CAMPFIRE_COSY_SMOKE,
landingPos.x + 0.5,
landingPos.y + 1.5,
landingPos.z + 0.5,
20,
0.5,
1.0,
0.5,
0.02
)
if (hardFall) {
world.playSound(
null,
landingPos,
SoundEvents.ENTITY_GENERIC_EXPLODE.value(),
SoundCategory.BLOCKS,
1.0f,
1.0f
)
world.spawnParticles(
ParticleTypes.EXPLOSION,
landingPos.x + 0.5,
landingPos.y + 1.0,
landingPos.z + 0.5,
10,
0.5,
0.5,
0.5,
0.1
)
} else {
world.playSound(
null,
landingPos,
SoundEvents.BLOCK_SNOW_FALL,
SoundCategory.BLOCKS,
0.9f,
1.2f
)
}
lootdropCoroutine.launch {
while (state == LootdropState.LANDED) {
val particleLocation = landingPos.toCenterPos()
world.spawnParticles(
ParticleTypes.ELECTRIC_SPARK,
particleLocation.x,
particleLocation.y,
particleLocation.z,
10,
0.25,
0.25,
0.25,
0.005
)
delay(50.milliseconds)
}
}
lootdropCoroutine.launch {
while (state == LootdropState.LANDED) {
val pitch = (3..12).random() / 10f
world.playSound(
null,
landingPos,
SoundEvents.BLOCK_AMETHYST_BLOCK_CHIME,
SoundCategory.BLOCKS,
2.5f,
pitch
)
delay((1500..3000).random().milliseconds)
}
}
}
private fun setBarrelAndContents() {
world.setBlockState(landingPos, Blocks.BARREL.defaultState.with(BarrelBlock.FACING, Direction.UP))
val barrel = world.getBlockEntity(landingPos) as? BarrelBlockEntity
val loot = lootTable.generateLoot(ITEMS_PER_AIR_DROP.random())
loot.forEach { item ->
val amount = item.amountRange.random()
when (item) {
is ItemStackLootdropItem -> {
barrel?.setStack(BARREL_SLOTS.random(), item.itemStack.copyWithCount(amount))
}
is ExperienceLootdropItem -> xpReward += amount
}
}
startShimmerEffect(world, landingPos)
}
private fun startShimmerEffect(world: ServerWorld, blockPos: BlockPos) {
val entities = mutableListOf<TextDisplayEntity>()
fun addSide(facing: Direction) {
val pos = Vec3d(blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble())
val displayPos = when (facing) {
Direction.NORTH -> pos.add(0.6, 0.0, -0.0001)
Direction.SOUTH -> pos.add(0.4, 0.0, 1.0001)
Direction.WEST -> pos.add(-0.0001, 0.0, 0.4)
Direction.EAST -> pos.add(1.0001, 0.0, 0.6)
Direction.UP -> pos.add(0.4, 1.0001, 1.0)
Direction.DOWN -> pos.add(0.4, -0.0001, 0.0)
}
val yawRotation = when (facing) {
Direction.NORTH -> 180f
Direction.SOUTH -> 0f
Direction.WEST -> 90f
Direction.EAST -> -90f
else -> 0f
}
val pitchRotation = when (facing) {
Direction.UP -> -90f
Direction.DOWN -> 90f
else -> 0f
}
val textDisplay = TextDisplayEntity(EntityType.TEXT_DISPLAY, Silk.serverOrThrow.overworld).apply {
setPosition(displayPos.x, displayPos.y, displayPos.z)
yaw = yawRotation
pitch = pitchRotation
setText(" ".literal)
setBackground(mainColor)
val scale = Vector3f(8f, 3.625f, 8f)
setTransformation(AffineTransformation(null, null, scale, null))
}
world.spawnEntity(textDisplay)
entities.add(textDisplay)
registerEntity(textDisplay)
}
listOf(Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN).forEach {
addSide(it)
}
CoroutineScope(Dispatchers.IO).launch {
shimmerEffect(entities)
}
}
private suspend fun shimmerEffect(entities: List<TextDisplayEntity>) {
var alpha = 0.0f
var increasing = true
val delayTime = 200L
val maxAlpha = 0.66f
val stepSize = 0.01f
while (true) {
entities.forEach { entity ->
alpha = if (increasing) {
alpha + stepSize
} else {
alpha - stepSize
}
if (alpha >= maxAlpha) {
alpha = maxAlpha
increasing = false
} else if (alpha <= 0.0f) {
alpha = 0.0f
increasing = true
delay(delayTime)
}
val alphaInt = (alpha * 255).toInt()
val rgbaColor = Color(36, 173, 227, alphaInt)
entity.setBackground(rgbaColor.rgb)
}
delay(40)
}
}
private fun createBarrelEntity(): FallingBlockEntity {
val pos = blockPos.toCenterPos()
return FallingBlockEntity(
world,
pos.x + 0.5,
pos.y + 50.0,
pos.z + 0.5,
Blocks.BARREL.defaultState.with(BarrelBlock.FACING, Direction.UP)
).apply {
setNoGravity(true)
setDestroyedOnLanding()
registerEntity(this)
isGlowing = true
}
}
private fun createBalloon(): FallingBlockEntity {
val pos = barrelEntity.pos
return FallingBlockEntity(
world,
pos.x,
pos.y + 3.0,
pos.z,
Blocks.LIGHT_BLUE_CONCRETE.defaultState
).apply {
setNoGravity(true)
setDestroyedOnLanding()
registerEntity(this)
}
}
private fun createLeashChickens(): List<ChickenEntity> {
val barrelChicken = ChickenEntity(EntityType.CHICKEN, world).apply {
setPosition(barrelEntity.x, barrelEntity.y - 0.75, barrelEntity.z)
}
val balloonChicken = ChickenEntity(EntityType.CHICKEN, world).apply {
setPosition(balloonEntity.x, balloonEntity.y, balloonEntity.z)
}
return listOf(barrelChicken, balloonChicken).onEach {
it.apply {
setNoGravity(true)
isInvisible = true
isSilent = true
isInvulnerable = true
addStatusEffect(StatusEffectInstance(StatusEffects.INVISIBILITY, Int.MAX_VALUE, 0, true, false))
addStatusEffect(StatusEffectInstance(StatusEffects.SLOWNESS, Int.MAX_VALUE, 200, true, false))
attributes.getCustomInstance(EntityAttributes.GENERIC_SCALE)?.baseValue = 0.2
goalSelector.goals.clear()
registerEntity(this)
}
}
}
private fun createTimerTextEntity(): TextDisplayEntity {
return TextDisplayEntity(EntityType.TEXT_DISPLAY, world).apply {
setBillboardMode(DisplayEntity.BillboardMode.CENTER)
registerEntity(this)
}
}
private fun startExpirationTimer() {
val pos = landingPos.toCenterPos().add(0.0, 1.0, 0.0)
timerTextEntity.requestTeleport(pos.x, pos.y, pos.z)
world.spawnEntity(timerTextEntity)
lootdropCoroutine.launch {
while (state == LootdropState.LANDED) {
updateTimerText()
delay(200.milliseconds)
}
}
}
private fun updateTimerText() {
timerTextEntity.setText(literalText {
text("Lootdrop") { color = mainColor; bold = true }
newLine()
val timeRemaining = (landingTime!! + EXPIRATION_TIME.inWholeMilliseconds - System.currentTimeMillis())
if (timeRemaining <= 0 || world.getBlockState(landingPos).block != Blocks.BARREL) {
onExpired()
return
}
timeRemaining.milliseconds.toComponents { min, sec, _ ->
text {
text(min.toString().padStart(2, '0')) { color = secondaryColor }
text(":")
text(sec.toString().padStart(2, '0')) { color = secondaryColor }
}
}
})
}
private fun registerEntity(entity: Entity) {
allEntities.add(entity)
entityIdLootdropMap[entity.id] = this
}
private fun unregisterEntity(entity: Entity) {
allEntities.remove(entity)
entity.discard()
entityIdLootdropMap.remove(entity.id)
}
}
enum class LootdropState {
SPAWNING, GLIDING, FREE_FALL, LANDED, OPENED, EXPIRED
}
@@ -0,0 +1,78 @@
package gg.norisk.ffa.server.mechanics.lootdrop
import kotlinx.coroutines.*
import net.minecraft.util.math.BlockPos
import net.minecraft.world.Heightmap
import net.silkmc.silk.core.Silk
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.broadcastText
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
object LootdropManager {
private val MAP_DURATION = 30.minutes
private val MAX_LOOTDROP_DELAY = 6.minutes
private val LOOTDROP_COUNT = 6
private val world by lazy { Silk.serverOrThrow.overworld }
private var currentJob: Job? = null
private var lootdropTimes: List<Long> = listOf()
private var lootdropIndex: Int = 0
fun onArenaReset() {
currentJob?.cancel()
lootdropTimes = generateLootdropTimes().map { it + System.currentTimeMillis() }
lootdropIndex = 0
currentJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
if (lootdropIndex >= lootdropTimes.size) return@launch
if (System.currentTimeMillis() >= lootdropTimes[lootdropIndex]) {
lootdropIndex++
mcCoroutineTask(sync = true, client = false) {
spawnLootdrop()
}
}
delay(1.seconds)
}
}
}
private fun spawnLootdrop() {
val pos = findRandomLocation()
Lootdrop(world, pos).drop()
Silk.serverOrThrow.broadcastText("Dropping Lootdrop at ${pos.toShortString()}")
}
private fun findRandomLocation(): BlockPos {
val worldBorder = world.worldBorder
val centerX = worldBorder.centerX.toInt()
val centerZ = worldBorder.centerZ.toInt()
val radius = (worldBorder.size / 2).toInt()
val x = centerX + Random.nextInt(-radius, radius)
val z = centerZ + Random.nextInt(-radius, radius)
return world.getTopPosition(Heightmap.Type.WORLD_SURFACE, BlockPos(x, 0, z))
}
private fun generateLootdropTimes(): List<Long> {
val lootdropTimes = mutableListOf<Long>()
val mapDurationMillis = MAP_DURATION.inWholeMilliseconds
var nextSpawnTime = Random.nextLong(0, MAX_LOOTDROP_DELAY.inWholeMilliseconds)
for (i in 0 until LOOTDROP_COUNT) {
if (nextSpawnTime >= mapDurationMillis) break // damit keine lootdrops nach reset spawnen (aber ig sollte eh nicht?)
lootdropTimes.add(nextSpawnTime)
val spawnDelay = Random.nextLong(0, MAX_LOOTDROP_DELAY.inWholeMilliseconds)
nextSpawnTime += spawnDelay
}
return lootdropTimes
}
}
@@ -0,0 +1,66 @@
package gg.norisk.ffa.server.mechanics.lootdrop.loottable
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import kotlin.random.Random
abstract class LootdropLoottable {
private val lootTable = mutableListOf<LootdropItem>()
private var totalWeight = 0.0
abstract fun init(): LootdropLoottable
fun item(itemStack: ItemStack, weight: Double, amountRange: IntRange = 1..1) {
register(ItemStackLootdropItem(itemStack, weight, amountRange))
}
fun item(item: Item, weight: Double, amountRange: IntRange = 1..1) {
register(ItemStackLootdropItem(item.defaultStack, weight, amountRange))
}
fun exp(weight: Double, amountRange: IntRange) {
register(ExperienceLootdropItem(weight, amountRange))
}
private fun register(lootdropItem: LootdropItem) {
if (lootdropItem.weight < 0) {
throw IllegalArgumentException("weight of LootDropItem must be greater than zero")
}
lootTable.add(lootdropItem)
totalWeight += lootdropItem.weight
}
fun generateLoot(count: Int): List<LootdropItem> {
return Array(count) { getRandom() }.mapNotNull { it }
}
private fun getRandom(): LootdropItem? {
if (lootTable.isEmpty()) return null
var randomValue = Random.nextDouble(0.0, totalWeight)
for (item in lootTable) {
randomValue -= item.weight
if (randomValue <= 0) {
return item
}
}
return null
}
}
sealed class LootdropItem {
abstract val weight: Double
abstract val amountRange: IntRange
}
class ItemStackLootdropItem(
val itemStack: ItemStack,
override val weight: Double,
override val amountRange: IntRange,
) : LootdropItem()
class ExperienceLootdropItem(
override val weight: Double,
override val amountRange: IntRange,
) : LootdropItem()
@@ -0,0 +1,37 @@
package gg.norisk.ffa.server.mechanics.lootdrop.loottable
import net.minecraft.item.Items
import net.minecraft.potion.Potions
import net.silkmc.silk.core.item.itemStack
import net.silkmc.silk.core.item.setPotion
class SoupLootdropLoottable: LootdropLoottable() {
override fun init(): SoupLootdropLoottable {
item(Items.DIAMOND_HELMET, 1.0)
item(Items.DIAMOND_CHESTPLATE, 1.0)
item(Items.DIAMOND_LEGGINGS, 1.0)
item(Items.DIAMOND_BOOTS, 1.0)
item(Items.DIAMOND_SWORD, 1.1)
item(Items.IRON_HELMET, 1.75)
item(Items.IRON_CHESTPLATE, 1.75)
item(Items.IRON_LEGGINGS, 1.75)
item(Items.IRON_BOOTS, 1.75)
item(Items.IRON_SWORD, 2.0)
item(Items.COBWEB, 2.0, 1..6)
item(Items.ENDER_PEARL, 1.5, 1..6)
item(Items.WATER_BUCKET, 1.25)
item(Items.LAVA_BUCKET, 1.25)
item(Items.MUSHROOM_STEW, 2.0, 1..4)
item(Items.RED_MUSHROOM, 2.0, 6..14)
item(Items.BROWN_MUSHROOM, 2.0, 6..14)
item(Items.EXPERIENCE_BOTTLE, 2.0, 1..3)
item(Items.LAPIS_LAZULI, 2.0, 1..12)
item(Items.ANVIL, 0.6)
item(Items.ENCHANTING_TABLE, 0.8)
item(itemStack(Items.SPLASH_POTION) {
setPotion(Potions.SWIFTNESS)
}, 0.8)
exp(1.0, 50..100)
return this
}
}
@@ -0,0 +1,40 @@
package gg.norisk.ffa.server.mechanics.lootdrop.loottable
import gg.norisk.ffa.server.utils.EnchantmentUtils.getEntry
import net.minecraft.enchantment.Enchantments
import net.minecraft.item.Items
import net.silkmc.silk.core.Silk
import net.silkmc.silk.core.item.itemStack
class UHCLootdropLoottable : LootdropLoottable() {
override fun init(): LootdropLoottable {
item(Items.NETHERITE_HELMET, 1.0)
item(Items.NETHERITE_CHESTPLATE, 1.0)
item(Items.NETHERITE_LEGGINGS, 1.0)
item(Items.NETHERITE_BOOTS, 1.0)
item(Items.NETHERITE_SWORD, 1.1)
item(Items.DIAMOND_HELMET, 0.45)
item(Items.DIAMOND_CHESTPLATE, 0.45)
item(Items.DIAMOND_LEGGINGS, 0.45)
item(Items.DIAMOND_BOOTS, 0.45)
item(Items.DIAMOND_SWORD, 0.33)
item(Items.COBWEB, 2.0, 3..8)
item(Items.ENDER_PEARL, 2.0, 1..6)
item(Items.WATER_BUCKET, 1.3)
item(Items.LAVA_BUCKET, 1.3)
item(Items.EXPERIENCE_BOTTLE, 1.5, 1..5)
item(Items.LAPIS_LAZULI, 2.0, 1..12)
item(Items.ANVIL, 0.3)
item(Items.ENCHANTING_TABLE, 0.8)
item(Items.ARROW, 1.4, 8..24)
item(Items.OAK_LOG, 1.7, 32..64)
item(Items.COBBLESTONE, 1.3, 64..128)
item(itemStack(Items.IRON_SWORD) {
addEnchantment(Enchantments.FIRE_ASPECT.getEntry(Silk.serverOrThrow.overworld), 1)
}, 0.3)
exp(1.0, 50..100)
return this
}
}
@@ -0,0 +1,187 @@
package gg.norisk.ffa.server.selector
import gg.norisk.datatracker.entity.setSyncedData
import gg.norisk.ffa.server.FFAServer.isFFA
import gg.norisk.ffa.server.mechanics.KitEditor
import gg.norisk.ffa.server.mechanics.Scoreboard
import gg.norisk.ffa.server.mechanics.Tracker
import gg.norisk.ffa.server.mixin.accessor.LivingEntityAccessor
import gg.norisk.ffa.server.world.WorldManager.findSpawnLocation
import gg.norisk.ffa.server.world.WorldManager.getCenter
import gg.norisk.heroes.common.HeroesManager.logger
import gg.norisk.heroes.common.events.HeroEvents
import gg.norisk.heroes.common.hero.HeroManager
import gg.norisk.heroes.common.hero.setHero
import gg.norisk.heroes.common.networking.Networking
import gg.norisk.heroes.common.networking.dto.HeroSelectorPacket
import gg.norisk.heroes.common.player.InventorySorting.Companion.loadInventory
import gg.norisk.heroes.common.player.ffaPlayer
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.enchantment.Enchantment
import net.minecraft.enchantment.Enchantments
import net.minecraft.entity.EquipmentSlot
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.world.GameMode
import net.minecraft.world.World
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.item.itemStack
import net.silkmc.silk.game.sideboard.Sideboard
import java.util.*
object SelectorServerManager {
private val scoreboards = mutableMapOf<UUID, Sideboard>()
fun initServer() {
HeroEvents.heroSelectEvent.listen { event ->
event.canSelect = true
val player = event.player as? ServerPlayerEntity ?: return@listen
val server = player.server
player.changeGameMode(GameMode.SURVIVAL)
player.isFFA = true
val spawn = server.overworld.findSpawnLocation().toCenterPos()
player.teleport(server.overworld, spawn.x, spawn.y, spawn.z, 0f, 0f)
player.setArenaReady()
}
ServerPlayConnectionEvents.JOIN.register(ServerPlayConnectionEvents.Join { handler, sender, server ->
handler.player.setSelectorReady()
})
ServerPlayConnectionEvents.DISCONNECT.register(ServerPlayConnectionEvents.Disconnect { handler, player ->
logger.info("REMOVING SCOREBOARD FOR ${handler.player}")
scoreboards.remove(handler.player.uuid)
})
if (FabricLoader.getInstance().isDevelopmentEnvironment) {
command("ffakit") {
literal("uhc") {
runs {
KitEditor.handleUHCKit(this.source.playerOrThrow)
}
}
literal("soup") {
runs {
KitEditor.handleSoupKit(this.source.playerOrThrow)
}
}
}
}
}
private fun ServerPlayerEntity.setArenaReady() {
val inventory = this.ffaPlayer.inventorySorting
if (inventory != null) {
this.loadInventory(inventory)
} else {
KitEditor.handleKit(this)
}
if (!KitEditor.isUHC()) {
setSyncedData("duels:OLD_PVP", true)
getAttributeInstance(EntityAttributes.GENERIC_ATTACK_SPEED)?.baseValue = 100.0
}
hungerManager.foodLevel = 20
hungerManager.saturationLevel = 5f
clearStatusEffects()
closeHandledScreen()
setExperienceLevel(0)
setExperiencePoints(0)
if (!FabricLoader.getInstance().isDevelopmentEnvironment) {
scoreboards.computeIfAbsent(this.uuid) { Scoreboard.getScoreboardForPlayer(this) }.displayToPlayer(this)
}
(this as LivingEntityAccessor).lastAttackTime = -10000
}
fun PlayerEntity.setSoupItems() {
inventory.setStack(0, Items.STONE_SWORD.defaultStack)
repeat(36) {
giveItemStack(Items.MUSHROOM_STEW.defaultStack)
}
inventory.setStack(8, Tracker.tracker)
inventory.setStack(13, ItemStack(Items.BOWL, 8))
inventory.setStack(14, ItemStack(Items.RED_MUSHROOM, 8))
inventory.setStack(15, ItemStack(Items.BROWN_MUSHROOM, 8))
}
fun PlayerEntity.setUHCItems() {
equipStack(EquipmentSlot.HEAD, itemStack(Items.DIAMOND_HELMET) {
//addEnchantment(Enchantments.PROTECTION.getEntry(world), 1)
})
equipStack(EquipmentSlot.CHEST, itemStack(Items.DIAMOND_CHESTPLATE) {
//addEnchantment(Enchantments.PROTECTION.getEntry(world), 1)
})
equipStack(EquipmentSlot.LEGS, itemStack(Items.DIAMOND_LEGGINGS) {
//addEnchantment(Enchantments.PROTECTION.getEntry(world), 1)
})
equipStack(EquipmentSlot.FEET, itemStack(Items.DIAMOND_BOOTS) {
//addEnchantment(Enchantments.PROTECTION.getEntry(world), 1)
})
equipStack(EquipmentSlot.OFFHAND, itemStack(Items.SHIELD) {
//addEnchantment(Enchantments.PROTECTION.getEntry(world), 1)
})
inventory.setStack(0, itemStack(Items.DIAMOND_SWORD) {
addEnchantment(Enchantments.SHARPNESS.getEntry(world), 1)
})
inventory.setStack(1, itemStack(Items.DIAMOND_AXE) {
addEnchantment(Enchantments.UNBREAKING.getEntry(world), 3)
})
inventory.setStack(2, itemStack(Items.GOLDEN_APPLE, 6) {
})
inventory.setStack(3, itemStack(Items.WATER_BUCKET) {
})
inventory.setStack(29, itemStack(Items.COOKED_BEEF, 16) {
})
inventory.setStack(30, itemStack(Items.WATER_BUCKET) {
})
inventory.setStack(4, itemStack(Items.LAVA_BUCKET) {
})
inventory.setStack(31, itemStack(Items.LAVA_BUCKET) {
})
inventory.setStack(5, itemStack(Items.COBBLESTONE, 64) {
})
inventory.setStack(32, itemStack(Items.OAK_PLANKS, 64) {
})
inventory.setStack(6, itemStack(Items.COBWEB, 8) {
})
inventory.setStack(7, itemStack(Items.BOW) {
addEnchantment(Enchantments.UNBREAKING.getEntry(world), 3)
addEnchantment(Enchantments.POWER.getEntry(world), 1)
})
inventory.setStack(8, itemStack(Items.CROSSBOW) {
addEnchantment(Enchantments.UNBREAKING.getEntry(world), 3)
addEnchantment(Enchantments.PIERCING.getEntry(world), 1)
})
inventory.setStack(17, Tracker.tracker)
inventory.setStack(14, itemStack(Items.DIAMOND_PICKAXE) {
addEnchantment(Enchantments.UNBREAKING.getEntry(world), 3)
addEnchantment(Enchantments.EFFICIENCY.getEntry(world), 1)
})
inventory.setStack(9, itemStack(Items.ARROW, 16) {
})
}
private fun RegistryKey<Enchantment>.getEntry(world: World): RegistryEntry<Enchantment> {
return world.registryManager.get(RegistryKeys.ENCHANTMENT).getEntry(this.value).get()
}
fun ServerPlayerEntity.setSelectorReady() {
this.health = this.maxHealth
isFFA = false
changeGameMode(GameMode.SPECTATOR)
closeHandledScreen()
clearStatusEffects()
setHero(null)
val packet = HeroSelectorPacket(HeroManager.registeredHeroes.keys.toList(), true, true)
Networking.s2cHeroSelectorPacket.send(
packet,
this,
)
val spawn = server.overworld.getCenter().toCenterPos()
this.teleport(server.overworld, spawn.x, spawn.y, spawn.z, 0f, 0f)
scoreboards[uuid]?.hideFromPlayer(this)
}
}
@@ -0,0 +1,14 @@
package gg.norisk.ffa.server.utils
import eu.cloudnetservice.driver.inject.InjectionLayer
import eu.cloudnetservice.driver.provider.CloudServiceProvider
import eu.cloudnetservice.wrapper.configuration.WrapperConfiguration
object CloudNetManager {
private val serviceProvider: CloudServiceProvider = InjectionLayer.ext().instance(CloudServiceProvider::class.java)
private val wrapperConfig: WrapperConfiguration = InjectionLayer.ext().instance(WrapperConfiguration::class.java)
fun stopCloudNetService() {
wrapperConfig.serviceInfoSnapshot().provider().stop()
}
}
@@ -0,0 +1,13 @@
package gg.norisk.ffa.server.utils
import net.minecraft.enchantment.Enchantment
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.world.World
object EnchantmentUtils {
fun RegistryKey<Enchantment>.getEntry(world: World): RegistryEntry<Enchantment> {
return world.registryManager.get(RegistryKeys.ENCHANTMENT).getEntry(this.value).get()
}
}
@@ -0,0 +1,73 @@
package gg.norisk.ffa.server.world
import com.sk89q.worldedit.WorldEdit
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats
import com.sk89q.worldedit.fabric.FabricAdapter
import com.sk89q.worldedit.function.operation.Operation
import com.sk89q.worldedit.function.operation.Operations
import com.sk89q.worldedit.math.BlockVector3
import com.sk89q.worldedit.session.ClipboardHolder
import gg.norisk.ffa.server.FFAServer.logger
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.world.World
import net.silkmc.silk.commands.command
import java.io.File
import kotlin.math.pow
object MapPlacer {
val chunkSize = 3
val mapSize = 512
fun init() {
if (FabricLoader.getInstance().isDevelopmentEnvironment) {
command("placeffamap") {
runs {
generateMap(this.source.world)
}
}
}
}
fun generateMap(world: World) {
val size = chunkSize * mapSize
val file = File(FabricLoader.getInstance().configDir.parent.parent.parent.toFile(), "assets/ffa-13-07-2024.schem")
if (!file.exists()) {
logger.error("${file.name} doesn't exist")
return
}
var counter = 0
for (chunkX in -size..size step mapSize) {
for (chunkZ in -size..size step mapSize) {
val posX = chunkX + size
val posZ = chunkZ + size
counter++
logger.info(
"Placing [$counter/${
(-chunkSize..chunkSize).count().toDouble().pow(2.0).toInt()
}] at ${posX} ${posZ}"
)
placeSchematic(
world,
file,
posX,
posZ,
80
)
}
}
}
private fun placeSchematic(world: World, file: File, offsetX: Int, offsetZ: Int, y: Int) {
val clipboard = ClipboardFormats.findByFile(file)?.getReader(file.inputStream())?.read()
WorldEdit.getInstance().editSessionFactory.getEditSession(FabricAdapter.adapt(world), -1)
.use { editSession ->
val operation: Operation = ClipboardHolder(clipboard)
.createPaste(editSession)
.to(BlockVector3.at(offsetX, y, offsetZ))
.ignoreAirBlocks(true)
.build()
Operations.complete(operation)
logger.info("Placed at $offsetX $offsetZ")
}
}
}
@@ -0,0 +1,271 @@
package gg.norisk.ffa.server.world
import gg.norisk.ffa.server.FFAServer.isFFA
import gg.norisk.ffa.server.FFAServer.logger
import gg.norisk.ffa.server.mechanics.lootdrop.LootdropManager
import gg.norisk.ffa.server.utils.CloudNetManager
import gg.norisk.ffa.server.world.MapPlacer.chunkSize
import gg.norisk.ffa.server.world.MapPlacer.mapSize
import gg.norisk.heroes.common.HeroesManager
import kotlinx.coroutines.Job
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.network.packet.s2c.play.PositionFlag
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.network.SpawnLocating
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.world.GameRules
import net.minecraft.world.World
import net.silkmc.silk.commands.PermissionLevel
import net.silkmc.silk.commands.command
import net.silkmc.silk.core.event.Events
import net.silkmc.silk.core.event.Server
import net.silkmc.silk.core.kotlin.ticks
import net.silkmc.silk.core.task.infiniteMcCoroutineTask
import net.silkmc.silk.core.task.mcCoroutineTask
import net.silkmc.silk.core.text.broadcastText
import net.silkmc.silk.core.text.literal
import net.silkmc.silk.core.text.literalText
import java.time.Duration
import java.time.LocalTime
import java.util.concurrent.atomic.AtomicLong
import kotlin.math.absoluteValue
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toKotlinDuration
object WorldManager {
var currentPair = Pair(0, 0)
var mapReset = 30 * 60L
var mapResetTask: Job? = null
val usedMaps = mutableSetOf<Pair<Int, Int>>()
val maxCount get() = (-chunkSize..chunkSize).count()
val counter = AtomicLong(mapReset)
fun initServer() {
Events.Server.postStart.listen { event ->
//MapPlacer.generateMap(Silk.serverOrThrow.overworld)
}
ServerWorldEvents.LOAD.register(ServerWorldEvents.Load { server, world ->
world.gameRules.get(GameRules.ANNOUNCE_ADVANCEMENTS).set(false, server)
world.gameRules.get(GameRules.DO_WEATHER_CYCLE).set(false, server)
world.gameRules.get(GameRules.DO_DAYLIGHT_CYCLE).set(false, server)
})
ServerLifecycleEvents.SERVER_STARTED.register { server ->
logger.info("Init Map Reset Cycle...")
usedMaps.clear()
if (!FabricLoader.getInstance().isDevelopmentEnvironment) {
//restartServerTimer(server)
mapResetCycle(server)
setWorldBorder(server.overworld)
LootdropManager.onArenaReset()
}
}
command("ffa") {
literal("resetmap") {
requires { it.hasPermissionLevel(PermissionLevel.OWNER.level) }
literal("settimer") {
argument<Long>("seconds") { seconds ->
suggestSingle { mapReset }
runs {
val sender = this.source
mapReset = seconds()
this.source.server.broadcastText(literalText {
text(HeroesManager.prefix)
text(sender.displayName)
text(" has set the map reset timer to ${mapReset.getTimeAsString()}!")
})
}
}
}
runs {
val sender = this.source
this.source.server.broadcastText(literalText {
text(HeroesManager.prefix)
text(sender.displayName)
text(" executed map reset!")
})
mapResetCycle(this.source.server, true)
}
}
}
}
fun restartServerTimer(server: MinecraftServer) {
val targetTime = LocalTime.of(4, 0) // Zielzeit: 3 Uhr nachts
val now = LocalTime.now()
// Berechne die Differenz zwischen jetzt und der Zielzeit
val initialDelay = if (now.isBefore(targetTime)) {
Duration.between(now, targetTime)
} else {
Duration.between(now, targetTime.plusHours(24))
}
val countdown = Duration.ofMinutes(5) // 5 Minuten Countdown
// Ziehe den Countdown vom initialen Delay ab, damit der Neustart pünktlich erfolgt
var adjustedDelay = initialDelay.minus(countdown)
if (adjustedDelay.isNegative || adjustedDelay.isZero) {
adjustedDelay = adjustedDelay.plusDays(1)
}
logger.info("Server Restart in ${adjustedDelay} ${adjustedDelay.seconds.getTimeAsString()}")
// Starte den Countdown, wenn der berechnete Delay größer als 0 ist
mcCoroutineTask(sync = true, delay = adjustedDelay.toKotlinDuration()) { delay ->
mcCoroutineTask(sync = true, period = 20.ticks, howOften = countdown.seconds) { counter ->
val left = counter.counterDownToZero
if (left.mod(60) == 0 || left <= 5 || left == 10L || left == 20L || left == 30L) {
server.broadcastText("SERVER RESTART IN ${left.getTimeAsString()}")
}
if (left == 0L) {
restartServer(server)
}
}
}
}
fun restartServer(server: MinecraftServer) {
server.broadcastText("RESTARTING SERVER")
CloudNetManager.stopCloudNetService()
}
fun mapResetCycle(server: MinecraftServer, force: Boolean = false) {
currentPair = getFreeMapPos()
if (force) {
server.overworld.players.forEach { player ->
player.teleportToNewMap(currentPair.first, currentPair.second)
}
setWorldBorder(server.overworld)
LootdropManager.onArenaReset()
}
mapResetTask?.cancel()
counter.set(mapReset)
mapResetTask = infiniteMcCoroutineTask(period = 20.ticks, sync = true, client = false) {
val players = server.playerManager.playerList
if (players.isEmpty()) {
return@infiniteMcCoroutineTask
}
//logger.info("Map Reset In: ${counter.get()}")
counter.decrementAndGet()
//if (counter.get() < 300) {
for (player in players) {
if (player.isFFA) {
Random
player.sendMessage("Map Reset ${counter.getTimeAsString()}".literal, true)
}
}
//}
if (counter.get() == 0L) {
usedMaps.add(currentPair)
mapResetCycle(server)
server.overworld.players.forEach { player ->
player.teleportToNewMap(currentPair.first, currentPair.second)
}
setWorldBorder(server.overworld)
LootdropManager.onArenaReset()
}
}
}
fun PlayerEntity.isInKitEditorWorld(): Boolean {
return this.world.registryKey.value == Identifier.of("hero-api", "kit-editor")
}
fun Number.getTimeAsString(): String {
val builder = StringBuilder()
this.toInt().seconds.toComponents { days, hours, minutes, seconds, _ ->
if (days > 0) builder.append(days).append("d ")
if (hours > 0) builder.append(hours).append("h ")
if (minutes > 0) builder.append(minutes).append("m ")
builder.append(seconds).append("s")
}
return builder.toString()
}
fun AtomicLong.getTimeAsString(): String {
return get().getTimeAsString()
}
fun setWorldBorder(world: World) {
world.worldBorder.setCenter(
(currentPair.first * mapSize).toDouble() + mapSize / 2.0,
(currentPair.second * mapSize).toDouble() + mapSize / 2.0
)
world.worldBorder.size = mapSize.toDouble()
}
fun getFreeMapPos(): Pair<Int, Int> {
//val chunkX = Random.nextInt(-512 / mapSize, 3072 / mapSize)
//val chunkZ = Random.nextInt(-512 / mapSize, 3072 / mapSize)
val chunkX = Random.nextInt(-1, (chunkSize * 2))
val chunkZ = Random.nextInt(-1, (chunkSize * 2))
//val chunkX = Random.nextInt(-mapSize, mapSize * (chunkSize * 2)) / mapSize
//val chunkZ = Random.nextInt(-mapSize, mapSize * (chunkSize * 2)) / mapSize
val pair = Pair(chunkX, chunkZ)
//TODO bypass für volle map sollte aber eig nicht passieren da große map
if (usedMaps.size >= maxCount) {
return pair
}
return if (usedMaps.contains(pair)) {
getFreeMapPos()
} else {
pair
}
}
fun ServerPlayerEntity.teleportToNewMap(x: Int, z: Int, mapSize: Int = MapPlacer.mapSize) {
val world = this.serverWorld
val relativeX = (this.blockPos.x and (mapSize - 1)) + this.x.fractional().absoluteValue
val relativeZ = (this.blockPos.z and (mapSize - 1)) + this.z.fractional().absoluteValue
//TODO bug dass es dings ist hä also z und x wird manchmal ganz seltsam
val realCoordinateX = mapSize * x + relativeX
val realCoordinateZ = mapSize * z + relativeZ
this.teleport(
world,
realCoordinateX,
this.y,
realCoordinateZ,
PositionFlag.VALUES,
this.yaw,
this.pitch
)
}
fun Double.fractional(): Double {
return this - this.toInt()
}
fun ServerWorld.getCenter(): BlockPos {
val x = (currentPair.first * mapSize).toDouble() + mapSize / 2.0
val z = (currentPair.second * mapSize).toDouble() + mapSize / 2.0
return BlockPos(x.toInt(), 64, z.toInt())
}
fun ServerWorld.findSpawnLocation(): BlockPos {
//smaller number -> bigger radius
val startMultiplier = 0.15
val endMultiplier = startMultiplier * 2
val xRange =
(currentPair.first * mapSize + (mapSize * startMultiplier).toInt()..currentPair.first * mapSize + (mapSize - mapSize * endMultiplier).toInt())
val zRange =
(currentPair.second * mapSize + (mapSize * startMultiplier).toInt()..currentPair.second * mapSize + (mapSize - mapSize * endMultiplier).toInt())
//logger.info("X-Range: $xRange | ${(mapSize * startMultiplier).toInt()} | ${(mapSize - mapSize * endMultiplier).toInt()}")
//logger.info("Z-Range: $zRange | ${(mapSize * startMultiplier).toInt()} | ${(mapSize - mapSize * endMultiplier).toInt()}")
return SpawnLocating.findOverworldSpawn(this, xRange.random(), zRange.random()) ?: this.findSpawnLocation()
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@@ -0,0 +1,29 @@
{
"schemaVersion": 1,
"name": "FFA-Server",
"id": "ffa-server",
"version": "${version}",
"description": "FFA-Server",
"authors": [
"NoRiskk",
"Freshkenny",
"BestAuto"
],
"icon": "assets/ffa-server/icon.png",
"license": "ARR",
"environment": "*",
"entrypoints": {
"main": [
{
"adapter": "kotlin",
"value": "gg.norisk.ffa.server.FFAServer"
}
]
},
"mixins": [
"ffa-server.mixins.json"
],
"accessWidener": "ffa-server.accesswidener",
"depends": {
}
}
@@ -0,0 +1,25 @@
accessWidener v1 named
accessible method net/minecraft/entity/decoration/DisplayEntity setTransformation (Lnet/minecraft/util/math/AffineTransformation;)V
accessible method net/minecraft/entity/decoration/DisplayEntity setBillboardMode (Lnet/minecraft/entity/decoration/DisplayEntity$BillboardMode;)V
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity getText ()Lnet/minecraft/text/Text;
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity setText (Lnet/minecraft/text/Text;)V
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity setBackground (I)V
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity getBackground ()I
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity getDisplayFlags ()B
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity setDisplayFlags (B)V
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity getLineWidth ()I
accessible method net/minecraft/entity/decoration/DisplayEntity$TextDisplayEntity setLineWidth (I)V
accessible method net/minecraft/entity/decoration/DisplayEntity$BlockDisplayEntity setBlockState (Lnet/minecraft/block/BlockState;)V
accessible method net/minecraft/entity/FallingBlockEntity <init> (Lnet/minecraft/world/World;DDDLnet/minecraft/block/BlockState;)V
accessible field net/minecraft/entity/mob/MobEntity goalSelector Lnet/minecraft/entity/ai/goal/GoalSelector;
accessible class net/minecraft/entity/ai/goal/GoalSelector
accessible method net/minecraft/server/network/SpawnLocating findOverworldSpawn (Lnet/minecraft/server/world/ServerWorld;II)Lnet/minecraft/util/math/BlockPos;
accessible field net/minecraft/entity/damage/DamageTracker ageOnLastDamage I
accessible field net/minecraft/entity/damage/DamageTracker recentlyAttacked Z
accessible field net/minecraft/entity/damage/DamageTracker hasDamage Z
accessible field net/minecraft/entity/damage/DamageTracker recentDamage Ljava/util/List;
accessible field net/minecraft/entity/ItemEntity itemAge I
@@ -0,0 +1,27 @@
{
"required": true,
"minVersion": "0.8",
"package": "gg.norisk.ffa.server.mixin",
"compatibilityLevel": "JAVA_21",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"BarrelBlockEntityMixin",
"BucketItemMixin",
"CommandManagerMixin",
"DamageTrackerMixin",
"FallingBlockEntityMixin",
"HungerManagerMixin",
"ItemStackMixin",
"MinecraftDedicatedServerMixin",
"MiningToolItemMixin",
"PersistentProjectileEntityMixin",
"PlayerEntityMixin",
"ServerPlayerEntityMixin",
"ServerPlayNetworkHandlerMixin",
"SwordItemMixin",
"WitherSkullBlockMixin",
"accessor.LivingEntityAccessor"
]
}