first commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
+21
@@ -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 {
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
+62
@@ -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();
|
||||
}
|
||||
}
|
||||
+22
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -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()
|
||||
+37
@@ -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
|
||||
}
|
||||
}
|
||||
+40
@@ -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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user