first commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
loom {
|
||||
accessWidenerPath.set(file("src/main/resources/hero-api.accesswidener"))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":datatracker", configuration = "namedElements"))
|
||||
|
||||
modApi(libs.bundles.fabric)
|
||||
modApi(libs.bundles.silk)
|
||||
modApi(libs.bundles.performance)
|
||||
modApi(libs.bundles.mongodb)
|
||||
modApi(libs.bundles.hglaborutils) {
|
||||
exclude(module = "fabric-api")
|
||||
exclude(module = "hglabor-utils-events")
|
||||
}
|
||||
modApi(libs.owolib)
|
||||
modApi(libs.geckolib)
|
||||
modApi(libs.emoteLib)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import gg.norisk.heroes.common.events.EntityEvents;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.data.TrackedData;
|
||||
import net.minecraft.world.World;
|
||||
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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin {
|
||||
@Shadow
|
||||
public abstract World getWorld();
|
||||
|
||||
@Unique
|
||||
private final Map<String, Object> syncedValues = new HashMap<>();
|
||||
|
||||
@Inject(method = "onTrackedDataSet", at = @At("TAIL"))
|
||||
private void injected(TrackedData<?> trackedData, CallbackInfo ci) {
|
||||
EntityEvents.INSTANCE.getOnTrackedDataSetEvent().invoke(new EntityEvents.EntityTrackedDataSetEvent((Entity) (Object) this, trackedData));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import gg.norisk.heroes.common.HeroesManager;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags;
|
||||
import net.minecraft.resource.featuretoggle.FeatureManager;
|
||||
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(FeatureFlags.class)
|
||||
public abstract class FeatureFlagsMixin {
|
||||
@Inject(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/featuretoggle/FeatureManager$Builder;build()Lnet/minecraft/resource/featuretoggle/FeatureManager;"))
|
||||
private static void heroapi$featureFlag(CallbackInfo ci, @Local FeatureManager.Builder builder) {
|
||||
HeroesManager.heroesFlag = builder.addFlag(HeroesManager.INSTANCE.toId("heroes"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import gg.norisk.heroes.common.events.EntityEvents;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(LivingEntity.class)
|
||||
public abstract class LivingEntityMixin extends Entity {
|
||||
@Shadow
|
||||
public abstract boolean damage(DamageSource source, float amount);
|
||||
|
||||
public LivingEntityMixin(EntityType<?> entityType, World world) {
|
||||
super(entityType, world);
|
||||
}
|
||||
|
||||
@Inject(method = "tickMovement", at = @At("HEAD"))
|
||||
private void tickMovementEvent(CallbackInfo ci) {
|
||||
EntityEvents.INSTANCE.getLivingEntityTickMovementEvent().invoke(new EntityEvents.LivingEntityEvent((LivingEntity) (Object) this));
|
||||
}
|
||||
|
||||
|
||||
@ModifyReturnValue(method = "computeFallDamage", at = @At("RETURN"))
|
||||
private int injected(int original, float fallDistance, float damageMultiplier) {
|
||||
var event = new EntityEvents.ComputeFallDamageEvent(fallDistance, damageMultiplier, original, (LivingEntity) (Object) this);
|
||||
EntityEvents.INSTANCE.getComputeFallDamageEvent().invoke(event);
|
||||
if (event.getFallDamage() != null) {
|
||||
return event.getFallDamage();
|
||||
} else {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public interface MinecraftServerAccessor {
|
||||
@Accessor("worlds")
|
||||
public Map<RegistryKey<World>, ServerWorld> getLevelsMap();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import gg.norisk.heroes.common.HeroesManager;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.resource.DataConfiguration;
|
||||
import net.minecraft.resource.ResourcePackManager;
|
||||
import net.minecraft.resource.ResourcePackProfile;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
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;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public abstract class MinecraftServerMixin {
|
||||
@Inject(method = "loadDataPacks(Lnet/minecraft/resource/ResourcePackManager;Lnet/minecraft/resource/DataConfiguration;ZZ)Lnet/minecraft/resource/DataConfiguration;", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
|
||||
private static void heroapi$forceDataPack(ResourcePackManager resourcePackManager, DataConfiguration dataConfiguration, boolean bl, boolean bl2, CallbackInfoReturnable<DataConfiguration> cir,
|
||||
@Local Set<String> set, @Local ResourcePackProfile profile
|
||||
) {
|
||||
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
|
||||
HeroesManager.INSTANCE.getLogger().info("Found Datapack {}", profile.getId());
|
||||
if ("heroes".equalsIgnoreCase(profile.getId())) {
|
||||
set.add(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import gg.norisk.heroes.common.hero.IHeroManagerKt;
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
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(PlayerEntity.class)
|
||||
public abstract class PlayerEntityMixin extends LivingEntity {
|
||||
protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
|
||||
super(entityType, world);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void tickInjection(CallbackInfo ci) {
|
||||
var player = (PlayerEntity) (Object) this;
|
||||
var hero = IHeroManagerKt.getHero(player);
|
||||
if (hero == null) return;
|
||||
for (AbstractAbility<?> ability : hero.getAbilities().values()) {
|
||||
ability.onTick(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import net.minecraft.resource.ResourcePackManager;
|
||||
import net.minecraft.resource.ResourcePackProvider;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(value = ResourcePackManager.class, priority = 2000)
|
||||
public abstract class ResourcePackManagerMixin {
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Set<ResourcePackProvider> providers;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void heroapi$resourcePack(ResourcePackProvider[] resources, CallbackInfo ci) {
|
||||
//this.providers.add(new HeroDataPackProvider(new SymlinkFinder(path -> true)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package gg.norisk.heroes.common.mixin;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.entity.EntityLookup;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(World.class)
|
||||
public interface WorldAccessor {
|
||||
@Invoker("getEntityLookup")
|
||||
EntityLookup<Entity> invokeGetEntityLookup();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import gg.norisk.heroes.client.events.ClientEvents;
|
||||
import gg.norisk.heroes.client.renderer.CameraShaker;
|
||||
import gg.norisk.heroes.client.ui.OrthoCamera;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.BlockView;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Camera.class)
|
||||
public abstract class CameraMixin {
|
||||
@ModifyConstant(method = "update", constant = @Constant(floatValue = 4.0f))
|
||||
private float updateInjection(float constant) {
|
||||
var event = new ClientEvents.CameraClipToSpaceEvent(constant);
|
||||
ClientEvents.INSTANCE.getCameraClipToSpaceEvent().invoke(event);
|
||||
return (float) event.getValue();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "update",
|
||||
at = @At(
|
||||
// Inject before the call to clipToSpace
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/render/Camera;setPos(DDD)V",
|
||||
shift = At.Shift.BY,
|
||||
by = 1
|
||||
)
|
||||
)
|
||||
void camerashake$onUpdate(BlockView area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickDelta, CallbackInfo ci) {
|
||||
double x = CameraShaker.INSTANCE.getAvgX();
|
||||
double y = CameraShaker.INSTANCE.getAvgY();
|
||||
((Camera) (Object)this).moveBy((float) .0, (float) y, (float) x);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "moveBy", at = @At("HEAD"), index = 1, argsOnly = true)
|
||||
private float heroapi$moveByHeadX(float value) {
|
||||
return OrthoCamera.INSTANCE.isEnabled() ? 0.0f : value;
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "moveBy", at = @At("HEAD"), index = 3, argsOnly = true)
|
||||
private float heroapi$moveByHeadZ(float value) {
|
||||
return OrthoCamera.INSTANCE.isEnabled() ? 0.0f : value;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import gg.norisk.heroes.common.events.AfterTickInputEvent;
|
||||
import gg.norisk.heroes.common.events.BasicEventsKt;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.input.Input;
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientPlayerEntity.class)
|
||||
public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
|
||||
@Shadow
|
||||
public Input input;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
protected MinecraftClient client;
|
||||
|
||||
public ClientPlayerEntityMixin(ClientWorld clientWorld, GameProfile gameProfile) {
|
||||
super(clientWorld, gameProfile);
|
||||
}
|
||||
|
||||
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(ZF)V", shift = At.Shift.AFTER))
|
||||
public void inputHandle(CallbackInfo ci) {
|
||||
PlayerEntity player = MinecraftClient.getInstance().player;
|
||||
if (!MinecraftClient.getInstance().isRunning() || player == null) return;
|
||||
BasicEventsKt.getAfterTickInputEvent().invoke(new AfterTickInputEvent(this.input));
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import gg.norisk.heroes.common.events.EntityEvents;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.render.entity.EntityRenderDispatcher;
|
||||
import net.minecraft.client.render.entity.EntityRenderer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(EntityRenderDispatcher.class)
|
||||
public abstract class EntityRenderDispatcherMixin {
|
||||
@WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/EntityRenderer;render(Lnet/minecraft/entity/Entity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V"))
|
||||
private boolean onlyRenderIfAllowed(EntityRenderer<Entity> targetClass, Entity entity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) {
|
||||
var event = new EntityEvents.EntityRendererEvent(entity, f, g, matrixStack, vertexConsumerProvider, i);
|
||||
EntityEvents.INSTANCE.getEntityRendererEvent().invoke(event);
|
||||
return !event.isCancelled().get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import gg.norisk.heroes.client.ui.OrthoCamera;
|
||||
import gg.norisk.heroes.client.ui.screen.HeroSelectorScreen;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.GameOptions;
|
||||
import net.minecraft.client.option.Perspective;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(GameOptions.class)
|
||||
public abstract class GameOptionsMixin {
|
||||
@ModifyReturnValue(method = "getPerspective", at = @At("RETURN"))
|
||||
private Perspective heroapi$GetPerspective(Perspective original) {
|
||||
if (OrthoCamera.INSTANCE.isEnabled()) {
|
||||
return Perspective.THIRD_PERSON_FRONT;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.systems.VertexSorter;
|
||||
import gg.norisk.heroes.client.renderer.CameraShaker;
|
||||
import gg.norisk.heroes.client.ui.OrthoCamera;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.hud.InGameHud;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.GameRenderer;
|
||||
import net.minecraft.client.render.RenderTickCounter;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public abstract class GameRendererMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private MinecraftClient client;
|
||||
|
||||
@Inject(
|
||||
method = "render",
|
||||
at = @At("HEAD")
|
||||
)
|
||||
private void heroapi$onRender(RenderTickCounter renderTickCounter, boolean tick, CallbackInfo ci) {
|
||||
if (!client.skipGameRender && tick && client.world != null) {
|
||||
CameraShaker.INSTANCE.newFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@WrapWithCondition(
|
||||
method = "render",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;render(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/client/render/RenderTickCounter;)V")
|
||||
)
|
||||
private boolean heroapi$dontRenderHud(InGameHud instance, DrawContext drawContext, RenderTickCounter renderTickCounter) {
|
||||
return !OrthoCamera.INSTANCE.isEnabled();
|
||||
}
|
||||
|
||||
@WrapWithCondition(
|
||||
method = "renderWorld",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;renderHand(Lnet/minecraft/client/render/Camera;FLorg/joml/Matrix4f;)V")
|
||||
)
|
||||
private boolean heroapi$dontRenderHand(GameRenderer instance, Camera camera, float f, Matrix4f matrix4f) {
|
||||
return !OrthoCamera.INSTANCE.isEnabled();
|
||||
}
|
||||
|
||||
|
||||
@Inject(
|
||||
method = "renderHand",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/render/GameRenderer;tiltViewWhenHurt(Lnet/minecraft/client/util/math/MatrixStack;F)V"
|
||||
)
|
||||
)
|
||||
private void heroapi$shakeHand(Camera camera, float f, Matrix4f matrix4f, CallbackInfo ci) {
|
||||
float x = (float) CameraShaker.INSTANCE.getAvgX();
|
||||
float y = (float) CameraShaker.INSTANCE.getAvgY();
|
||||
|
||||
matrix4f.translate(x, -y, (float) .0); // opposite of camera
|
||||
}
|
||||
|
||||
// TODO keine ahnung es will nxi so wie ich will T_T
|
||||
@ModifyArg(
|
||||
method = "renderWorld",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/render/WorldRenderer;setupFrustum(Lnet/minecraft/util/math/Vec3d;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V"
|
||||
),
|
||||
index = 2
|
||||
)
|
||||
private Matrix4f heroapi$orthoFrustumProjMat(Matrix4f projMat) {
|
||||
if (OrthoCamera.INSTANCE.isEnabled()) {
|
||||
return OrthoCamera.INSTANCE.createOrthoMatrix(1.0F, 20.0F);
|
||||
}
|
||||
return projMat;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "renderWorld",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/render/WorldRenderer;render(Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/GameRenderer;Lnet/minecraft/client/render/LightmapTextureManager;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V"
|
||||
|
||||
),
|
||||
index = 6
|
||||
)
|
||||
private Matrix4f heroapi$orthoProjMat(Matrix4f projMat, @Local(argsOnly = true) RenderTickCounter tickCounter) {
|
||||
if (OrthoCamera.INSTANCE.isEnabled()) {
|
||||
float tickDelta = tickCounter.getTickDelta(true);
|
||||
Matrix4f mat = OrthoCamera.INSTANCE.createOrthoMatrix(tickDelta, 0.0F);
|
||||
RenderSystem.setProjectionMatrix(mat, VertexSorter.BY_Z);
|
||||
return mat;
|
||||
}
|
||||
return projMat;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "renderWorld",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lorg/joml/Quaternionf;conjugate(Lorg/joml/Quaternionf;)Lorg/joml/Quaternionf;",
|
||||
remap = false
|
||||
)
|
||||
)
|
||||
private Quaternionf heroapi$modifyRotation(Quaternionf original, @Local(argsOnly = true) RenderTickCounter tickCounter) {
|
||||
if (!OrthoCamera.INSTANCE.isEnabled()) {
|
||||
return original;
|
||||
}
|
||||
return original.rotationXYZ(
|
||||
OrthoCamera.INSTANCE.handlePitch(original, tickCounter.getTickDelta(false)),
|
||||
OrthoCamera.INSTANCE.handleYaw(original, tickCounter.getTickDelta(false)),
|
||||
0.0F
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import gg.norisk.heroes.client.events.ClientEvents;
|
||||
import gg.norisk.heroes.common.events.BasicEventsKt;
|
||||
import gg.norisk.heroes.common.events.MouseScrollEvent;
|
||||
import net.minecraft.client.Mouse;
|
||||
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(Mouse.class)
|
||||
public abstract class MouseMixin {
|
||||
@Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;getOverlay()Lnet/minecraft/client/gui/screen/Overlay;", shift = At.Shift.BEFORE))
|
||||
private void hookMouseScroll(long window, double horizontal, double vertical, CallbackInfo callbackInfo) {
|
||||
BasicEventsKt.getMouseScrollEvent().invoke(new MouseScrollEvent(window, horizontal, vertical));
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "onMouseScroll(JDD)V",
|
||||
at = @At(
|
||||
value = "FIELD",
|
||||
target = "Lnet/minecraft/client/Mouse;eventDeltaVerticalWheel:D",
|
||||
ordinal = 6
|
||||
),
|
||||
cancellable = true
|
||||
)
|
||||
private void updateZoom(CallbackInfo info) {
|
||||
var event = new ClientEvents.PreHotbarScrollEvent();
|
||||
ClientEvents.INSTANCE.getPreHotbarScrollEvent().invoke(event);
|
||||
if (event.isCancelled().get()) {
|
||||
info.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import gg.norisk.heroes.client.renderer.SkinUtils;
|
||||
import gg.norisk.heroes.common.hero.IHeroManagerKt;
|
||||
import gg.norisk.heroes.common.player.FFAPlayerKt;
|
||||
import net.minecraft.client.model.ModelPart;
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.render.entity.EntityRendererFactory;
|
||||
import net.minecraft.client.render.entity.LivingEntityRenderer;
|
||||
import net.minecraft.client.render.entity.PlayerEntityRenderer;
|
||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
||||
import net.minecraft.client.util.SkinTextures;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.entity.EntityAttachmentType;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(PlayerEntityRenderer.class)
|
||||
public abstract class PlayerEntityRendererMixin extends LivingEntityRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
|
||||
public PlayerEntityRendererMixin(EntityRendererFactory.Context ctx, PlayerEntityModel<AbstractClientPlayerEntity> model, float shadowRadius) {
|
||||
super(ctx, model, shadowRadius);
|
||||
}
|
||||
|
||||
@Inject(method = "getTexture(Lnet/minecraft/client/network/AbstractClientPlayerEntity;)Lnet/minecraft/util/Identifier;", at = @At("RETURN"), cancellable = true)
|
||||
private void getSkinTextureInjection(AbstractClientPlayerEntity player, CallbackInfoReturnable<Identifier> cir) {
|
||||
var hero = IHeroManagerKt.getHero(player);
|
||||
if (hero == null) return;
|
||||
var skin = hero.getInternalCallbacks().getGetSkin();
|
||||
if (skin != null) {
|
||||
cir.setReturnValue(skin.invoke(player));
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyReturnValue(
|
||||
method = "getTexture(Lnet/minecraft/client/network/AbstractClientPlayerEntity;)Lnet/minecraft/util/Identifier;",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private Identifier redirectHeroSkin(Identifier original, AbstractClientPlayerEntity abstractClientPlayerEntity) {
|
||||
return SkinUtils.INSTANCE.redirectCombinedSkin(original, abstractClientPlayerEntity);
|
||||
}
|
||||
|
||||
@Inject(method = "renderLabelIfPresent(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/text/Text;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IF)V", at = @At(value = "HEAD"))
|
||||
private void heroapi$renderBounty(AbstractClientPlayerEntity abstractClientPlayerEntity, Text text, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, float f, CallbackInfo ci) {
|
||||
renderBounty(this.dispatcher.getSquaredDistanceToCamera(abstractClientPlayerEntity), abstractClientPlayerEntity, matrixStack, vertexConsumerProvider, i, f);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void renderBounty(double d, AbstractClientPlayerEntity abstractClientPlayerEntity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, float f) {
|
||||
if (d < (double) 2000.0F) {
|
||||
int bounty = FFAPlayerKt.getFfaBounty(abstractClientPlayerEntity);
|
||||
if (bounty > 0) {
|
||||
Vec3d vec3d = abstractClientPlayerEntity.getAttachments().getPointNullable(EntityAttachmentType.NAME_TAG, 0, abstractClientPlayerEntity.getYaw(f));
|
||||
if (vec3d != null) {
|
||||
matrixStack.push();
|
||||
matrixStack.scale(0.5f, 0.5f, 0.5f);
|
||||
matrixStack.translate(vec3d.x, vec3d.y + 0.125f, vec3d.z);
|
||||
super.renderLabelIfPresent(abstractClientPlayerEntity, Text.empty().append("Bounty: ").append(String.valueOf(bounty)), matrixStack, vertexConsumerProvider, i, f);
|
||||
matrixStack.pop();
|
||||
matrixStack.translate(0.0F, 0.075F, 0.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "renderArm",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/SkinTextures;texture()Lnet/minecraft/util/Identifier;")
|
||||
)
|
||||
private Identifier heroapi$redirectRenderArmSkinTexture(SkinTextures instance, Operation<Identifier> original, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, AbstractClientPlayerEntity abstractClientPlayerEntity, ModelPart modelPart, ModelPart modelPart2) {
|
||||
return SkinUtils.INSTANCE.redirectCombinedSkin(original.call(instance), abstractClientPlayerEntity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package gg.norisk.heroes.common.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import gg.norisk.heroes.client.renderer.SkinUtils;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.hud.PlayerListHud;
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||
import net.minecraft.client.network.PlayerListEntry;
|
||||
import net.minecraft.client.util.SkinTextures;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(PlayerListHud.class)
|
||||
public abstract class PlayerListHudMixin {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private MinecraftClient client;
|
||||
|
||||
@WrapOperation(
|
||||
method = "render",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/SkinTextures;texture()Lnet/minecraft/util/Identifier;")
|
||||
)
|
||||
private Identifier heroapi$redirectCombinedSkin(SkinTextures instance, Operation<Identifier> original, @Local PlayerListEntry playerListEntry) {
|
||||
var player = client.world == null ? null : client.world.getPlayerByUuid(playerListEntry.getProfile().getId());
|
||||
return SkinUtils.INSTANCE.redirectCombinedSkin(original.call(instance), ((AbstractClientPlayerEntity) player));
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package gg.norisk.heroes.common.mixin.client.compat;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import gg.norisk.heroes.client.ui.OrthoCamera;
|
||||
import gg.norisk.heroes.client.ui.screen.HeroSelectorScreen;
|
||||
import me.jellysquid.mods.sodium.client.gl.device.RenderDevice;
|
||||
import me.jellysquid.mods.sodium.client.render.chunk.DefaultChunkRenderer;
|
||||
import me.jellysquid.mods.sodium.client.render.chunk.ShaderChunkRenderer;
|
||||
import me.jellysquid.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(value = DefaultChunkRenderer.class, remap = false)
|
||||
public abstract class DefaultChunkRendererMixin extends ShaderChunkRenderer {
|
||||
public DefaultChunkRendererMixin(RenderDevice device, ChunkVertexType vertexType) {
|
||||
super(device, vertexType);
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
remap = false,
|
||||
method = "render",
|
||||
at = @At(value = "FIELD", target = "Lme/jellysquid/mods/sodium/client/gui/SodiumGameOptions$PerformanceSettings;useBlockFaceCulling:Z", remap = false)
|
||||
)
|
||||
private boolean ffa$blockFaceCulling(boolean original) {
|
||||
return original && !(OrthoCamera.INSTANCE.isEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by FernFlower decompiler)
|
||||
//
|
||||
|
||||
package gg.norisk.heroes.common.ui;
|
||||
|
||||
import gg.norisk.heroes.client.ui.skilltree.AbilitySkillTreeComponent;
|
||||
import io.wispforest.owo.ui.container.FlowLayout;
|
||||
import io.wispforest.owo.ui.container.WrappingParentComponent;
|
||||
import io.wispforest.owo.ui.core.*;
|
||||
import io.wispforest.owo.ui.util.Delta;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ScrollContainerV2<C extends Component> extends WrappingParentComponent<C> {
|
||||
public static final Identifier VERTICAL_VANILLA_SCROLLBAR_TEXTURE = Identifier.of("owo", "scrollbar/vanilla_vertical");
|
||||
public static final Identifier DISABLED_VERTICAL_VANILLA_SCROLLBAR_TEXTURE = Identifier.of("owo", "scrollbar/vanilla_vertical_disabled");
|
||||
public static final Identifier HORIZONTAL_VANILLA_SCROLLBAR_TEXTURE = Identifier.of("owo", "scrollbar/vanilla_horizontal_disabled");
|
||||
public static final Identifier DISABLED_HORIZONTAL_VANILLA_SCROLLBAR_TEXTURE = Identifier.of("owo", "scrollbar/vanilla_horizontal_disabled");
|
||||
public static final Identifier VANILLA_SCROLLBAR_TRACK_TEXTURE = Identifier.of("owo", "scrollbar/track");
|
||||
public static final Identifier FLAT_VANILLA_SCROLLBAR_TEXTURE = Identifier.of("owo", "scrollbar/vanilla_flat");
|
||||
protected double scrollOffsetVertical = (double) 0.0F;
|
||||
protected double scrollOffsetHorizontal = (double) 0.0F;
|
||||
protected double currentScrollPositionVertical = (double) 0.0F;
|
||||
protected double currentScrollPositionHorizontal = (double) 0.0F;
|
||||
protected int lastScrollPositionVertical = -1;
|
||||
protected int lastScrollPositionHorizontal = -1;
|
||||
protected int scrollStep = 0;
|
||||
protected int fixedScrollbarLength = 0;
|
||||
protected double lastScrollbarLengthVertical = (double) 0.0F;
|
||||
protected double lastScrollbarLengthHorizontal = (double) 0.0F;
|
||||
protected boolean scrollbaring = false;
|
||||
protected int maxScrollVertical = 0;
|
||||
protected int maxScrollHorziontal = 0;
|
||||
protected int childSize = 0;
|
||||
protected final ScrollDirection verticalDirection = ScrollDirection.VERTICAL;
|
||||
protected final ScrollDirection horizontalDirection = ScrollDirection.HORIZONTAL;
|
||||
private boolean init = true;
|
||||
|
||||
public ScrollContainerV2(Sizing horizontalSizing, Sizing verticalSizing, C child) {
|
||||
super(horizontalSizing, verticalSizing, child);
|
||||
this.scrollOffsetHorizontal = maxScrollHorziontal - maxScrollHorziontal / 2.0;
|
||||
this.currentScrollPositionHorizontal = scrollOffsetHorizontal;
|
||||
}
|
||||
|
||||
protected int determineHorizontalContentSize(Sizing sizing) {
|
||||
if (this.verticalDirection == ScrollDirection.VERTICAL) {
|
||||
return super.determineHorizontalContentSize(sizing);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Horizontal ScrollContainer cannot be horizontally content-sized");
|
||||
}
|
||||
}
|
||||
|
||||
protected int determineVerticalContentSize(Sizing sizing) {
|
||||
if (this.horizontalDirection == ScrollDirection.HORIZONTAL) {
|
||||
return super.determineVerticalContentSize(sizing);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Vertical ScrollContainer cannot be vertically content-sized");
|
||||
}
|
||||
}
|
||||
|
||||
public void layout(Size space) {
|
||||
super.layout(space);
|
||||
this.maxScrollVertical = Math.max(0, (Integer) this.verticalDirection.sizeGetter.apply(this.child) - ((Integer) this.verticalDirection.sizeGetter.apply(this) - (Integer) this.verticalDirection.insetGetter.apply((Insets) this.padding.get())));
|
||||
this.maxScrollHorziontal = Math.max(0, (Integer) this.horizontalDirection.sizeGetter.apply(this.child) - ((Integer) this.horizontalDirection.sizeGetter.apply(this) - (Integer) this.horizontalDirection.insetGetter.apply((Insets) this.padding.get())));
|
||||
this.scrollOffsetVertical = MathHelper.clamp(this.scrollOffsetVertical, (double) 0.0F, (double) this.maxScrollVertical + (double) 0.5F);
|
||||
this.scrollOffsetHorizontal = MathHelper.clamp(this.scrollOffsetHorizontal, (double) 0.0F, (double) this.maxScrollHorziontal + (double) 0.5F);
|
||||
this.childSize = (Integer) this.verticalDirection.sizeGetter.apply(this.child);
|
||||
this.lastScrollPositionVertical = -1;
|
||||
this.lastScrollPositionHorizontal = -1;
|
||||
}
|
||||
|
||||
protected int childMountX() {
|
||||
return (int) ((double) super.childMountX() - this.verticalDirection.choose(this.currentScrollPositionVertical, (double) 0.0F));
|
||||
}
|
||||
|
||||
protected int childMountY() {
|
||||
return (int) ((double) super.childMountY() - this.verticalDirection.choose((double) 0.0F, this.currentScrollPositionVertical));
|
||||
}
|
||||
|
||||
protected void parentUpdate(float delta, int mouseX, int mouseY) {
|
||||
super.parentUpdate(delta, mouseX, mouseY);
|
||||
this.currentScrollPositionVertical += Delta.compute(this.currentScrollPositionVertical, this.scrollOffsetVertical, (double) delta * (double) 0.5F);
|
||||
this.currentScrollPositionHorizontal += Delta.compute(this.currentScrollPositionHorizontal, this.scrollOffsetHorizontal, (double) delta * (double) 0.5F);
|
||||
}
|
||||
|
||||
public void draw(OwoUIDrawContext context, int mouseX, int mouseY, float partialTicks, float delta) {
|
||||
if (init) {
|
||||
//for centering
|
||||
init = false;
|
||||
this.scrollOffsetHorizontal = maxScrollHorziontal - maxScrollHorziontal / 2.0;
|
||||
this.currentScrollPositionHorizontal = scrollOffsetHorizontal;
|
||||
}
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta);
|
||||
int effectiveScrollOffsetVertical = this.scrollStep > 0 ? (int) this.scrollOffsetVertical / this.scrollStep * this.scrollStep : (int) this.currentScrollPositionVertical;
|
||||
if (this.scrollStep > 0 && (double) this.maxScrollVertical - this.scrollOffsetVertical == (double) -1.0F) {
|
||||
effectiveScrollOffsetVertical = (int) ((double) effectiveScrollOffsetVertical + this.scrollOffsetVertical % (double) this.scrollStep);
|
||||
}
|
||||
|
||||
int newScrollPositionVertical = this.verticalDirection.coordinateGetter.apply(this) - effectiveScrollOffsetVertical;
|
||||
if (newScrollPositionVertical != this.lastScrollPositionVertical) {
|
||||
this.verticalDirection.coordinateSetter.accept(this.child, newScrollPositionVertical + (this.padding.get().top() + this.child.margins().get().top()));
|
||||
this.lastScrollPositionVertical = newScrollPositionVertical;
|
||||
}
|
||||
|
||||
//HORIZONTAL
|
||||
|
||||
int effectiveScrollOffsetHorizontal = this.scrollStep > 0 ? (int) this.scrollOffsetHorizontal / this.scrollStep * this.scrollStep : (int) this.currentScrollPositionHorizontal;
|
||||
if (this.scrollStep > 0 && (double) this.maxScrollHorziontal - this.scrollOffsetHorizontal == (double) -1.0F) {
|
||||
effectiveScrollOffsetHorizontal = (int) ((double) effectiveScrollOffsetHorizontal + this.scrollOffsetHorizontal % (double) this.scrollStep);
|
||||
}
|
||||
|
||||
int newScrollPositionHorizontal = this.horizontalDirection.coordinateGetter.apply(this) - effectiveScrollOffsetHorizontal;
|
||||
if (newScrollPositionHorizontal != this.lastScrollPositionHorizontal) {
|
||||
this.horizontalDirection.coordinateSetter.accept(this.child, newScrollPositionHorizontal + (this.padding.get().left() + this.child.margins().get().left()));
|
||||
this.lastScrollPositionHorizontal = newScrollPositionHorizontal;
|
||||
}
|
||||
|
||||
|
||||
context.getMatrices().push();
|
||||
double visualOffsetVertical = -(this.currentScrollPositionVertical % (double) 1.0F);
|
||||
if (visualOffsetVertical > 0.9999999 || visualOffsetVertical < 1.0E-7) {
|
||||
visualOffsetVertical = (double) 0.0F;
|
||||
}
|
||||
double visualOffsetHorizontal = -(this.currentScrollPositionHorizontal % (double) 1.0F);
|
||||
if (visualOffsetHorizontal > 0.9999999 || visualOffsetHorizontal < 1.0E-7) {
|
||||
visualOffsetHorizontal = (double) 0.0F;
|
||||
}
|
||||
|
||||
context.getMatrices().translate(this.horizontalDirection.choose(visualOffsetHorizontal, 0.0F), this.verticalDirection.choose(0.0F, visualOffsetVertical), 0.0F);
|
||||
this.drawChildren(context, mouseX, mouseY, partialTicks, delta, this.childView);
|
||||
context.getMatrices().pop();
|
||||
|
||||
Insets padding = this.padding.get();
|
||||
int selfSizeVertical = this.verticalDirection.sizeGetter.apply(this);
|
||||
int contentSizeVertical = this.verticalDirection.sizeGetter.apply(this) - this.verticalDirection.insetGetter.apply(padding);
|
||||
this.lastScrollbarLengthVertical = this.fixedScrollbarLength == 0 ? Math.min(Math.floor((float) selfSizeVertical / (float) this.childSize * (float) contentSizeVertical), contentSizeVertical) : (double) this.fixedScrollbarLength;
|
||||
|
||||
int selfSizeHorizontal = this.horizontalDirection.sizeGetter.apply(this);
|
||||
int contentSizeHorizontal = this.horizontalDirection.sizeGetter.apply(this) - this.horizontalDirection.insetGetter.apply(padding);
|
||||
this.lastScrollbarLengthHorizontal = this.fixedScrollbarLength == 0 ? Math.min(Math.floor((float) selfSizeHorizontal / (float) this.childSize * (float) contentSizeHorizontal), contentSizeHorizontal) : (double) this.fixedScrollbarLength;
|
||||
}
|
||||
|
||||
public boolean canFocus(FocusSource source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onMouseScroll(double mouseX, double mouseY, double amount) {
|
||||
if (this.child.onMouseScroll((double) this.x + mouseX - (double) this.child.x(), (double) this.y + mouseY - (double) this.child.y(), amount)) {
|
||||
return true;
|
||||
} else {
|
||||
if (this.scrollStep < 1) {
|
||||
this.scrollByVertical(-amount * (double) 15.0F, false, true);
|
||||
} else {
|
||||
this.scrollByVertical(-amount * (double) this.scrollStep, true, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onMouseDown(double mouseX, double mouseY, int button) {
|
||||
if (this.isInScrollbar((double) this.x + mouseX, (double) this.y + mouseY)) {
|
||||
super.onMouseDown(mouseX, mouseY, button);
|
||||
return true;
|
||||
} else {
|
||||
return super.onMouseDown(mouseX, mouseY, button);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onMouseDrag(double mouseX, double mouseY, double deltaX, double deltaY, int button) {
|
||||
if (!this.scrollbaring && !this.isInScrollbar((double) this.x + mouseX, (double) this.y + mouseY)) {
|
||||
return super.onMouseDrag(mouseX, mouseY, deltaX, deltaY, button);
|
||||
} else {
|
||||
double deltaVertical = this.verticalDirection.choose(deltaX, deltaY) * -1;
|
||||
double selfSizeVertical = this.verticalDirection.sizeGetter.apply(this) - this.verticalDirection.insetGetter.apply(this.padding.get());
|
||||
double scalarVertical = (double) this.maxScrollVertical / (selfSizeVertical - this.lastScrollbarLengthVertical);
|
||||
if (!Double.isFinite(scalarVertical)) {
|
||||
scalarVertical = 0.0F;
|
||||
}
|
||||
|
||||
this.scrollByVertical(deltaVertical * scalarVertical, true, false);
|
||||
|
||||
double deltaHorizontal = this.horizontalDirection.choose(deltaX, deltaY) * -1;
|
||||
double selfSizeHorizontal = this.horizontalDirection.sizeGetter.apply(this) - this.horizontalDirection.insetGetter.apply(this.padding.get());
|
||||
double scalarHorizontal = (double) this.maxScrollHorziontal / (selfSizeHorizontal - this.lastScrollbarLengthVertical);
|
||||
if (!Double.isFinite(scalarHorizontal)) {
|
||||
scalarHorizontal = 0.0F;
|
||||
}
|
||||
|
||||
this.scrollByHorizontal(deltaHorizontal * scalarHorizontal, true, false);
|
||||
this.scrollbaring = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onKeyPress(int keyCode, int scanCode, int modifiers) {
|
||||
if (keyCode == this.verticalDirection.lessKeycode) {
|
||||
this.scrollByVertical((double) -10.0F, false, true);
|
||||
} else if (keyCode == this.verticalDirection.moreKeycode) {
|
||||
this.scrollByVertical((double) 10.0F, false, true);
|
||||
} else if (keyCode == 267) {
|
||||
this.scrollByVertical(this.verticalDirection.choose((double) this.width, (double) this.height) * 0.8, false, true);
|
||||
} else if (keyCode == 266) {
|
||||
this.scrollByVertical(this.verticalDirection.choose((double) this.width, (double) this.height) * -0.8, false, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onMouseUp(double mouseX, double mouseY, int button) {
|
||||
this.scrollbaring = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public @Nullable Component childAt(int x, int y) {
|
||||
return this.isInScrollbar((double) x, (double) y) ? this : super.childAt(x, y);
|
||||
}
|
||||
|
||||
protected void scrollByVertical(double offset, boolean instant, boolean showScrollbar) {
|
||||
this.scrollOffsetVertical = MathHelper.clamp(this.scrollOffsetVertical + offset, (double) 0.0F, (double) this.maxScrollVertical + (double) 0.5F);
|
||||
if (instant) {
|
||||
this.currentScrollPositionVertical = this.scrollOffsetVertical;
|
||||
}
|
||||
}
|
||||
|
||||
protected void scrollByHorizontal(double offset, boolean instant, boolean showScrollbar) {
|
||||
this.scrollOffsetHorizontal = MathHelper.clamp(this.scrollOffsetHorizontal + offset, (double) 0.0F, (double) this.maxScrollHorziontal + (double) 0.5F);
|
||||
if (instant) {
|
||||
this.currentScrollPositionHorizontal = this.scrollOffsetHorizontal;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isInScrollbar(double mouseX, double mouseY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ScrollContainerV2<C> scrollTo(Component component) {
|
||||
this.scrollOffsetVertical = MathHelper.clamp(this.scrollOffsetVertical - (double) (this.y - component.y() + ((Insets) component.margins().get()).top()), (double) 0.0F, (double) this.maxScrollVertical);
|
||||
this.scrollOffsetHorizontal = MathHelper.clamp(this.scrollOffsetHorizontal - (double) (this.x - component.x() + ((Insets) component.margins().get()).right()) - component.width() * 4 - 28, (double) 0.0F, (double) this.maxScrollHorziontal);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScrollContainerV2<C> scrollTo(@Range(
|
||||
from = 0L,
|
||||
to = 1L
|
||||
) double horizontal, double vertical) {
|
||||
this.scrollOffsetVertical = (double) this.maxScrollVertical * vertical;
|
||||
this.scrollOffsetHorizontal = (double) this.maxScrollHorziontal * horizontal;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScrollContainerV2<C> scrollStep(int scrollStep) {
|
||||
this.scrollStep = scrollStep;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int scrollStep() {
|
||||
return this.scrollStep;
|
||||
}
|
||||
|
||||
public ScrollContainerV2<C> fixedScrollbarLength(int fixedScrollbarLength) {
|
||||
this.fixedScrollbarLength = fixedScrollbarLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int fixedScrollbarLength() {
|
||||
return this.fixedScrollbarLength;
|
||||
}
|
||||
|
||||
public static enum ScrollDirection {
|
||||
VERTICAL(Component::height, Component::updateY, Component::y, Insets::vertical, 265, 264),
|
||||
HORIZONTAL(Component::width, Component::updateX, Component::x, Insets::horizontal, 263, 262);
|
||||
|
||||
public final Function<Component, Integer> sizeGetter;
|
||||
public final BiConsumer<Component, Integer> coordinateSetter;
|
||||
public final Function<ScrollContainerV2<?>, Integer> coordinateGetter;
|
||||
public final Function<Insets, Integer> insetGetter;
|
||||
public final int lessKeycode;
|
||||
public final int moreKeycode;
|
||||
|
||||
private ScrollDirection(Function<Component, Integer> sizeGetter, BiConsumer<Component, Integer> coordinateSetter, Function<ScrollContainerV2<?>, Integer> coordinateGetter, Function<Insets, Integer> insetGetter, int lessKeycode, int moreKeycode) {
|
||||
this.sizeGetter = sizeGetter;
|
||||
this.coordinateSetter = coordinateSetter;
|
||||
this.coordinateGetter = coordinateGetter;
|
||||
this.insetGetter = insetGetter;
|
||||
this.lessKeycode = lessKeycode;
|
||||
this.moreKeycode = moreKeycode;
|
||||
}
|
||||
|
||||
public double choose(double horizontal, double vertical) {
|
||||
double var10000;
|
||||
switch (this.ordinal()) {
|
||||
case 0 -> var10000 = vertical;
|
||||
case 1 -> var10000 = horizontal;
|
||||
default -> throw new MatchException((String) null, (Throwable) null);
|
||||
}
|
||||
|
||||
return var10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package gg.norisk.heroes.client
|
||||
|
||||
import gg.norisk.heroes.client.command.ClientHeroCommand
|
||||
import gg.norisk.heroes.client.config.ConfigManagerClient
|
||||
import gg.norisk.heroes.client.hero.ability.AbilityKeyBindManager
|
||||
import gg.norisk.heroes.client.hero.ability.AbilityManagerClient
|
||||
import gg.norisk.heroes.client.networking.MouseListener
|
||||
import gg.norisk.heroes.client.option.HeroKeyBindings
|
||||
import gg.norisk.heroes.client.renderer.CameraShaker
|
||||
import gg.norisk.heroes.client.renderer.KeyBindHud
|
||||
import gg.norisk.heroes.client.renderer.Speedlines
|
||||
import gg.norisk.heroes.client.ui.OrthoCamera
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import net.fabricmc.api.ClientModInitializer
|
||||
|
||||
object HeroesManagerClient : ClientModInitializer {
|
||||
override fun onInitializeClient() {
|
||||
logger.info("Init Hero client...")
|
||||
|
||||
HeroKeyBindings.initClient()
|
||||
ConfigManagerClient.init()
|
||||
AbilityManagerClient.init()
|
||||
OrthoCamera.initClient()
|
||||
AbilityKeyBindManager.initializeKeyBindListeners()
|
||||
|
||||
KeyBindHud.init()
|
||||
MouseListener.initClient()
|
||||
Speedlines.initClient()
|
||||
CameraShaker.initClient()
|
||||
ClientHeroCommand.init()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package gg.norisk.heroes.client.command
|
||||
|
||||
import gg.norisk.heroes.client.config.ConfigManagerClient
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import kotlinx.serialization.encodeToString
|
||||
import net.silkmc.silk.commands.clientCommand
|
||||
import net.silkmc.silk.commands.player
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
|
||||
object ClientHeroCommand {
|
||||
fun init() {
|
||||
clientCommand("heroes-client") {
|
||||
requires { it.enabledFeatures.contains(HeroesManager.heroesFlag) }
|
||||
literal("debug") {
|
||||
literal("printffaplayer") {
|
||||
runs {
|
||||
val player = this.source.player
|
||||
player.sendMessage(literalText {
|
||||
text("FFA Player:")
|
||||
emptyLine()
|
||||
text(ConfigManagerClient.JSON.encodeToString(player.ffaPlayer))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package gg.norisk.heroes.client.config
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.HeroManager
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
|
||||
object ConfigManagerClient {
|
||||
val JSON = Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
fun init() {
|
||||
Networking.s2cHeroSettingsPacket.receiveOnClient { packet, context ->
|
||||
mcCoroutineTask(sync = true, client = true) {
|
||||
val decoded = JSON.decodeFromString<List<Hero.HeroJson>>(packet)
|
||||
for (heroJson in decoded) {
|
||||
logger.info("Loading HeroJson ${heroJson.internalKey}")
|
||||
HeroManager.getHero(heroJson.internalKey)?.load(heroJson)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package gg.norisk.heroes.client.events
|
||||
|
||||
import net.silkmc.silk.core.event.Cancellable
|
||||
import net.silkmc.silk.core.event.Event
|
||||
import net.silkmc.silk.core.event.EventScopeProperty
|
||||
|
||||
object ClientEvents {
|
||||
data class CameraClipToSpaceEvent(var value: Double)
|
||||
|
||||
val cameraClipToSpaceEvent = Event.onlySync<CameraClipToSpaceEvent>()
|
||||
|
||||
class PreHotbarScrollEvent: Cancellable {
|
||||
override val isCancelled: EventScopeProperty<Boolean> = EventScopeProperty(false)
|
||||
}
|
||||
|
||||
val preHotbarScrollEvent = Event.onlySync<PreHotbarScrollEvent>()
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package gg.norisk.heroes.client.hero.ability
|
||||
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.ability.AbilityPacketDescription
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.common.hero.ability.implementation.HoldAbility
|
||||
import gg.norisk.heroes.common.hero.ability.implementation.PressAbility
|
||||
import gg.norisk.heroes.common.hero.ability.implementation.ToggleAbility
|
||||
import gg.norisk.heroes.common.hero.getHero
|
||||
import gg.norisk.utils.events.KeyEvents
|
||||
import gg.norisk.utils.events.MouseEvents
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.silkmc.silk.core.event.EventPriority
|
||||
|
||||
object AbilityKeyBindManager {
|
||||
fun initializeKeyBindListeners() {
|
||||
MouseEvents.mouseClickEvent.listen(EventPriority.FIRST) { event ->
|
||||
if (MinecraftClient.getInstance().currentScreen != null) return@listen
|
||||
val player = MinecraftClient.getInstance().player ?: return@listen
|
||||
val hero = MinecraftClient.getInstance().player?.getHero() ?: return@listen
|
||||
hero.getUsableAbilities(player).filter { it.keyBind?.matchesMouse(event.key.code) ?: false }
|
||||
.sortedByDescending { it.condition != null }.forEach { ability ->
|
||||
val isConditionMet =
|
||||
if (ability.condition == null) true else ability.condition?.invoke(player) == true
|
||||
if (isConditionMet && handleAbility(player, hero, ability, event.pressed, event.pressed)) {
|
||||
event.isCancelled.set(true)
|
||||
return@listen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyEvents.keyEvent.listen(EventPriority.FIRST) { event ->
|
||||
if (MinecraftClient.getInstance().currentScreen != null) return@listen
|
||||
val player = MinecraftClient.getInstance().player ?: return@listen
|
||||
val hero = MinecraftClient.getInstance().player?.getHero() ?: return@listen
|
||||
hero.getUsableAbilities(player).filter { it.keyBind?.matchesKey(event.key, event.scanCode) ?: false }
|
||||
.sortedByDescending { it.condition != null }.forEach { ability ->
|
||||
val isConditionMet =
|
||||
if (ability.condition == null) true else ability.condition?.invoke(player) == true
|
||||
if (isConditionMet && handleAbility(
|
||||
player,
|
||||
hero,
|
||||
ability,
|
||||
event.isClicked(),
|
||||
event.isHold()
|
||||
)
|
||||
) {
|
||||
event.isCancelled.set(true)
|
||||
return@listen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO FUNKTIONIERT DAS GUT?
|
||||
fun initializeKeyBind(ability: AbstractAbility<*>) {
|
||||
logger.info("Initialize Keybind for Ability ${ability.internalKey}")
|
||||
val keyBind = ability.keyBind ?: return
|
||||
mouseClickEvent.listen { event ->
|
||||
if (keyBind.matchesMouse(event.key.code) && canUseAbility(ability)) {
|
||||
handleAbility(ability, event.pressed)
|
||||
}
|
||||
}
|
||||
keyEvent.listen { event ->
|
||||
if (event.isHold()) return@listen
|
||||
if (keyBind.matchesKey(event.key, event.scanCode) && canUseAbility(ability)) {
|
||||
handleAbility(ability, event.isClicked())
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private fun handleAbility(
|
||||
player: PlayerEntity,
|
||||
hero: Hero,
|
||||
ability: AbstractAbility<*>,
|
||||
pressed: Boolean,
|
||||
hold: Boolean,
|
||||
): Boolean {
|
||||
when (ability) {
|
||||
is PressAbility -> {
|
||||
if (!pressed) return false
|
||||
return AbilityManagerClient.useAbility(player, hero, ability, AbilityPacketDescription.Use())
|
||||
}
|
||||
|
||||
is HoldAbility -> {
|
||||
if (pressed) {
|
||||
if (AbilityManagerClient.isUsingAbility(player, ability)) return false
|
||||
return AbilityManagerClient.startAbility(player, hero, ability)
|
||||
} else if (hold) {
|
||||
return false
|
||||
} else {
|
||||
if (!AbilityManagerClient.isUsingAbility(player, ability)) return false
|
||||
return AbilityManagerClient.endAbility(player, hero, ability)
|
||||
}
|
||||
}
|
||||
|
||||
is ToggleAbility -> {
|
||||
if (pressed) return false
|
||||
return if (!AbilityManagerClient.isUsingAbility(player, ability)) {
|
||||
AbilityManagerClient.startAbility(player, hero, ability)
|
||||
} else {
|
||||
AbilityManagerClient.endAbility(player, hero, ability)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package gg.norisk.heroes.client.hero.ability
|
||||
|
||||
import gg.norisk.datatracker.entity.syncedValueChangeEvent
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.HeroManager
|
||||
import gg.norisk.heroes.common.hero.ability.*
|
||||
import gg.norisk.heroes.common.hero.ability.implementation.Ability
|
||||
import gg.norisk.heroes.common.hero.ability.implementation.PressAbility
|
||||
import gg.norisk.heroes.common.hero.ability.implementation.ToggleAbility
|
||||
import gg.norisk.heroes.common.hero.getHero
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.heroes.common.networking.Networking.s2cAbilityPacket
|
||||
import gg.norisk.heroes.common.networking.Networking.s2cCooldownPacket
|
||||
import gg.norisk.utils.DevUtils.uniqueId
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
import java.util.*
|
||||
|
||||
object AbilityManagerClient : IAbilityManager {
|
||||
private val abilitiesInUse = hashMapOf<UUID, AbstractAbility<*>>()
|
||||
|
||||
override fun init() {
|
||||
//cleanup
|
||||
syncedValueChangeEvent.listen { event ->
|
||||
if (event.key != HeroManager.HERO_KEY) return@listen
|
||||
val player = event.entity as? AbstractClientPlayerEntity? ?: return@listen
|
||||
if (player == MinecraftClient.getInstance().player) {
|
||||
(event.oldValue as? Hero?)?.abilities?.forEach { (name, ability) -> ability.onDisable(player) }
|
||||
abilitiesInUse.remove(player.uniqueId)
|
||||
HeroManager.registeredHeroes.values.forEach {
|
||||
it.abilities.values.forEach { ability ->
|
||||
ability.removeCooldown(player)
|
||||
}
|
||||
}
|
||||
player.getHero()?.abilities?.forEach { (name, ability) -> ability.onEnable(player) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
s2cCooldownPacket.receiveOnClient { packet, context ->
|
||||
mcCoroutineTask(sync = true, client = true) {
|
||||
val player =
|
||||
context.client.world?.getEntityById(packet.entityId) as? PlayerEntity? ?: return@mcCoroutineTask
|
||||
val hero = HeroManager.getHero(packet.heroKey) ?: return@mcCoroutineTask
|
||||
val ability = hero.abilities[packet.abilityKey] ?: return@mcCoroutineTask
|
||||
ability.setCooldown(packet, player)
|
||||
}
|
||||
}
|
||||
|
||||
s2cAbilityPacket.receiveOnClient { packet, context ->
|
||||
runCatching {
|
||||
val player = context.client.player ?: return@receiveOnClient
|
||||
val heroPlayer = context.client.world?.players?.firstOrNull { it.uuid == packet.playerUuid }
|
||||
?: return@receiveOnClient
|
||||
val ability = getAbilityFromAbilityPacket(packet) ?: return@receiveOnClient
|
||||
val description = packet.description
|
||||
val isOwnPacket = heroPlayer.uuid == player.uuid
|
||||
val abilityScope = AbilityScope(heroPlayer)
|
||||
when (ability) {
|
||||
is Ability,
|
||||
is PressAbility -> {
|
||||
ability.onStart(player, abilityScope)
|
||||
/*val callbacks = ability.internalCallbacks as AbstractAbility.ReceiveCallbacks
|
||||
callbacks.handleAllClients?.invoke(heroPlayer, player, description)
|
||||
if (isOwnPacket) {
|
||||
callbacks.handleOwnClient?.invoke(player, description)
|
||||
} else {
|
||||
callbacks.handleOtherClients?.invoke(heroPlayer, player, description)
|
||||
}*/
|
||||
}
|
||||
|
||||
is ToggleAbility -> {
|
||||
val callbacks = when (description) {
|
||||
is AbilityPacketDescription.Start -> {
|
||||
abilitiesInUse[packet.playerUuid] = ability
|
||||
logger.info("Start")
|
||||
ability.onStart(player, abilityScope)
|
||||
//ability.internalCallbacks.START
|
||||
}
|
||||
|
||||
is AbilityPacketDescription.Use -> {
|
||||
ability.onUse(player)
|
||||
//ability.internalCallbacks.USE
|
||||
}
|
||||
|
||||
is AbilityPacketDescription.End -> {
|
||||
abilitiesInUse.remove(packet.playerUuid)
|
||||
ability.onEnd(player, ToggleAbility.AbilityEndInformation())
|
||||
//(ability).internalCallbacks.END
|
||||
}
|
||||
}
|
||||
//callbacks.handleAllClients?.invoke(heroPlayer, player, description)
|
||||
|
||||
if (isOwnPacket) {
|
||||
//callbacks.handleOwnClient?.invoke(player, description)
|
||||
} else {
|
||||
//callbacks.handleOtherClients?.invoke(heroPlayer, player, description)
|
||||
}
|
||||
}
|
||||
|
||||
else -> error("Received an unknown Ability?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isUsingAbility(player: PlayerEntity, ability: AbstractAbility<*>): Boolean {
|
||||
return abilitiesInUse[player.uuid]?.internalKey == ability.internalKey
|
||||
}
|
||||
|
||||
override fun registerAbility(ability: AbstractAbility<*>) {
|
||||
// REMOVED AbilityKeyBindManager.initializeKeyBind(ability)
|
||||
}
|
||||
|
||||
override fun useAbility(
|
||||
player: PlayerEntity,
|
||||
hero: Hero,
|
||||
ability: AbstractAbility<*>,
|
||||
description: AbilityPacketDescription.Use
|
||||
): Boolean {
|
||||
if (ability.hasCooldown(player)) {
|
||||
return false
|
||||
} else {
|
||||
val packet = AbilityPacket(player.uuid, hero.internalKey, ability.internalKey, description)
|
||||
Networking.c2sAbilityPacket.send(packet)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun useAbility(
|
||||
player: PlayerEntity,
|
||||
ability: AbstractAbility<*>,
|
||||
description: AbilityPacketDescription.Use
|
||||
) {
|
||||
val hero = player.getHero() ?: return
|
||||
//player.sendDebugMessage("Sending Start Use $ability".literal)
|
||||
val packet = AbilityPacket(player.uuid, hero.internalKey, ability.internalKey, description)
|
||||
Networking.c2sAbilityPacket.send(packet)
|
||||
}
|
||||
|
||||
fun startAbility(player: PlayerEntity, hero: Hero, ability: ToggleAbility): Boolean {
|
||||
if (ability.hasCooldown(player)) {
|
||||
return false
|
||||
} else {
|
||||
//player.sendDebugMessage("Sending Start Ability $ability".literal)
|
||||
val packet =
|
||||
AbilityPacket(player.uuid, hero.internalKey, ability.internalKey, AbilityPacketDescription.Start)
|
||||
Networking.c2sAbilityPacket.send(packet)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun endAbility(player: PlayerEntity, hero: Hero, ability: ToggleAbility): Boolean {
|
||||
//player.sendDebugMessage("Sending End Ability $ability".literal)
|
||||
val packet = AbilityPacket(player.uuid, hero.internalKey, ability.internalKey, AbilityPacketDescription.End)
|
||||
Networking.c2sAbilityPacket.send(packet)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getAbilityFromAbilityPacket(packet: AbilityPacket<out AbilityPacketDescription>): AbstractAbility<*>? {
|
||||
val hero = HeroManager.getHero(packet.heroKey) ?: return null
|
||||
val ability = hero.abilities[packet.abilityKey]
|
||||
return ability
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package gg.norisk.heroes.client.networking
|
||||
|
||||
import gg.norisk.heroes.common.events.mouseScrollEvent
|
||||
import gg.norisk.heroes.common.networking.Networking.mousePacket
|
||||
import gg.norisk.heroes.common.networking.Networking.mouseScrollPacket
|
||||
import gg.norisk.heroes.common.networking.dto.MouseAction
|
||||
import gg.norisk.heroes.common.networking.dto.MousePacket
|
||||
import gg.norisk.heroes.common.networking.dto.MouseType
|
||||
import gg.norisk.utils.events.MouseEvents.mouseClickEvent
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object MouseListener {
|
||||
fun initClient() {
|
||||
mouseScrollEvent.listen {
|
||||
MinecraftClient.getInstance().player ?: return@listen
|
||||
mouseScrollPacket.send(it.vertical > 0)
|
||||
}
|
||||
mouseClickEvent.listen {
|
||||
MinecraftClient.getInstance().player ?: return@listen
|
||||
if (MinecraftClient.getInstance().options.attackKey.matchesMouse(it.key.code)) {
|
||||
mousePacket.send(
|
||||
MousePacket(
|
||||
MouseType.LEFT,
|
||||
if (it.pressed) MouseAction.CLICK else MouseAction.RELEASE
|
||||
)
|
||||
)
|
||||
} else if (MinecraftClient.getInstance().options.useKey.matchesMouse(it.key.code)) {
|
||||
mousePacket.send(
|
||||
MousePacket(
|
||||
MouseType.RIGHT,
|
||||
if (it.pressed) MouseAction.CLICK else MouseAction.RELEASE
|
||||
)
|
||||
)
|
||||
} else if (MinecraftClient.getInstance().options.pickItemKey.matchesMouse(it.key.code)) {
|
||||
mousePacket.send(
|
||||
MousePacket(
|
||||
MouseType.MIDDLE,
|
||||
if (it.pressed) MouseAction.CLICK else MouseAction.RELEASE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
ClientTickEvents.END_CLIENT_TICK.register {
|
||||
MinecraftClient.getInstance().player ?: return@register
|
||||
if (MinecraftClient.getInstance().options.attackKey.isPressed) {
|
||||
mousePacket.send(MousePacket(MouseType.LEFT, MouseAction.HOLD))
|
||||
}
|
||||
if (MinecraftClient.getInstance().options.useKey.isPressed) {
|
||||
mousePacket.send(MousePacket(MouseType.RIGHT, MouseAction.HOLD))
|
||||
}
|
||||
if (MinecraftClient.getInstance().options.pickItemKey.isPressed) {
|
||||
mousePacket.send(MousePacket(MouseType.MIDDLE, MouseAction.HOLD))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package gg.norisk.heroes.client.option
|
||||
|
||||
import gg.norisk.heroes.common.hero.getHero
|
||||
import gg.norisk.utils.events.KeyEvents
|
||||
import net.fabricmc.api.EnvType
|
||||
import net.fabricmc.api.Environment
|
||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.option.KeyBinding
|
||||
import net.minecraft.client.util.InputUtil
|
||||
import org.lwjgl.glfw.GLFW
|
||||
|
||||
object HeroKeyBindings {
|
||||
val firstKeyBind = if (FabricLoader.getInstance().environmentType == EnvType.SERVER) null
|
||||
else KeyBindingHelper.registerKeyBinding(
|
||||
KeyBinding(
|
||||
"key.heroes.first",
|
||||
InputUtil.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_X,
|
||||
"category.heroes.abilities"
|
||||
)
|
||||
)
|
||||
|
||||
val secondKeyBind = if (FabricLoader.getInstance().environmentType == EnvType.SERVER) null
|
||||
else KeyBindingHelper.registerKeyBinding(
|
||||
KeyBinding(
|
||||
"key.heroes.second", // The translation key of the keybinding's name
|
||||
InputUtil.Type.KEYSYM, // The type of the keybinding, KEYSYM for keyboard, MOUSE for mouse.
|
||||
GLFW.GLFW_KEY_V, // The keycode of the key
|
||||
"category.heroes.abilities" // The translation key of the keybinding's category.
|
||||
)
|
||||
)
|
||||
|
||||
val thirdKeyBind = if (FabricLoader.getInstance().environmentType == EnvType.SERVER) null
|
||||
else KeyBindingHelper.registerKeyBinding(
|
||||
KeyBinding(
|
||||
"key.heroes.third", // The translation key of the keybinding's name
|
||||
InputUtil.Type.KEYSYM, // The type of the keybinding, KEYSYM for keyboard, MOUSE for mouse.
|
||||
GLFW.GLFW_KEY_G, // The keycode of the key
|
||||
"category.heroes.abilities" // The translation key of the keybinding's category.
|
||||
)
|
||||
)
|
||||
|
||||
val fourthKeyBinding = if (FabricLoader.getInstance().environmentType == EnvType.SERVER) null
|
||||
else KeyBindingHelper.registerKeyBinding(
|
||||
KeyBinding(
|
||||
"key.heroes.fourth", // The translation key of the keybinding's name
|
||||
InputUtil.Type.KEYSYM, // The type of the keybinding, KEYSYM for keyboard, MOUSE for mouse.
|
||||
GLFW.GLFW_KEY_H, // The keycode of the key
|
||||
"category.heroes.abilities" // The translation key of the keybinding's category.
|
||||
)
|
||||
)
|
||||
|
||||
val fifthKeyBind = if (FabricLoader.getInstance().environmentType == EnvType.SERVER) null
|
||||
else KeyBindingHelper.registerKeyBinding(
|
||||
KeyBinding(
|
||||
"key.heroes.fifth", // The translation key of the keybinding's name
|
||||
InputUtil.Type.KEYSYM, // The type of the keybinding, KEYSYM for keyboard, MOUSE for mouse.
|
||||
GLFW.GLFW_KEY_Z, // The keycode of the key
|
||||
"category.heroes.abilities" // The translation key of the keybinding's category.
|
||||
)
|
||||
)
|
||||
|
||||
val pickItemKeyBinding by lazy {
|
||||
if (FabricLoader.getInstance().environmentType == EnvType.SERVER) null
|
||||
else MinecraftClient.getInstance().options.pickItemKey
|
||||
}
|
||||
|
||||
val heroKeyBindings by lazy { listOf(firstKeyBind, secondKeyBind, thirdKeyBind, fourthKeyBinding, fifthKeyBind) }
|
||||
//TODO das maybe als config damit wir lvie updaten können?
|
||||
val blacklist = mutableSetOf("key.voice_chat", "key.voice_chat_group", "key.hide_icons")
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
fun initClient() {
|
||||
KeyEvents.keyBindingOnPressEvent.listen { event ->
|
||||
val player = MinecraftClient.getInstance().player ?: return@listen
|
||||
if (player.getHero() == null) return@listen
|
||||
if (blacklist.contains(event.keybinding.translationKey)) {
|
||||
if (heroKeyBindings.any { it?.equals(event.keybinding) == true}) {
|
||||
event.isCancelled.set(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package gg.norisk.heroes.client.renderer
|
||||
|
||||
import gg.norisk.heroes.common.utils.toVec
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
import net.minecraft.client.render.VertexConsumerProvider
|
||||
import net.minecraft.client.render.WorldRenderer
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Box
|
||||
|
||||
|
||||
object BlockOutlineRenderer {
|
||||
|
||||
fun drawBlockBox(
|
||||
matrixStack: MatrixStack,
|
||||
vertexConsumerProvider: VertexConsumerProvider,
|
||||
blockPos: BlockPos,
|
||||
f: Float,
|
||||
g: Float,
|
||||
h: Float,
|
||||
i: Float
|
||||
) {
|
||||
drawBox(matrixStack, vertexConsumerProvider, blockPos, blockPos.add(1, 1, 1), f, g, h, i)
|
||||
}
|
||||
|
||||
fun drawBox(
|
||||
matrixStack: MatrixStack,
|
||||
vertexConsumerProvider: VertexConsumerProvider,
|
||||
blockPos: BlockPos,
|
||||
blockPos2: BlockPos,
|
||||
f: Float,
|
||||
g: Float,
|
||||
h: Float,
|
||||
i: Float
|
||||
) {
|
||||
val camera = MinecraftClient.getInstance().gameRenderer.camera
|
||||
if (camera.isReady) {
|
||||
val vec3d = camera.pos.negate()
|
||||
val box = Box.from(blockPos.toVec()).offset(vec3d)
|
||||
drawBox(matrixStack, vertexConsumerProvider, box, f, g, h, i)
|
||||
}
|
||||
}
|
||||
|
||||
fun drawBox(
|
||||
matrixStack: MatrixStack,
|
||||
vertexConsumerProvider: VertexConsumerProvider,
|
||||
box: Box,
|
||||
f: Float,
|
||||
g: Float,
|
||||
h: Float,
|
||||
i: Float
|
||||
) {
|
||||
drawBox(
|
||||
matrixStack,
|
||||
vertexConsumerProvider,
|
||||
box.minX,
|
||||
box.minY,
|
||||
box.minZ,
|
||||
box.maxX,
|
||||
box.maxY,
|
||||
box.maxZ,
|
||||
f,
|
||||
g,
|
||||
h,
|
||||
i
|
||||
)
|
||||
}
|
||||
|
||||
fun drawBox(
|
||||
matrixStack: MatrixStack,
|
||||
vertexConsumerProvider: VertexConsumerProvider,
|
||||
d: Double,
|
||||
e: Double,
|
||||
f: Double,
|
||||
g: Double,
|
||||
h: Double,
|
||||
i: Double,
|
||||
j: Float,
|
||||
k: Float,
|
||||
l: Float,
|
||||
m: Float
|
||||
) {
|
||||
val vertexConsumer = vertexConsumerProvider.getBuffer(RenderLayer.getDebugFilledBox())
|
||||
WorldRenderer.renderFilledBox(matrixStack, vertexConsumer, d, e, f, g, h, i, j, k, l, m)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package gg.norisk.heroes.client.renderer
|
||||
|
||||
import com.google.common.util.concurrent.AtomicDouble
|
||||
import gg.norisk.heroes.common.networking.CameraShakeEvent
|
||||
import gg.norisk.heroes.common.networking.cameraShakePacket
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.network.ClientPlayerEntity
|
||||
import net.minecraft.util.Util
|
||||
import kotlin.random.Random
|
||||
|
||||
//Credits to https://github.com/LoganDark/fabric-camera-shake
|
||||
object CameraShaker {
|
||||
var avgX: Double = 0.0
|
||||
var avgY: Double = 0.0
|
||||
private var smooth: Int = 3
|
||||
private var pastI: Int = 0
|
||||
private val pastX: DoubleArray = DoubleArray(smooth)
|
||||
private val pastY: DoubleArray = DoubleArray(smooth)
|
||||
private val events: MutableSet<CameraShakeEvent> = HashSet()
|
||||
private val providers: MutableSet<CameraShakeProvider> = HashSet()
|
||||
private var eventInceptions = Object2LongOpenHashMap<CameraShakeEvent>()
|
||||
|
||||
fun newFrame() {
|
||||
val magnitude: Double = getCameraShakeMagnitude(MinecraftClient.getInstance().player)
|
||||
|
||||
val x: Double = (Random.nextDouble() - .5) * magnitude
|
||||
val y: Double = (Random.nextDouble() - .5) * magnitude
|
||||
|
||||
pastX[pastI] = x
|
||||
pastY[pastI++] = y
|
||||
pastI %= smooth
|
||||
|
||||
calculateAvg()
|
||||
}
|
||||
|
||||
private fun calculateAvg() {
|
||||
avgX = .0
|
||||
avgY = .0
|
||||
for (i in 0 until smooth) {
|
||||
avgX += pastX[i]
|
||||
avgY += pastY[i]
|
||||
}
|
||||
avgX /= smooth.toDouble()
|
||||
avgY /= smooth.toDouble()
|
||||
}
|
||||
|
||||
interface CameraShakeProvider {
|
||||
fun getCameraShakeMagnitude(player: ClientPlayerEntity?): Double
|
||||
}
|
||||
|
||||
fun initClient() {
|
||||
cameraShakePacket.receiveOnClient { packet, context ->
|
||||
addEvent(packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCameraShakeMagnitude(player: ClientPlayerEntity?): Double {
|
||||
val magnitude = AtomicDouble()
|
||||
val eventIterator: MutableIterator<CameraShakeEvent> = events.iterator()
|
||||
eventIterator.forEachRemaining { event: CameraShakeEvent ->
|
||||
val t: Double
|
||||
try {
|
||||
t = getTime(event)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
eventIterator.remove()
|
||||
return@forEachRemaining
|
||||
}
|
||||
if (!event.isValid(t)) {
|
||||
eventIterator.remove()
|
||||
onEventRemoved(event)
|
||||
} else {
|
||||
magnitude.addAndGet(event.getCameraShakeMagnitude(t))
|
||||
}
|
||||
}
|
||||
for (provider in providers) {
|
||||
magnitude.addAndGet(provider.getCameraShakeMagnitude(player))
|
||||
}
|
||||
return magnitude.get()
|
||||
}
|
||||
|
||||
private fun <E : CameraShakeEvent> onEventRemoved(event: E) {
|
||||
eventInceptions.removeLong(event)
|
||||
}
|
||||
|
||||
private fun <E : CameraShakeEvent?> onEventAdded(event: E) {
|
||||
eventInceptions.put(event, Util.getMeasuringTimeNano())
|
||||
}
|
||||
|
||||
private fun getTime(event: CameraShakeEvent): Double {
|
||||
require(eventInceptions.containsKey(event)) { "Passed event was not added to this CameraShakeManager" }
|
||||
val then: Long = eventInceptions.getLong(event)
|
||||
val now = Util.getMeasuringTimeNano()
|
||||
val delta = now - then
|
||||
return delta.toDouble() / 1000000000.0
|
||||
}
|
||||
|
||||
fun <E : CameraShakeEvent> addEvent(event: E): E? {
|
||||
if (events.add(event)) {
|
||||
onEventAdded(event)
|
||||
return event
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package gg.norisk.heroes.client.renderer
|
||||
|
||||
import gg.norisk.heroes.common.hero.getHero
|
||||
import gg.norisk.heroes.common.hero.utils.ColorUtils
|
||||
import gg.norisk.ui.api.value.key
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
import net.minecraft.client.render.RenderTickCounter
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Colors
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import net.silkmc.silk.core.world.pos.Pos2i
|
||||
import java.awt.Color
|
||||
|
||||
object KeyBindHud {
|
||||
fun init() {
|
||||
HudRenderCallback.EVENT.register(::render)
|
||||
}
|
||||
|
||||
fun render(drawContext: DrawContext, tickCounter: RenderTickCounter) {
|
||||
if (MinecraftClient.getInstance().debugHud.shouldShowDebugHud()) return
|
||||
val player = MinecraftClient.getInstance().player ?: return
|
||||
val hero = player.getHero() ?: return
|
||||
val offset = 2
|
||||
val scale = 0.75f
|
||||
|
||||
drawContext.matrices.push()
|
||||
drawContext.matrices.scale(scale, scale, scale)
|
||||
|
||||
|
||||
hero.getUsableAbilities(player).map { ability ->
|
||||
val keyBind = ability.keyBind
|
||||
var text = literalText {
|
||||
text {
|
||||
//if (keyBind.condition != null) text("${keyBind.condition.hudText} + ")
|
||||
val deactivatedColor = 0x4A4A4A
|
||||
text(
|
||||
keyBind?.boundKeyLocalizedText ?: keyBind?.defaultKey?.localizedText
|
||||
?: ability.getCustomActivation()
|
||||
) {
|
||||
color = if (ability.hasCooldown(player)) {
|
||||
deactivatedColor
|
||||
} else {
|
||||
hero.color
|
||||
}
|
||||
}
|
||||
if (ability.condition != null) {
|
||||
text(" + ")
|
||||
text(Text.translatable("heroes.ability.condition.short.${ability.internalKey}")) {
|
||||
color = if (ability.condition?.invoke(player) == true || ability.condition == null) {
|
||||
hero.color
|
||||
} else {
|
||||
deactivatedColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text(" - ") { color = 0x919191 }
|
||||
text(ability.name)
|
||||
}
|
||||
|
||||
ability.getCooldown(player)?.let { cooldownInfo ->
|
||||
text = literalText {
|
||||
text(text)
|
||||
cooldownInfo.durationString?.let { extension ->
|
||||
text(" ")
|
||||
text(extension) { color = ColorUtils.contrast(0x248223) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text to ability
|
||||
}.sortedByDescending { MinecraftClient.getInstance().textRenderer.getWidth(it.first) }
|
||||
.forEachIndexed { index, (text, ability) ->
|
||||
val pos = Pos2i(5, 5 + (text.height + offset * 2) * index)
|
||||
drawContext.fill(
|
||||
RenderLayer.getGuiOverlay(),
|
||||
pos.x - offset,
|
||||
pos.z - offset,
|
||||
pos.x + text.width + offset,
|
||||
pos.z + text.height + offset,
|
||||
-1873784752
|
||||
)
|
||||
drawContext.drawText(
|
||||
MinecraftClient.getInstance().textRenderer, literalText {
|
||||
if (!ability.hasUnlocked(player)) {
|
||||
text(text.string)
|
||||
strikethrough = true
|
||||
color = Colors.LIGHT_GRAY
|
||||
} else {
|
||||
text(text)
|
||||
}
|
||||
}, pos.x, pos.z, 14737632, true
|
||||
)
|
||||
}
|
||||
|
||||
drawContext.matrices.pop()
|
||||
}
|
||||
|
||||
val Text.width
|
||||
get() = MinecraftClient.getInstance().textRenderer.getWidth(this)
|
||||
|
||||
val Text.height
|
||||
get() = MinecraftClient.getInstance().textRenderer.fontHeight
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package gg.norisk.heroes.client.renderer
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
object RenderUtils {
|
||||
fun renderOverlay(drawContext: DrawContext, identifier: Identifier, f: Float) {
|
||||
RenderSystem.disableDepthTest()
|
||||
RenderSystem.depthMask(false)
|
||||
RenderSystem.enableBlend()
|
||||
drawContext.setShaderColor(1.0f, 1.0f, 1.0f, f)
|
||||
drawContext.drawTexture(
|
||||
identifier,
|
||||
0,
|
||||
0,
|
||||
-90,
|
||||
0.0f,
|
||||
0.0f,
|
||||
drawContext.scaledWindowWidth,
|
||||
drawContext.scaledWindowHeight,
|
||||
drawContext.scaledWindowWidth,
|
||||
drawContext.scaledWindowHeight
|
||||
)
|
||||
RenderSystem.disableBlend()
|
||||
RenderSystem.depthMask(true)
|
||||
RenderSystem.enableDepthTest()
|
||||
drawContext.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package gg.norisk.heroes.client.renderer
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.getHero
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity
|
||||
import net.minecraft.client.texture.NativeImage
|
||||
import net.minecraft.client.texture.NativeImageBackedTexture
|
||||
import net.minecraft.util.Identifier
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
object SkinUtils {
|
||||
fun initClient() {
|
||||
if (!FabricLoader.getInstance().isDevelopmentEnvironment) return
|
||||
}
|
||||
|
||||
fun applyOverlay(baseSkinId: Identifier, overlayId: Identifier, hero: Hero) {
|
||||
val result = combineSkins(baseSkinId, overlayId)
|
||||
if (FabricLoader.getInstance().isDevelopmentEnvironment) {
|
||||
logger.info("Merged Skin $baseSkinId $overlayId: $result")
|
||||
val folder = File(FabricLoader.getInstance().configDir.toFile(), "aang").apply { mkdirs() }
|
||||
|
||||
//MinecraftClient.getInstance().textureManager.registerTexture()
|
||||
|
||||
result?.writeTo(File(folder, "${baseSkinId.path}_overlay.png"))
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
MinecraftClient.getInstance().submit {
|
||||
MinecraftClient.getInstance().textureManager.registerTexture(
|
||||
baseSkinId.toOverlaySkin(hero.internalKey.lowercase()),
|
||||
NativeImageBackedTexture(result)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Identifier.toOverlaySkin(id: String): Identifier {
|
||||
return Identifier.of(this.namespace, this.path + "_$id")
|
||||
}
|
||||
|
||||
fun redirectCombinedSkin(original: Identifier, player: AbstractClientPlayerEntity?): Identifier {
|
||||
val hero = player?.getHero()
|
||||
val skin = hero?.overlaySkin
|
||||
if (hero != null && skin != null) {
|
||||
val mergedSkin = original.toOverlaySkin(hero.internalKey.lowercase())
|
||||
if (MinecraftClient.getInstance().textureManager.getOrDefault(mergedSkin, null) != null) {
|
||||
return mergedSkin
|
||||
} else {
|
||||
applyOverlay(original, skin, hero)
|
||||
return original
|
||||
}
|
||||
} else {
|
||||
return original
|
||||
}
|
||||
}
|
||||
|
||||
fun extractTextureToNativeImage(glId: Int, width: Int, height: Int): NativeImage {
|
||||
// Erstelle ein NativeImage mit der passenden Breite, Höhe und Format
|
||||
val nativeImage = NativeImage(NativeImage.Format.RGBA, width, height, false)
|
||||
|
||||
// Binde die Textur mit der OpenGL-ID
|
||||
RenderSystem.bindTexture(glId)
|
||||
|
||||
// Lade die Textur-Pixel-Daten aus OpenGL in das NativeImage
|
||||
nativeImage.loadFromTextureImage(0, false) // i = 0 für Mipmap Level 0, bl = false für keine Alpha-Korrektur
|
||||
|
||||
return nativeImage // Das NativeImage enthält nun die Texturdaten
|
||||
}
|
||||
|
||||
/**
|
||||
* Kombiniert zwei Skins, wobei der zweite Skin alles ersetzt, was nicht transparent ist,
|
||||
* und gibt das resultierende Bild als NativeImage zurück.
|
||||
*
|
||||
* @param baseSkin Der Identifier für das Basisbild (der erste Skin).
|
||||
* @param overlaySkin Der Identifier für das Overlay-Bild (der zweite Skin).
|
||||
* @return Das kombinierte Bild als NativeImage.
|
||||
*/
|
||||
fun combineSkins(baseSkin: Identifier, overlaySkin: Identifier): NativeImage? {
|
||||
return try {
|
||||
// Lade die Basis- und Overlay-Skins als NativeImage
|
||||
val baseImageTexture = MinecraftClient.getInstance().textureManager.getOrDefault(baseSkin, null)
|
||||
|
||||
val overlayImage = loadSkinAsNativeImage(overlaySkin)
|
||||
|
||||
// Falls einer der beiden Skins nicht geladen werden konnte
|
||||
if (baseImageTexture == null || overlayImage == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
//ich hoffe das macht nichts kapuut lol
|
||||
val baseImage: NativeImage = extractTextureToNativeImage(baseImageTexture.glId, 64, 64)
|
||||
|
||||
// Überprüfen, ob beide Bilder die gleiche Größe haben
|
||||
if (baseImage.width != overlayImage.width || baseImage.height != overlayImage.height) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Erstelle ein neues NativeImage mit der gleichen Größe wie die Basis
|
||||
val combinedImage = NativeImage(baseImage.width, baseImage.height, true)
|
||||
|
||||
// Durchlaufen der Pixel und anwenden des Overlays auf das neue Bild
|
||||
for (x in 0 until baseImage.width) {
|
||||
for (y in 0 until baseImage.height) {
|
||||
val baseColor = baseImage.getColor(x, y)
|
||||
val overlayColor = overlayImage.getColor(x, y)
|
||||
val alpha = (overlayColor shr 24) and 0xFF // Alpha-Wert extrahieren
|
||||
|
||||
// Wenn das Overlay-Pixel nicht transparent ist, kopiere es ins kombinierte Bild
|
||||
if (alpha > 0) {
|
||||
combinedImage.setColor(x, y, overlayColor)
|
||||
} else {
|
||||
// Andernfalls kopiere das Basis-Pixel
|
||||
combinedImage.setColor(x, y, baseColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Freigeben der Basis- und Overlay-Bilder, da sie nicht mehr benötigt werden
|
||||
baseImage.close()
|
||||
overlayImage.close()
|
||||
|
||||
// Gib das kombinierte Bild zurück
|
||||
combinedImage
|
||||
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt einen Skin von einem Identifier als NativeImage.
|
||||
*
|
||||
* @param skinIdentifier Der Identifier des Skins.
|
||||
* @return Das NativeImage des Skins, oder null falls das Bild nicht geladen werden konnte.
|
||||
*/
|
||||
fun loadSkinAsNativeImage(skinIdentifier: Identifier): NativeImage? {
|
||||
val resourceManager = MinecraftClient.getInstance().resourceManager
|
||||
return try {
|
||||
val resource = resourceManager.getResource(skinIdentifier).getOrNull() ?: return null
|
||||
NativeImage.read(resource.inputStream)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package gg.norisk.heroes.client.renderer
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import gg.norisk.datatracker.entity.getSyncedData
|
||||
import gg.norisk.datatracker.entity.setSyncedData
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.CoreShaderRegistrationCallback
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gl.GlUniform
|
||||
import net.minecraft.client.gl.ShaderProgram
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.render.*
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.util.math.MathHelper
|
||||
import org.lwjgl.opengl.GL11
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
object Speedlines {
|
||||
var lerpedSpeed: Double = 0.0
|
||||
|
||||
lateinit var edge: GlUniform
|
||||
lateinit var speedlinesRenderTypeProgram: ShaderProgram
|
||||
|
||||
private const val SPEEDLINES_KEY = "speedlines"
|
||||
|
||||
var PlayerEntity.showSpeedlines: Boolean
|
||||
get() = this.getSyncedData<Boolean>(SPEEDLINES_KEY) == true
|
||||
set(value) {
|
||||
this.setSyncedData(SPEEDLINES_KEY, value)
|
||||
}
|
||||
|
||||
fun initClient() {
|
||||
CoreShaderRegistrationCallback.EVENT.register(CoreShaderRegistrationCallback { context: CoreShaderRegistrationCallback.RegistrationContext ->
|
||||
context.register(
|
||||
"speedlines".toId(), VertexFormats.POSITION
|
||||
) { shaderProgram: ShaderProgram ->
|
||||
speedlinesRenderTypeProgram = shaderProgram
|
||||
edge = shaderProgram.getUniform("Edge")!!
|
||||
}
|
||||
})
|
||||
|
||||
HudRenderCallback.EVENT.register(HudRenderCallback { context: DrawContext, tickCounter: RenderTickCounter ->
|
||||
val player = MinecraftClient.getInstance().player ?: return@HudRenderCallback
|
||||
val client = MinecraftClient.getInstance()
|
||||
if (player.showSpeedlines) {
|
||||
val width = client.getWindow().width.toFloat()
|
||||
val height = client.getWindow().height.toFloat()
|
||||
val delta = tickCounter.getTickDelta(false)
|
||||
lerpedSpeed =
|
||||
MathHelper.lerp((delta * 0.05f).toDouble(), lerpedSpeed, client.player!!.velocity.length())
|
||||
|
||||
var speed = max(0.0, (lerpedSpeed - 0.2) / 2f)
|
||||
speed = min(speed, 0.2)
|
||||
edge.set((0.5f - speed).toFloat())
|
||||
|
||||
val positionMatrix = context.matrices.peek().positionMatrix
|
||||
val tessellator = Tessellator.getInstance().begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION)
|
||||
tessellator.vertex(positionMatrix, 0f, height, 0f)
|
||||
tessellator.vertex(positionMatrix, 0f, 0f, 0f)
|
||||
tessellator.vertex(positionMatrix, width, 0f, 0f)
|
||||
tessellator.vertex(positionMatrix, width, height, 0f)
|
||||
RenderSystem.setShader { speedlinesRenderTypeProgram }
|
||||
setupRender()
|
||||
BufferRenderer.drawWithGlobalProgram(tessellator.end())
|
||||
endRender()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupRender() {
|
||||
RenderSystem.enableBlend()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
|
||||
RenderSystem.disableCull()
|
||||
RenderSystem.depthFunc(GL11.GL_ALWAYS)
|
||||
}
|
||||
|
||||
private fun endRender() {
|
||||
RenderSystem.disableBlend()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package gg.norisk.heroes.client.ui
|
||||
|
||||
import gg.norisk.heroes.client.ui.screen.HeroSelectorScreen
|
||||
import gg.norisk.heroes.client.ui.skilltree.HeroSelectorScreenV2
|
||||
import gg.norisk.heroes.client.ui.skilltree.SkillTreeScreen
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.HeroManager
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.heroes.common.networking.dto.HeroSelectorPacket
|
||||
import gg.norisk.utils.OldAnimation
|
||||
import me.cortex.nvidium.Nvidium
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.util.math.MathHelper
|
||||
import net.silkmc.silk.commands.clientCommand
|
||||
import net.silkmc.silk.core.kotlin.ticks
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
import org.joml.Matrix4f
|
||||
import org.joml.Quaternionf
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
|
||||
object OrthoCamera {
|
||||
val isEnabled get() = MinecraftClient.getInstance().currentScreen is HeroSelectorScreen || MinecraftClient.getInstance().currentScreen is HeroSelectorScreenV2
|
||||
var yawAnimation = OldAnimation(0f, 360f, 2.minutes.toJavaDuration())
|
||||
|
||||
fun initClient() {
|
||||
Networking.s2cHeroSelectorPacket.receiveOnClient { packet, context ->
|
||||
if (packet.isActive) {
|
||||
openHeroSelectorScreen(packet.heroes.mapNotNull { HeroManager.getHero(it) }, packet)
|
||||
} else {
|
||||
mcCoroutineTask(sync = true, client = true) {
|
||||
if (isEnabled) {
|
||||
if (FabricLoader.getInstance().isModLoaded("nvidium")) {
|
||||
Nvidium.FORCE_DISABLE = false
|
||||
MinecraftClient.getInstance()?.worldRenderer?.reload()
|
||||
}
|
||||
MinecraftClient.getInstance().setScreen(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clientCommand("heroselector") {
|
||||
runs {
|
||||
mcCoroutineTask(delay = 1.ticks, sync = true, client = true) {
|
||||
MinecraftClient.getInstance().setScreen(HeroSelectorScreen(HeroManager.registeredHeroes.values.toList(), false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clientCommand("skilltree") {
|
||||
runs {
|
||||
mcCoroutineTask(sync = true, client = true, delay = 1.ticks) {
|
||||
MinecraftClient.getInstance().setScreen(SkillTreeScreen())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openHeroSelectorScreen(
|
||||
heroes: List<Hero> = HeroManager.registeredHeroes.values.toList(),
|
||||
packet: HeroSelectorPacket
|
||||
) {
|
||||
mcCoroutineTask(delay = 1.ticks, sync = true, client = true) {
|
||||
MinecraftClient.getInstance().setScreen(HeroSelectorScreenV2(heroes, packet.isKitEditorEnabled))
|
||||
}
|
||||
}
|
||||
|
||||
fun createOrthoMatrix(delta: Float, minScale: Float): Matrix4f {
|
||||
val client: MinecraftClient = MinecraftClient.getInstance()
|
||||
val scale = 100f
|
||||
val width = max(minScale, scale * client.window.framebufferWidth / client.window.framebufferHeight)
|
||||
val height = max(minScale, scale)
|
||||
return Matrix4f().setOrtho(
|
||||
-width, width,
|
||||
-height, height,
|
||||
-1000.0F, 1000.0f
|
||||
)
|
||||
}
|
||||
|
||||
fun handlePitch(quaternion: Quaternionf, tickDelta: Float): Float {
|
||||
return 30f * MathHelper.RADIANS_PER_DEGREE
|
||||
}
|
||||
|
||||
fun handleYaw(quaternion: Quaternionf, tickDelta: Float): Float {
|
||||
if (yawAnimation.isDone) {
|
||||
yawAnimation.reset()
|
||||
}
|
||||
return yawAnimation.get() * MathHelper.RADIANS_PER_DEGREE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package gg.norisk.heroes.client.ui.components
|
||||
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.ui.components.ScalableButtonComponent
|
||||
import io.wispforest.owo.ui.component.ButtonComponent
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.OwoUIDrawContext
|
||||
import io.wispforest.owo.ui.core.Sizing
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import java.util.*
|
||||
|
||||
class AbilitiesComponent(
|
||||
val hero: Hero,
|
||||
val uuid: UUID = MinecraftClient.getInstance().player!!.uuid,
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
|
||||
val mainWrapper = Containers.horizontalFlow(Sizing.content(), Sizing.content())
|
||||
val buttonWrapper = Containers.horizontalFlow(Sizing.content(), Sizing.content())
|
||||
|
||||
init {
|
||||
for (ability in hero.abilities.values) {
|
||||
buttonWrapper.child(ScalableButtonComponent(ability.name.literal, 0.8f) {
|
||||
onAbilityButtonClick(it, ability)
|
||||
})
|
||||
}
|
||||
|
||||
child(buttonWrapper)
|
||||
child(mainWrapper)
|
||||
buttonWrapper.children().filterIsInstance<ButtonComponent>().first().onPress()
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
for (child in mainWrapper.children()) {
|
||||
val width = buttonWrapper.fullSize().width
|
||||
child.horizontalSizing(Sizing.fixed(width))
|
||||
}
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
private fun onAbilityButtonClick(it: ButtonComponent, ability: AbstractAbility<*>) {
|
||||
buttonWrapper.children().filterIsInstance<ButtonComponent>().forEach { button ->
|
||||
button.active(true)
|
||||
}
|
||||
it.active(false)
|
||||
mainWrapper.clearChildren()
|
||||
mainWrapper.child(AbilityComponent(ability))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package gg.norisk.heroes.client.ui.components
|
||||
|
||||
import gg.norisk.heroes.common.ability.CooldownProperty
|
||||
import gg.norisk.heroes.common.ability.LevelInformation
|
||||
import gg.norisk.heroes.common.ability.PlayerProperty
|
||||
import gg.norisk.heroes.common.ability.SingleUseProperty
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.common.hero.ability.SkillPropertyPacket
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.ui.components.ScalableButtonComponent
|
||||
import gg.norisk.ui.components.ScalableLabelComponent
|
||||
import io.wispforest.owo.ui.component.ButtonComponent
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
import net.minecraft.text.Text
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import java.awt.Color
|
||||
import java.util.*
|
||||
|
||||
class AbilityComponent(
|
||||
val ability: AbstractAbility<*>,
|
||||
val uuid: UUID = MinecraftClient.getInstance().player!!.uuid,
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.HORIZONTAL) {
|
||||
|
||||
val abilityDescription = ScalableLabelComponent(ability.description, 0.5f).apply {
|
||||
shadow(true)
|
||||
}
|
||||
val leftWrapper = Containers.verticalFlow(Sizing.fill(45), Sizing.content())
|
||||
|
||||
init {
|
||||
surface(Surface.VANILLA_TRANSLUCENT)
|
||||
padding(Insets.of(5))
|
||||
leftWrapper.apply {
|
||||
child(ScalableLabelComponent(literalText {
|
||||
text(ability.name.literal)
|
||||
underline = true
|
||||
bold = true
|
||||
}).apply {
|
||||
shadow(true)
|
||||
})
|
||||
child(abilityDescription)
|
||||
gap(3)
|
||||
}
|
||||
child(leftWrapper)
|
||||
|
||||
gap(2)
|
||||
|
||||
horizontalAlignment(HorizontalAlignment.LEFT)
|
||||
|
||||
child(Containers.verticalFlow(Sizing.fill(55), Sizing.content()).apply {
|
||||
//debug()
|
||||
gap(3)
|
||||
for (property in ability.getAllProperties()) {
|
||||
if (property is SingleUseProperty) continue
|
||||
child(PropertyComponent(property))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
abilityDescription.maxWidth(leftWrapper.fullSize().width * 2)
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
private inner class PropertyComponent(
|
||||
val property: PlayerProperty<*>,
|
||||
horizontalSizing: Sizing = Sizing.fill(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
val progressBar = Containers.horizontalFlow(Sizing.fill(83), Sizing.fixed(3))
|
||||
var progressColor: Color = Color.GREEN
|
||||
val title = ScalableLabelComponent(literalText {
|
||||
text(Text.translatable(property.name)) {
|
||||
bold = true
|
||||
}
|
||||
text(":")
|
||||
}, 0.75f).apply {
|
||||
shadow(true)
|
||||
}
|
||||
val valueLabel = ScalableLabelComponent(getValueText(property.getValue(uuid)), 0.75f).apply {
|
||||
shadow(true)
|
||||
}
|
||||
val levelLabel = ScalableLabelComponent(getLevelText(property.getLevelInfo(uuid)), 0.5f).apply {
|
||||
shadow(true)
|
||||
}
|
||||
val skillButton = ScalableButtonComponent("+".literal, 0.75f, ::onSkill).apply {
|
||||
sizing(Sizing.fixed(15))
|
||||
}
|
||||
|
||||
init {
|
||||
//debug()
|
||||
child(Containers.horizontalFlow(Sizing.content(), Sizing.content()).apply {
|
||||
child(title)
|
||||
child(valueLabel)
|
||||
gap(5)
|
||||
tooltip(upgradeTooltip())
|
||||
//debug()
|
||||
})
|
||||
child(Containers.horizontalFlow(Sizing.content(), Sizing.content()).apply {
|
||||
child(Containers.verticalFlow(Sizing.content(), Sizing.content()).apply {
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
child(levelLabel)
|
||||
child(progressBar)
|
||||
gap(3)
|
||||
})
|
||||
child(skillButton)
|
||||
alignment(HorizontalAlignment.LEFT, VerticalAlignment.CENTER)
|
||||
gap(5)
|
||||
//debug()
|
||||
})
|
||||
gap(3)
|
||||
//alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP)
|
||||
}
|
||||
|
||||
private fun upgradeTooltip(): Text {
|
||||
return literalText {
|
||||
text("Upgrade") {
|
||||
bold = true
|
||||
}
|
||||
repeat(property.maxLevel + 1) { level ->
|
||||
newLine()
|
||||
text("Lvl $level -> ")
|
||||
text(getValueText(property.getValue(level)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun progressTooltip(levelInformation: LevelInformation): Text {
|
||||
return literalText {
|
||||
text((levelInformation.xpNextLevel - levelInformation.xpTillNextLevel).toString())
|
||||
text("/")
|
||||
text(levelInformation.xpNextLevel.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSkill(buttonComponent: ButtonComponent) {
|
||||
mcCoroutineTask(client = true, sync = true) {
|
||||
Networking.c2sSkillProperty.send(
|
||||
SkillPropertyPacket(
|
||||
ability.hero.internalKey, ability.internalKey, property.internalKey
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLevelText(levelInformation: LevelInformation): Text {
|
||||
return literalText {
|
||||
text("Lvl ")
|
||||
text(levelInformation.currentLevel.toString())
|
||||
text("/")
|
||||
text(levelInformation.maxLevel.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> getValueText(value: T): Text {
|
||||
return literalText {
|
||||
text(value.toString())
|
||||
if (property is CooldownProperty) {
|
||||
text("s")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
val levelInfo = property.getLevelInfo(MinecraftClient.getInstance().session.uuidOrNull)
|
||||
valueLabel.text(getValueText(property.getValue(uuid)))
|
||||
levelLabel.text(getLevelText(levelInfo))
|
||||
progressBar.tooltip(progressTooltip(levelInfo))
|
||||
levelLabel.tooltip(progressTooltip(levelInfo))
|
||||
val currentPercentage = levelInfo.percentageTillNextLevel
|
||||
progressBar.surface { surfaceContext, component ->
|
||||
val barWidth = progressBar.width() * (currentPercentage / 100.0)
|
||||
|
||||
surfaceContext.fill(
|
||||
RenderLayer.getGui(),
|
||||
component.x(),
|
||||
component.y(),
|
||||
(component.x() + barWidth).toInt(),
|
||||
component.y() + component.height(),
|
||||
0,
|
||||
progressColor.rgb
|
||||
)
|
||||
|
||||
surfaceContext.fill(
|
||||
RenderLayer.getGui(),
|
||||
component.x() + barWidth.toInt(),
|
||||
component.y(),
|
||||
(component.x() + progressBar.width()),
|
||||
component.y() + component.height(),
|
||||
0,
|
||||
progressColor.darker().darker().withAlpha(200).rgb
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Color.withAlpha(alpha: Int): Color {
|
||||
return Color(red, green, blue, alpha)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package gg.norisk.heroes.client.ui.components
|
||||
|
||||
|
||||
import gg.norisk.heroes.client.ui.screen.HeroSelectorScreen
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.ui.components.ScalableButtonComponent
|
||||
import io.wispforest.owo.ui.component.ButtonComponent
|
||||
import io.wispforest.owo.ui.component.Components
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import io.wispforest.owo.ui.util.UISounds
|
||||
import net.silkmc.silk.core.text.literal
|
||||
|
||||
class HeroListComponent(
|
||||
val heroes: List<Hero>,
|
||||
val heroSelectorScreen: HeroSelectorScreen,
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(
|
||||
horizontalSizing, verticalSizing, Algorithm.VERTICAL
|
||||
) {
|
||||
val lockInButton = ScalableButtonComponent("LOCK IN".literal, 1f, ::onLockInButton).apply {
|
||||
horizontalSizing(Sizing.fixed(100))
|
||||
}
|
||||
val editorButton = ScalableButtonComponent("EDITOR".literal, 1f, ::onEditorButton).apply {
|
||||
horizontalSizing(Sizing.fixed(100))
|
||||
}
|
||||
|
||||
init {
|
||||
gap(5)
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
|
||||
val grid = Containers.grid(Sizing.content(), Sizing.content(), 1, heroes.size)
|
||||
for ((index, hero) in heroes.withIndex()) {
|
||||
grid.child(HeroHeadComponent(hero), 0, index)
|
||||
}
|
||||
child(grid)
|
||||
grid.surface(Surface.VANILLA_TRANSLUCENT)
|
||||
grid.padding(Insets.of(5))
|
||||
|
||||
child(lockInButton)
|
||||
if (heroSelectorScreen.isKitEditorEnabled) {
|
||||
child(editorButton)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLockInButton(buttonComponent: ButtonComponent) {
|
||||
Networking.c2sHeroSelectorPacket.send(heroSelectorScreen.hero!!.internalKey)
|
||||
}
|
||||
|
||||
private fun onEditorButton(buttonComponent: ButtonComponent) {
|
||||
Networking.c2sKitEditorRequestPacket.send(Unit)
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
lockInButton.active(heroSelectorScreen.hero != null)
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
inner class HeroHeadComponent(
|
||||
val hero: Hero, horizontalSizing: Sizing = Sizing.content(), verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(
|
||||
horizontalSizing, verticalSizing, Algorithm.HORIZONTAL
|
||||
) {
|
||||
init {
|
||||
val l = 8
|
||||
val m = 8
|
||||
val heroHead = Components.texture(hero.icon, 0, 0, 64, 64, 64, 64)
|
||||
//val heroHead2 = Components.texture(hero.skin, 40, l, 8, m, 64, 64)
|
||||
//OVERLAY
|
||||
heroHead.sizing(Sizing.fixed(32))
|
||||
child(heroHead)
|
||||
|
||||
margins(Insets.of(2))
|
||||
padding(Insets.of(2))
|
||||
|
||||
surface(Surface.outline(java.awt.Color.WHITE.darker().rgb))
|
||||
|
||||
mouseDown().subscribe { _, _, _ ->
|
||||
UISounds.playButtonSound()
|
||||
heroSelectorScreen.hero = hero
|
||||
return@subscribe true
|
||||
}
|
||||
mouseEnter().subscribe {
|
||||
surface(Surface.outline(Color.WHITE.argb()))
|
||||
}
|
||||
mouseLeave().subscribe {
|
||||
surface(Surface.outline(java.awt.Color.WHITE.darker().rgb))
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package gg.norisk.heroes.client.ui.components
|
||||
|
||||
|
||||
import gg.norisk.heroes.client.ui.skilltree.HeroSelectorScreenV2
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.ui.components.ScalableButtonComponent
|
||||
import io.wispforest.owo.ui.component.ButtonComponent
|
||||
import io.wispforest.owo.ui.component.Components
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.container.OverlayContainer
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import io.wispforest.owo.ui.util.UISounds
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.silkmc.silk.core.text.literal
|
||||
|
||||
class HeroListComponentV2(
|
||||
val heroes: List<Hero>,
|
||||
val heroSelectorScreen: HeroSelectorScreenV2,
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(
|
||||
horizontalSizing, verticalSizing, Algorithm.VERTICAL
|
||||
) {
|
||||
val lockInButton = ScalableButtonComponent("LOCK IN".literal, 1f, ::onLockInButton).apply {
|
||||
horizontalSizing(Sizing.fixed(100))
|
||||
}
|
||||
val editorButton = ScalableButtonComponent("EDITOR".literal, 1f, ::onEditorButton).apply {
|
||||
horizontalSizing(Sizing.fixed(48))
|
||||
}
|
||||
val lobbyButton = ScalableButtonComponent("SPEC".literal, 1f, ::onLobbyButton).apply {
|
||||
horizontalSizing(Sizing.fixed(48))
|
||||
}
|
||||
|
||||
init {
|
||||
gap(5)
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
|
||||
val grid = Containers.grid(Sizing.content(), Sizing.content(), 1, heroes.size)
|
||||
for ((index, hero) in heroes.withIndex()) {
|
||||
grid.child(HeroHeadComponent(hero), 0, index)
|
||||
}
|
||||
child(grid)
|
||||
grid.surface(Surface.VANILLA_TRANSLUCENT)
|
||||
grid.padding(Insets.of(5))
|
||||
|
||||
val buttonWrapper = ButtonWrapper()
|
||||
child(buttonWrapper)
|
||||
|
||||
buttonWrapper.child(lockInButton)
|
||||
if (heroSelectorScreen.isKitEditorEnabled) {
|
||||
buttonWrapper.child(Containers.horizontalFlow(Sizing.content(), Sizing.content()).apply {
|
||||
child(editorButton)
|
||||
child(lobbyButton)
|
||||
gap(5)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ButtonWrapper(
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(
|
||||
horizontalSizing, verticalSizing, Algorithm.VERTICAL
|
||||
) {
|
||||
init {
|
||||
gap(5)
|
||||
}
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
if (heroSelectorScreen.adapter.children().any { it is OverlayContainer<*> }) {
|
||||
return
|
||||
}
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLockInButton(buttonComponent: ButtonComponent) {
|
||||
Networking.c2sHeroSelectorPacket.send(heroSelectorScreen.hero!!.internalKey)
|
||||
}
|
||||
|
||||
private fun onEditorButton(buttonComponent: ButtonComponent) {
|
||||
Networking.c2sKitEditorRequestPacket.send(Unit)
|
||||
}
|
||||
|
||||
private fun onLobbyButton(buttonComponent: ButtonComponent) {
|
||||
heroSelectorScreen.close()
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
lockInButton.active(heroSelectorScreen.hero != null)
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
inner class HeroHeadComponent(
|
||||
val hero: Hero, horizontalSizing: Sizing = Sizing.content(), verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(
|
||||
horizontalSizing, verticalSizing, Algorithm.HORIZONTAL
|
||||
) {
|
||||
init {
|
||||
val l = 8
|
||||
val m = 8
|
||||
val heroHead = Components.texture(hero.icon, 0, 0, 64, 64, 64, 64)
|
||||
//val heroHead2 = Components.texture(hero.skin, 40, l, 8, m, 64, 64)
|
||||
//OVERLAY
|
||||
heroHead.sizing(Sizing.fixed(32))
|
||||
child(heroHead)
|
||||
|
||||
margins(Insets.of(2))
|
||||
padding(Insets.of(2))
|
||||
|
||||
surface(Surface.outline(java.awt.Color.WHITE.darker().rgb))
|
||||
|
||||
mouseDown().subscribe { _, _, _ ->
|
||||
UISounds.playButtonSound()
|
||||
heroSelectorScreen.hero = hero
|
||||
return@subscribe true
|
||||
}
|
||||
mouseEnter().subscribe {
|
||||
surface(Surface.outline(Color.WHITE.argb()))
|
||||
}
|
||||
mouseLeave().subscribe {
|
||||
surface(Surface.outline(java.awt.Color.WHITE.darker().rgb))
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package gg.norisk.heroes.client.ui.screen
|
||||
|
||||
|
||||
//import me.cortex.nvidium.Nvidium
|
||||
import gg.norisk.heroes.client.ui.components.AbilitiesComponent
|
||||
import gg.norisk.heroes.client.ui.components.HeroListComponent
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.ui.components.ScalableLabelComponent
|
||||
import io.wispforest.owo.ui.base.BaseOwoScreen
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import me.cortex.nvidium.Nvidium
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
|
||||
class HeroSelectorScreen(val heroes: List<Hero>, val isKitEditorEnabled: Boolean = false) :
|
||||
BaseOwoScreen<FlowLayout>() {
|
||||
var hero: Hero? = null
|
||||
set(value) {
|
||||
heroInfoComponent?.remove()
|
||||
centerLabel.remove()
|
||||
if (field != value) {
|
||||
field = value
|
||||
if (value != null) {
|
||||
heroInfoComponent = heroAbility(value)
|
||||
this.uiAdapter.rootComponent.child(heroInfoComponent)
|
||||
}
|
||||
} else {
|
||||
field = null
|
||||
this.uiAdapter.rootComponent.child(centerLabel)
|
||||
}
|
||||
}
|
||||
var heroInfoComponent: FlowLayout? = null
|
||||
var centerLabel: FlowLayout = Containers.horizontalFlow(Sizing.fill(), Sizing.content()).apply {
|
||||
child(ScalableLabelComponent(literalText {
|
||||
text("CHOOSE YOUR HERO")
|
||||
}, 3f).apply {
|
||||
shadow(true)
|
||||
})
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
positioning(Positioning.relative(30, 50))
|
||||
}
|
||||
|
||||
override fun createAdapter(): OwoUIAdapter<FlowLayout> {
|
||||
return OwoUIAdapter.create(this, Containers::verticalFlow);
|
||||
}
|
||||
|
||||
private fun heroAbility(hero: Hero): FlowLayout {
|
||||
val container = Containers.verticalFlow(Sizing.content(), Sizing.content())
|
||||
.apply { positioning(Positioning.relative(0, 30)) }
|
||||
container.child(Containers.verticalFlow(Sizing.content(), Sizing.content()).apply {
|
||||
child(ScalableLabelComponent(literalText {
|
||||
text(hero.name.uppercase())
|
||||
bold = true
|
||||
//color = Color.YELLOW.rgb
|
||||
}, 2f).apply {
|
||||
this.margins(Insets.of(3))
|
||||
})
|
||||
child(XpLabel(1f).apply {
|
||||
this.margins(Insets.of(3))
|
||||
})
|
||||
gap(3)
|
||||
})
|
||||
container.gap(5)
|
||||
container.child(AbilitiesComponent(hero))
|
||||
return container
|
||||
}
|
||||
|
||||
private class XpLabel(scale: Float = 1f) : ScalableLabelComponent("".literal, scale) {
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
text(literalText {
|
||||
text("XP: ")
|
||||
text((MinecraftClient.getInstance().player?.ffaPlayer?.xp ?: 0).toString())
|
||||
})
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
if (FabricLoader.getInstance().isModLoaded("nvidium")) {
|
||||
Nvidium.FORCE_DISABLE = false
|
||||
this.client?.worldRenderer?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(root: FlowLayout) {
|
||||
val heroList = HeroListComponent(heroes, this)
|
||||
heroList.positioning(Positioning.relative(50, 90))
|
||||
|
||||
root.child(heroList)
|
||||
if (hero == null) {
|
||||
root.child(centerLabel)
|
||||
}
|
||||
|
||||
if (FabricLoader.getInstance().isModLoaded("nvidium")) {
|
||||
Nvidium.FORCE_DISABLE = true
|
||||
this.client?.worldRenderer?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldPause(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc(): Boolean {
|
||||
return FabricLoader.getInstance().isDevelopmentEnvironment
|
||||
}
|
||||
}
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.heroes.common.ui.ScrollContainerV2
|
||||
import gg.norisk.ui.components.ScalableLabelComponent
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import io.wispforest.owo.ui.util.NinePatchTexture
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.render.BufferRenderer
|
||||
import net.minecraft.client.render.GameRenderer
|
||||
import net.minecraft.client.render.Tessellator
|
||||
import net.minecraft.client.render.VertexFormat.DrawMode
|
||||
import net.minecraft.client.render.VertexFormats
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.sound.SoundEvents
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Colors
|
||||
import net.minecraft.util.Identifier
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import org.joml.Matrix4f
|
||||
import org.joml.Vector2d
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
|
||||
class AbilitySkillTreeComponent(
|
||||
val ability: AbstractAbility<*>,
|
||||
horizontalSizing: Sizing = Sizing.fill(50),
|
||||
verticalSizing: Sizing = Sizing.fill(60),
|
||||
val player: PlayerEntity = MinecraftClient.getInstance().player!!
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
val shadow = "textures/gui/shadow.png".toId()
|
||||
val scrollChild = ScrollChild()
|
||||
val scroll = ScrollContainerV2(Sizing.fill(), Sizing.fill(80), scrollChild)
|
||||
var isHovered = false
|
||||
|
||||
init {
|
||||
surface(Surface.PANEL)
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP)
|
||||
padding(Insets.of(5).withLeft(8).withRight(8))
|
||||
|
||||
//val scroll = Containers.horizontalScroll(Sizing.fill(25), Sizing.fill(60), scrollChild)
|
||||
child(Containers.horizontalFlow(Sizing.fill(), Sizing.content(1)).apply {
|
||||
child(ScalableLabelComponent(literalText {
|
||||
text(ability.name.literal)
|
||||
color = Colors.GRAY
|
||||
}))
|
||||
})
|
||||
child(Containers.horizontalFlow(Sizing.fill(), Sizing.content(1)).apply {
|
||||
child(ScalableLabelComponent(literalText {
|
||||
text(ability.description)
|
||||
color = Colors.GRAY
|
||||
},0.75f).apply {
|
||||
maxWidth(350)
|
||||
})
|
||||
})
|
||||
child(scroll)
|
||||
scroll.alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
scroll.padding(Insets.of(2))
|
||||
scrollChild.surface(Surface { context, component ->
|
||||
val divider = 240f
|
||||
RenderSystem.setShaderColor(divider / 255f, divider / 255f, divider / 255f, 1f);
|
||||
context.drawTexture(
|
||||
ability.getBackgroundTexture(),
|
||||
component.x(),
|
||||
component.y(),
|
||||
0f,
|
||||
0f,
|
||||
component.width(),
|
||||
component.height(),
|
||||
16,
|
||||
16
|
||||
);
|
||||
|
||||
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
|
||||
})
|
||||
|
||||
scrollChild.alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP)
|
||||
scrollChild.child(getComponent(SkillTreeUtils.toSkillTree(ability)))
|
||||
child(XPComponent())
|
||||
}
|
||||
|
||||
inner class XPComponent(
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content(),
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
|
||||
init {
|
||||
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
val textRenderer = MinecraftClient.getInstance().textRenderer
|
||||
val string = "" + player.ffaPlayer.xp
|
||||
|
||||
val x = this.x() - textRenderer.getWidth(string) / 2
|
||||
val y = this.y() + textRenderer.fontHeight / 2
|
||||
|
||||
context.drawText(textRenderer, string, x + 1, y, 0, false)
|
||||
context.drawText(textRenderer, string, x - 1, y, 0, false)
|
||||
context.drawText(textRenderer, string, x, y + 1, 0, false)
|
||||
context.drawText(textRenderer, string, x, y - 1, 0, false)
|
||||
context.drawText(textRenderer, string, x, y, 8453920, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inner class ScrollChild(
|
||||
horizontalSizing: Sizing = Sizing.fixed(500),
|
||||
verticalSizing: Sizing = Sizing.fixed(500),
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
override fun drawChildren(
|
||||
context: OwoUIDrawContext,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
partialTicks: Float,
|
||||
delta: Float,
|
||||
children: MutableList<out Component>
|
||||
) {
|
||||
super.drawChildren(context, mouseX, mouseY, partialTicks, delta, children)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getComponent(node: TreeNode<ISkill>): Component {
|
||||
val container = Wrapper(node, Sizing.content(), Sizing.content(), Algorithm.VERTICAL)
|
||||
container.alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP)
|
||||
|
||||
// container.surface(Surface.outline(colors.random().argb()))
|
||||
container.padding(Insets.of(1))
|
||||
//container.debug()
|
||||
|
||||
if (node.children.isNotEmpty()) {
|
||||
|
||||
// Kinder horizontal anordnen
|
||||
val childContainer = Containers.horizontalFlow(Sizing.content(), Sizing.content())
|
||||
childContainer.id("child-node")
|
||||
childContainer.alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP)
|
||||
childContainer.padding(Insets.of(2)) // Fügt Abstand zwischen den Kindknoten hinzu
|
||||
|
||||
|
||||
node.children.forEach {
|
||||
childContainer.child(getComponent(it))
|
||||
}
|
||||
|
||||
container.child(childContainer)
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
|
||||
RenderSystem.enableDepthTest()
|
||||
|
||||
RenderSystem.enableBlend()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
val matrices = context.matrices
|
||||
matrices.push()
|
||||
matrices.translate(0f, 0f, 5.0f)
|
||||
context.drawTexture(
|
||||
shadow,
|
||||
scroll.x(),
|
||||
scroll.y(),
|
||||
0f,
|
||||
0f,
|
||||
scroll.width(),
|
||||
scroll.height(),
|
||||
scroll.width(),
|
||||
scroll.height(),
|
||||
)
|
||||
RenderSystem.disableBlend()
|
||||
matrices.pop()
|
||||
}
|
||||
|
||||
inner class Wrapper(
|
||||
val node: TreeNode<ISkill>,
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content(),
|
||||
algorithm: Algorithm
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, algorithm) {
|
||||
val label = ScalableLabelComponent(node.value.title()).apply {
|
||||
shadow(true)
|
||||
}
|
||||
var isHoveredChild = false
|
||||
var isVisible = false
|
||||
val box: FlowLayout = Containers.verticalFlow(Sizing.fixed(26), Sizing.fixed(26)).apply {
|
||||
if (node.value.isUnlocked(MinecraftClient.getInstance().player!!)) {
|
||||
} else {
|
||||
}
|
||||
//child(label)
|
||||
cursorStyle(CursorStyle.POINTER)
|
||||
mouseEnter().subscribe {
|
||||
isHovered = true
|
||||
isHoveredChild = true
|
||||
}
|
||||
mouseLeave().subscribe {
|
||||
isHovered = false
|
||||
isHoveredChild = false
|
||||
}
|
||||
mouseDown().subscribe { _, _, _ ->
|
||||
if (isVisible) {
|
||||
MinecraftClient.getInstance().soundManager.play(
|
||||
PositionedSoundInstance.master(
|
||||
SoundEvents.BLOCK_AMETHYST_BLOCK_HIT,
|
||||
1.0f, 1f
|
||||
)
|
||||
)
|
||||
node.value.skill()
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
child(node.value.icon())
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
margins(Insets.top(5))
|
||||
child(label)
|
||||
child(box)
|
||||
}
|
||||
|
||||
fun anchorPoint(): Component {
|
||||
return box
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
|
||||
if (isVisible) {
|
||||
box.tooltip(node.value.tooltip(player))
|
||||
} else {
|
||||
box.tooltip(Text.empty())
|
||||
}
|
||||
|
||||
val player = MinecraftClient.getInstance().player!!
|
||||
var drawLines = true
|
||||
if (node.value.isParentUnlocked(player)) {
|
||||
if (node.value.isUnlocked(player)) {
|
||||
if (node.value.parent() == null) {
|
||||
box.surface { context2, component ->
|
||||
val root = "textures/gui/root_panel.png".toId()
|
||||
context2.drawTexture(
|
||||
root,
|
||||
component.x(),
|
||||
component.y(),
|
||||
0f,
|
||||
0f,
|
||||
26,
|
||||
26,
|
||||
26,
|
||||
26
|
||||
)
|
||||
}
|
||||
} else {
|
||||
box.surface { owoUIDrawContext, parentComponent ->
|
||||
if (node.value.isLast()) {
|
||||
val root =
|
||||
Identifier.ofVanilla("textures/gui/sprites/advancements/goal_frame_obtained.png")
|
||||
owoUIDrawContext.drawTexture(
|
||||
root,
|
||||
parentComponent.x(),
|
||||
parentComponent.y(),
|
||||
0f,
|
||||
0f,
|
||||
26,
|
||||
26,
|
||||
26,
|
||||
26
|
||||
)
|
||||
} else {
|
||||
NinePatchTexture.draw("unlocked".toId(), context, parentComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (node.value.progress(player) == 1.0) {
|
||||
box.surface { context2, component ->
|
||||
val root = Identifier.ofVanilla("textures/gui/sprites/advancements/goal_frame_obtained.png")
|
||||
context2.drawTexture(
|
||||
root,
|
||||
component.x(),
|
||||
component.y(),
|
||||
0f,
|
||||
0f,
|
||||
26,
|
||||
26,
|
||||
26,
|
||||
26
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (node.value.isLast()) {
|
||||
box.surface { owoUIDrawContext, parentComponent ->
|
||||
val root =
|
||||
Identifier.ofVanilla("textures/gui/sprites/advancements/goal_frame_unobtained.png")
|
||||
owoUIDrawContext.drawTexture(
|
||||
root,
|
||||
parentComponent.x(),
|
||||
parentComponent.y(),
|
||||
0f,
|
||||
0f,
|
||||
26,
|
||||
26,
|
||||
26,
|
||||
26
|
||||
)
|
||||
}
|
||||
} else {
|
||||
box.surface(Surface.PANEL)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!node.value.isUnlocked(player) && node.value.parent()?.isParentUnlocked(player) == true) {
|
||||
if (node.value.isLast()) {
|
||||
box.surface { owoUIDrawContext, parentComponent ->
|
||||
val root = "textures/gui/goal_frame_dark.png".toId()
|
||||
owoUIDrawContext.drawTexture(
|
||||
root,
|
||||
parentComponent.x(),
|
||||
parentComponent.y(),
|
||||
0f,
|
||||
0f,
|
||||
26,
|
||||
26,
|
||||
26,
|
||||
26
|
||||
)
|
||||
}
|
||||
} else {
|
||||
box.surface(Surface.DARK_PANEL)
|
||||
}
|
||||
drawLines = false
|
||||
} else {
|
||||
isVisible = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isVisible = true
|
||||
|
||||
|
||||
if (!node.value.isUnlocked(MinecraftClient.getInstance().player!!)) {
|
||||
// return
|
||||
}
|
||||
val childContainer = childById(FlowLayout::class.java, "child-node") as? FlowLayout? ?: return super.draw(
|
||||
context,
|
||||
mouseX,
|
||||
mouseY,
|
||||
partialTicks,
|
||||
delta
|
||||
)
|
||||
if (childContainer.children().isEmpty()) return super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
|
||||
val parentX = anchorPoint().x() + anchorPoint().width() / 2
|
||||
val parentY = anchorPoint().y() + anchorPoint().height() - 5
|
||||
|
||||
// Berechne die mittlere Y-Position für die horizontale Linie
|
||||
val lowestChildY =
|
||||
childContainer.children().minOfOrNull { (it as? Wrapper)?.anchorPoint()?.y() ?: Int.MAX_VALUE }
|
||||
?: return super.draw(context, mouseX, mouseX, partialTicks, delta)
|
||||
val midY = parentY + (lowestChildY - parentY) / 2
|
||||
val whiteLineThickness = 2.0
|
||||
val grayedColor = Color.ofRgb(Colors.GRAY)
|
||||
val progressColor = Color.GREEN// Farbe für die Fortschrittslinie
|
||||
|
||||
// Zeichne die graue Linie und die grüne Linie
|
||||
for ((index, child) in childContainer.children().withIndex()) {
|
||||
if (child is Wrapper) {
|
||||
val childX = child.anchorPoint().x() + (child.anchorPoint().width()) / 2
|
||||
val childY = child.anchorPoint().y()
|
||||
|
||||
// Zeichne die graue Linie
|
||||
val angle =
|
||||
Math.toDegrees(Math.atan2((childY - parentY).toDouble(), (childX - parentX).toDouble()));
|
||||
val length =
|
||||
Math.sqrt(((childX - parentX) * (childX - parentX) + (childY - parentY) * (childY - parentY)).toDouble());
|
||||
if (drawLines) {
|
||||
context.drawLine(parentX, parentY, angle, length, whiteLineThickness * 2, Color.BLACK);
|
||||
context.drawLine(parentX, parentY, angle, length, whiteLineThickness, grayedColor);
|
||||
}
|
||||
|
||||
val progress = child.node.value.progress(player)
|
||||
|
||||
// Berechne den Fortschritt-Endpunkt
|
||||
val progressEndX = parentX + (childX - parentX) * progress
|
||||
val progressEndY =
|
||||
parentY + (childY - parentY) * progress / ((childY - parentY).toDouble().takeIf { it != 0.0 }
|
||||
?: 1.0)
|
||||
|
||||
// Zeichne die grüne Linie über der grauen Linie
|
||||
val progressAngle = angle; // Verwende denselben Winkel
|
||||
val progressLength = length * progress; // Berechne die Länge basierend auf dem Fortschritt
|
||||
|
||||
if (drawLines) {
|
||||
context.drawLine(
|
||||
parentX,
|
||||
parentY,
|
||||
progressAngle,
|
||||
progressLength,
|
||||
whiteLineThickness,
|
||||
progressColor
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun OwoUIDrawContext.drawLine(x1: Int, y1: Int, angle: Double, length: Double, thickness: Double, color: Color) {
|
||||
// Berechne die Endpunkte der Linie basierend auf dem Winkel
|
||||
val radians = Math.toRadians(angle)
|
||||
val x2 = (x1 + cos(radians) * length).toInt()
|
||||
val y2 = (y1 + sin(radians) * length).toInt()
|
||||
|
||||
val offset =
|
||||
(Vector2d((x2 - x1).toDouble(), (y2 - y1).toDouble())).perpendicular().normalize().mul(thickness * 0.5f)
|
||||
val buffer = Tessellator.getInstance().begin(DrawMode.QUADS, VertexFormats.POSITION_COLOR)
|
||||
val matrix: Matrix4f = this.getMatrices().peek().getPositionMatrix()
|
||||
val vColor = color.argb()
|
||||
|
||||
buffer.vertex(matrix, (x1.toDouble() + offset.x).toFloat(), (y1.toDouble() + offset.y).toFloat(), 0.0f)
|
||||
.color(vColor)
|
||||
buffer.vertex(matrix, (x1.toDouble() - offset.x).toFloat(), (y1.toDouble() - offset.y).toFloat(), 0.0f)
|
||||
.color(vColor)
|
||||
buffer.vertex(matrix, (x2.toDouble() - offset.x).toFloat(), (y2.toDouble() - offset.y).toFloat(), 0.0f)
|
||||
.color(vColor)
|
||||
buffer.vertex(matrix, (x2.toDouble() + offset.x).toFloat(), (y2.toDouble() + offset.y).toFloat(), 0.0f)
|
||||
.color(vColor)
|
||||
|
||||
RenderSystem.enableBlend()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
RenderSystem.setShader { GameRenderer.getPositionColorProgram() }
|
||||
BufferRenderer.drawWithGlobalProgram(buffer.end())
|
||||
}
|
||||
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
|
||||
//import me.cortex.nvidium.Nvidium
|
||||
import gg.norisk.heroes.client.ui.components.HeroListComponentV2
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.ui.components.LabelButtonComponent
|
||||
import gg.norisk.ui.components.ScalableLabelComponent
|
||||
import io.wispforest.owo.ui.base.BaseOwoScreen
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import io.wispforest.owo.ui.util.UISounds
|
||||
import me.cortex.nvidium.Nvidium
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import java.awt.Color
|
||||
|
||||
class HeroSelectorScreenV2(val heroes: List<Hero>, val isKitEditorEnabled: Boolean = false) :
|
||||
BaseOwoScreen<FlowLayout>() {
|
||||
var hero: Hero? = null
|
||||
set(value) {
|
||||
heroInfoComponent?.remove()
|
||||
centerLabel.remove()
|
||||
if (field != value) {
|
||||
field = value
|
||||
if (value != null) {
|
||||
heroInfoComponent = heroAbility(value)
|
||||
this.uiAdapter.rootComponent.child(heroInfoComponent)
|
||||
}
|
||||
} else {
|
||||
field = null
|
||||
this.uiAdapter.rootComponent.child(centerLabel)
|
||||
}
|
||||
}
|
||||
var heroInfoComponent: FlowLayout? = null
|
||||
var centerLabel: FlowLayout = Containers.verticalFlow(Sizing.fill(), Sizing.content()).apply {
|
||||
child(ScalableLabelComponent(literalText {
|
||||
text("CHOOSE YOUR HERO")
|
||||
}, 3f).apply {
|
||||
shadow(true)
|
||||
})
|
||||
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
positioning(Positioning.relative(30, 40))
|
||||
}
|
||||
|
||||
override fun createAdapter(): OwoUIAdapter<FlowLayout> {
|
||||
return OwoUIAdapter.create(this, Containers::verticalFlow);
|
||||
}
|
||||
|
||||
val adapter get() = uiAdapter.rootComponent
|
||||
|
||||
private fun heroAbility(hero: Hero): FlowLayout {
|
||||
val container = Containers.verticalFlow(Sizing.content(), Sizing.content())
|
||||
.apply { positioning(Positioning.relative(50, 30)) }
|
||||
|
||||
container.child(ScalableLabelComponent(literalText {
|
||||
text(hero.name) {
|
||||
}
|
||||
}, 3f).apply {
|
||||
shadow(true)
|
||||
})
|
||||
container.alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
container.child(LabelButtonComponent("SKILL TREE".literal, Color.YELLOW).apply {
|
||||
label.scale = 1.5f
|
||||
mouseDown().subscribe { _, _, _ ->
|
||||
UISounds.playButtonSound()
|
||||
buildSkillTree(hero)
|
||||
return@subscribe true
|
||||
}
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
private fun buildSkillTree(hero: Hero) {
|
||||
uiAdapter.rootComponent.child(Containers.overlay(SkillTreeWrapper(hero)))
|
||||
}
|
||||
|
||||
private class XpLabel(scale: Float = 1f) : ScalableLabelComponent("".literal, scale) {
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
text(literalText {
|
||||
text("XP: ")
|
||||
text((MinecraftClient.getInstance().player?.ffaPlayer?.xp ?: 0).toString())
|
||||
})
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
if (FabricLoader.getInstance().isModLoaded("nvidium")) {
|
||||
Nvidium.FORCE_DISABLE = false
|
||||
this.client?.worldRenderer?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(root: FlowLayout) {
|
||||
val heroList = HeroListComponentV2(heroes, this)
|
||||
heroList.positioning(Positioning.relative(50, 90))
|
||||
|
||||
root.child(heroList)
|
||||
if (hero == null) {
|
||||
root.child(centerLabel)
|
||||
}
|
||||
|
||||
if (FabricLoader.getInstance().isModLoaded("nvidium")) {
|
||||
Nvidium.FORCE_DISABLE = true
|
||||
this.client?.worldRenderer?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldPause(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc(): Boolean {
|
||||
return FabricLoader.getInstance().isDevelopmentEnvironment
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
import io.wispforest.owo.ui.core.Component
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
|
||||
interface ISkill {
|
||||
fun isUnlocked(player: PlayerEntity): Boolean
|
||||
fun isParentUnlocked(player: PlayerEntity): Boolean
|
||||
fun title(): Text
|
||||
fun parent(): ISkill?
|
||||
fun progress(player: PlayerEntity): Double
|
||||
fun skill()
|
||||
fun isLast(): Boolean
|
||||
fun tooltip(player: PlayerEntity): Text
|
||||
fun icon(): Component?
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
|
||||
//import me.cortex.nvidium.Nvidium
|
||||
import gg.norisk.heroes.common.hero.getHero
|
||||
import io.wispforest.owo.ui.base.BaseOwoScreen
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.HorizontalAlignment
|
||||
import io.wispforest.owo.ui.core.OwoUIAdapter
|
||||
import io.wispforest.owo.ui.core.VerticalAlignment
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
|
||||
class SkillTreeScreen : BaseOwoScreen<FlowLayout>() {
|
||||
|
||||
override fun createAdapter(): OwoUIAdapter<FlowLayout> {
|
||||
return OwoUIAdapter.create(this, Containers::verticalFlow);
|
||||
}
|
||||
|
||||
|
||||
override fun build(root: FlowLayout) {
|
||||
root.alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
root.child(SkillTreeWrapper(MinecraftClient.getInstance().player?.getHero() ?: return))
|
||||
}
|
||||
|
||||
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
super.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
import gg.norisk.heroes.common.ability.CooldownProperty
|
||||
import gg.norisk.heroes.common.ability.SingleUseProperty
|
||||
import gg.norisk.heroes.common.command.DebugCommand.getProgressBar
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.common.hero.ability.SkillPropertyPacket
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import io.wispforest.owo.ui.core.Component
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
|
||||
object SkillTreeUtils {
|
||||
fun toSkillTree(ability: AbstractAbility<*>): TreeNode<ISkill> {
|
||||
val root = TreeNode<ISkill>(object : ISkill {
|
||||
override fun isUnlocked(player: PlayerEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isParentUnlocked(player: PlayerEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun title(): Text {
|
||||
return Text.translatable(ability.name)
|
||||
}
|
||||
|
||||
override fun parent(): ISkill? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun progress(player: PlayerEntity): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
override fun skill() {
|
||||
|
||||
}
|
||||
|
||||
override fun isLast(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tooltip(player: PlayerEntity): Text {
|
||||
return Text.empty()
|
||||
}
|
||||
|
||||
override fun icon(): Component {
|
||||
return ability.getIconComponent()
|
||||
}
|
||||
})
|
||||
for (property in ability.getAllProperties()) {
|
||||
if (property is SingleUseProperty) continue
|
||||
if (property is CooldownProperty) {
|
||||
if (property.name == "NoCooldown") continue
|
||||
}
|
||||
var lastChild: TreeNode<ISkill>? = root
|
||||
|
||||
repeat(property.maxLevel) { level ->
|
||||
val newChild = TreeNode<ISkill>(object : ISkill {
|
||||
val parent = lastChild?.value
|
||||
override fun isUnlocked(player: PlayerEntity): Boolean {
|
||||
val levelInfo = property.getLevelInfo(player.uuid)
|
||||
levelInfo.percentageTillNextLevel
|
||||
return levelInfo.currentLevel > level
|
||||
}
|
||||
|
||||
override fun isParentUnlocked(player: PlayerEntity): Boolean {
|
||||
val levelInfo = property.getLevelInfo(player.uuid)
|
||||
return levelInfo.currentLevel > level - 1
|
||||
}
|
||||
|
||||
override fun title(): Text {
|
||||
return literalText {
|
||||
text(Text.translatable(property.translationKey))
|
||||
text(" ")
|
||||
text(intToRoman(level + 1))
|
||||
}
|
||||
}
|
||||
|
||||
override fun parent(): ISkill? {
|
||||
return parent
|
||||
}
|
||||
|
||||
override fun progress(player: PlayerEntity): Double {
|
||||
val levelInfo = property.getLevelInfo(player.uuid)
|
||||
if (levelInfo.currentLevel == level) {
|
||||
return levelInfo.percentageTillNextLevel / 100.0
|
||||
} else if (levelInfo.currentLevel > level) {
|
||||
return 1.0
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
override fun skill() {
|
||||
Networking.c2sSkillProperty.send(
|
||||
SkillPropertyPacket(
|
||||
ability.hero.internalKey, ability.internalKey, property.internalKey
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun isLast(): Boolean {
|
||||
return level >= property.maxLevel - 1
|
||||
}
|
||||
|
||||
private fun <T> getValueText(value: T): Text {
|
||||
return literalText {
|
||||
// Überprüfe, ob der Wert eine Zahl ist
|
||||
val formattedValue = when (value) {
|
||||
is Number -> {
|
||||
val doubleValue = value.toDouble()
|
||||
// Überprüfe, ob der Wert Nachkommastellen hat
|
||||
if (doubleValue % 1.0 == 0.0) {
|
||||
doubleValue.toInt().toString() // Keine Nachkommastellen, nur die ganze Zahl
|
||||
} else {
|
||||
String.format("%.3f", doubleValue) // Formatiere auf 3 Nachkommastellen
|
||||
}
|
||||
}
|
||||
else -> value.toString() // Andernfalls einfach den Wert als String
|
||||
}
|
||||
|
||||
text(formattedValue)
|
||||
if (property is CooldownProperty) {
|
||||
text("s")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun tooltip(player: PlayerEntity): Text {
|
||||
val levelInfo = property.getLevelInfo(player.uuid, level)
|
||||
return literalText {
|
||||
text("[")
|
||||
text(Text.translatable(property.translationKey))
|
||||
text("]")
|
||||
newLine()
|
||||
text(Text.translatable(property.descriptionKey))
|
||||
emptyLine()
|
||||
text("[")
|
||||
text("Progress")
|
||||
text("]")
|
||||
newLine()
|
||||
text(
|
||||
getProgressBar(
|
||||
levelInfo.percentageTillNextLevel,
|
||||
100.0,
|
||||
50,
|
||||
"|".single()
|
||||
)
|
||||
)
|
||||
text(" ${String.format("%.2f", levelInfo.percentageTillNextLevel)}%")
|
||||
emptyLine()
|
||||
text(getValueText(property.getValue(level + 1)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun icon(): Component {
|
||||
return property.icon.invoke()
|
||||
}
|
||||
})
|
||||
lastChild?.addChild(newChild)
|
||||
lastChild = newChild
|
||||
}
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
private val m_k = listOf(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1)
|
||||
private val m_v = listOf("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I")
|
||||
|
||||
fun intToRoman(num: Int): String {
|
||||
var str = ""
|
||||
var n = num
|
||||
|
||||
for (i in m_k.indices) {
|
||||
while (n >= m_k[i]) {
|
||||
n -= m_k[i]
|
||||
str += m_v[i]
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import io.wispforest.owo.ui.component.Components
|
||||
import io.wispforest.owo.ui.component.TextureComponent
|
||||
import io.wispforest.owo.ui.container.Containers
|
||||
import io.wispforest.owo.ui.container.FlowLayout
|
||||
import io.wispforest.owo.ui.core.*
|
||||
import io.wispforest.owo.ui.util.UISounds
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.sound.SoundEvents
|
||||
import net.minecraft.util.Colors
|
||||
import net.minecraft.util.Identifier
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
|
||||
class SkillTreeWrapper(
|
||||
val hero: Hero,
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
val skillTreeWrapper = Containers.horizontalFlow(Sizing.content(), Sizing.content())
|
||||
val tabs = mutableListOf<TabButton>()
|
||||
|
||||
init {
|
||||
var index = 0
|
||||
for ((name, ability) in hero.abilities) {
|
||||
tabs += TabButton(ability, index)
|
||||
index++
|
||||
}
|
||||
|
||||
child(TabWrapper().apply {
|
||||
children(tabs)
|
||||
//zIndex(5000)
|
||||
allowOverflow(true)
|
||||
gap(2)
|
||||
})
|
||||
child(skillTreeWrapper)
|
||||
|
||||
tabs.first { it.ability.hasUnlocked(MinecraftClient.getInstance().player!!) }.apply {
|
||||
this.isSelected = true
|
||||
this.onClick()
|
||||
}
|
||||
|
||||
//children(tabs)
|
||||
}
|
||||
|
||||
override fun drawChildren(
|
||||
context: OwoUIDrawContext,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
partialTicks: Float,
|
||||
delta: Float,
|
||||
children: MutableList<out Component>
|
||||
) {
|
||||
super.drawChildren(context, mouseX, mouseY, partialTicks, delta, children.reversed())
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext?, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
|
||||
inner class TabWrapper(
|
||||
horizontalSizing: Sizing = Sizing.content(),
|
||||
verticalSizing: Sizing = Sizing.content()
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.HORIZONTAL) {
|
||||
}
|
||||
|
||||
inner class TabButton(
|
||||
val ability: AbstractAbility<*>,
|
||||
index: Int,
|
||||
horizontalSizing: Sizing = Sizing.fixed(28),
|
||||
verticalSizing: Sizing = Sizing.fixed(28)
|
||||
) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.VERTICAL) {
|
||||
var page = AbilitySkillTreeComponent(ability)
|
||||
var isSelected = false
|
||||
var item = ability.getIconComponent().id("unlocked")
|
||||
var lockIcon = Components.texture("textures/gui/lock_icon.png".toId(), 0, 0, 20, 20, 20, 20).apply {
|
||||
id("locked")
|
||||
tooltip(literalText {
|
||||
text(ability.name)
|
||||
newLine()
|
||||
text(ability.getUnlockCondition()) {
|
||||
color = Colors.LIGHT_GRAY
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
init {
|
||||
surface { context, container ->
|
||||
context.matrices.push()
|
||||
val texture = if (isSelected) {
|
||||
if (index == 0) {
|
||||
Identifier.of("textures/gui/sprites/advancements/tab_above_left_selected.png")
|
||||
} else {
|
||||
Identifier.of("textures/gui/sprites/advancements/tab_above_middle_selected.png")
|
||||
}
|
||||
} else {
|
||||
if (index == 0) {
|
||||
Identifier.of("textures/gui/sprites/advancements/tab_above_left.png")
|
||||
} else {
|
||||
Identifier.of("textures/gui/sprites/advancements/tab_above_middle.png")
|
||||
}
|
||||
}
|
||||
context.drawTexture(
|
||||
texture,
|
||||
container.x(),
|
||||
container.y(),
|
||||
0f,
|
||||
0f,
|
||||
28,
|
||||
32,
|
||||
28,
|
||||
32
|
||||
)
|
||||
// NinePatchTexture.draw(OwoUIDrawContext.PANEL_NINE_PATCH_TEXTURE, context, container.x(), container.y(), container.width(), container.height())
|
||||
context.matrices.pop()
|
||||
}
|
||||
mouseDown().subscribe { _, _, _ ->
|
||||
if (!ability.hasUnlocked(MinecraftClient.getInstance().player!!)) {
|
||||
MinecraftClient.getInstance().soundManager.play(
|
||||
PositionedSoundInstance.master(
|
||||
SoundEvents.ENTITY_VILLAGER_NO,
|
||||
1.0f
|
||||
)
|
||||
)
|
||||
return@subscribe true
|
||||
}
|
||||
if (!isSelected) {
|
||||
UISounds.playInteractionSound()
|
||||
isSelected = !isSelected
|
||||
tabs.filter { it != this }.forEach { it.isSelected = false }
|
||||
onClick()
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
tooltip(ability.name.literal)
|
||||
alignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER)
|
||||
//positioning(Positioning.absolute(0, 0))
|
||||
child(lockIcon)
|
||||
//zIndex(-5000)
|
||||
allowOverflow(true)
|
||||
}
|
||||
|
||||
fun onClick() {
|
||||
if (isSelected) {
|
||||
skillTreeWrapper.clearChildren()
|
||||
skillTreeWrapper.child(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(context: OwoUIDrawContext, mouseX: Int, mouseY: Int, partialTicks: Float, delta: Float) {
|
||||
val hasUnlocked = ability.hasUnlocked(MinecraftClient.getInstance().player!!)
|
||||
val locked = childById(TextureComponent::class.java, "locked")
|
||||
val unlocked = childById(Component::class.java, "unlocked")
|
||||
val component = if (hasUnlocked) {
|
||||
if (unlocked == null) {
|
||||
child(item)
|
||||
}
|
||||
if (locked != null) {
|
||||
removeChild(locked)
|
||||
}
|
||||
item
|
||||
} else {
|
||||
if (locked == null) {
|
||||
child(lockIcon)
|
||||
}
|
||||
if (unlocked != null) {
|
||||
removeChild(item)
|
||||
}
|
||||
lockIcon
|
||||
}
|
||||
|
||||
|
||||
if (isSelected) {
|
||||
component.margins(Insets.none())
|
||||
} else {
|
||||
component.margins(Insets.top(8))
|
||||
}
|
||||
super.draw(context, mouseX, mouseY, partialTicks, delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package gg.norisk.heroes.client.ui.skilltree
|
||||
|
||||
class TreeNode<T>(val value: T) {
|
||||
val children: MutableList<TreeNode<T>> = mutableListOf()
|
||||
|
||||
fun addChild(child: TreeNode<T>): TreeNode<T> {
|
||||
children.add(child)
|
||||
return this
|
||||
}
|
||||
|
||||
// Tiefensuche (Depth-First Search)
|
||||
fun dfs(visit: (T) -> Unit) {
|
||||
visit(value)
|
||||
children.forEach { it.dfs(visit) }
|
||||
}
|
||||
|
||||
// Breitensuche (Breadth-First Search)
|
||||
fun bfs(visit: (T) -> Unit) {
|
||||
val queue = ArrayDeque<TreeNode<T>>()
|
||||
queue.add(this)
|
||||
|
||||
while (queue.isNotEmpty()) {
|
||||
val currentNode = queue.removeFirst()
|
||||
visit(currentNode.value)
|
||||
currentNode.children.forEach { queue.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package gg.norisk.heroes.common
|
||||
|
||||
import gg.norisk.heroes.common.registry.SoundRegistry
|
||||
import gg.norisk.heroes.server.HeroesManagerServer
|
||||
import net.fabricmc.api.EnvType
|
||||
import net.fabricmc.api.ModInitializer
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlag
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.WorldSavePath
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
object HeroesManager : ModInitializer {
|
||||
const val MOD_ID = "hero-api"
|
||||
var baseDirectory: File = getBasePath(null)
|
||||
val logger = LogManager.getLogger(MOD_ID)
|
||||
fun String.toId() = Identifier.of(MOD_ID, this)
|
||||
|
||||
lateinit var heroesFlag: FeatureFlag
|
||||
|
||||
val prefix
|
||||
get() = literalText {
|
||||
text("[") { }
|
||||
text("Heroes") { }
|
||||
text("]") { }
|
||||
text(" ")
|
||||
}
|
||||
|
||||
val isServer get() = FabricLoader.getInstance().isDevelopmentEnvironment || FabricLoader.getInstance().environmentType == EnvType.SERVER
|
||||
val isClient get() = FabricLoader.getInstance().isDevelopmentEnvironment || FabricLoader.getInstance().environmentType == EnvType.CLIENT
|
||||
|
||||
override fun onInitialize() {
|
||||
logger.info("Init Hero-Api Common...")
|
||||
SoundRegistry.init()
|
||||
HeroesManagerServer.initServer()
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTING.register {
|
||||
setBasePath(it.getSavePath(WorldSavePath("heroes")))
|
||||
logger.info("Found Server Path: ${baseDirectory}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBasePath(serverPath: Path?): File {
|
||||
val defaultPath = if (FabricLoader.getInstance().environmentType == EnvType.SERVER) {
|
||||
FabricLoader.getInstance().configDir
|
||||
} else {
|
||||
serverPath ?: FabricLoader.getInstance().configDir
|
||||
}
|
||||
|
||||
val baseDirectory = File(
|
||||
System.getProperty(
|
||||
"hero_folder_path",
|
||||
defaultPath.toFile().absolutePath
|
||||
),
|
||||
).apply {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
return baseDirectory
|
||||
}
|
||||
|
||||
private fun setBasePath(serverPath: Path?) {
|
||||
this.baseDirectory = getBasePath(serverPath)
|
||||
}
|
||||
|
||||
fun client(callBack: () -> Unit) {
|
||||
if (FabricLoader.getInstance().environmentType == EnvType.CLIENT) {
|
||||
callBack.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.ability.operation.AddValueTotal
|
||||
import gg.norisk.heroes.common.ability.operation.MultiplyBase
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
sealed class AbstractNumberProperty : PlayerProperty<Double>() {
|
||||
abstract var modifier: Operation
|
||||
|
||||
override fun getValue(uuid: UUID): Double {
|
||||
return getValue(getLevelInfo(uuid).currentLevel)
|
||||
}
|
||||
|
||||
override fun getValue(int: Int): Double {
|
||||
return when (modifier) {
|
||||
is AddValueTotal, is MultiplyBase -> {
|
||||
modifier.getOperatedValue(baseValue, int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fromJson(text: String) {
|
||||
runCatching {
|
||||
val loaded = JSON.decodeFromString<NumberProperty>(text)
|
||||
baseValue = loaded.baseValue
|
||||
maxLevel = loaded.maxLevel
|
||||
name = loaded.name
|
||||
levelScale = loaded.levelScale
|
||||
modifier = loaded.modifier
|
||||
}.onFailure {
|
||||
HeroesManager.logger.error("Error Loading $name ${it.message}")
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toJson(): String {
|
||||
return JSON.encodeToString<PlayerProperty<Double>>(this)
|
||||
}
|
||||
|
||||
override fun toJsonElement(): JsonElement {
|
||||
return JSON.encodeToJsonElement<PlayerProperty<Double>>(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class AbstractUsageProperty : AbstractNumberProperty()
|
||||
@@ -0,0 +1,15 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
open class CooldownProperty(
|
||||
override var baseValue: Double,
|
||||
override var maxLevel: Int,
|
||||
override var name: String,
|
||||
override var modifier: Operation,
|
||||
override var levelScale: Int = PlayerProperty.levelScale
|
||||
) : AbstractNumberProperty() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
data class LevelInformation(
|
||||
val currentLevel: Int,
|
||||
val nextLevel: Int,
|
||||
val xpCurrentLevel: Int,
|
||||
val xpNextLevel: Int,
|
||||
val xpTillNextLevel: Int,
|
||||
val percentageTillNextLevel: Double,
|
||||
val experiencePoints: Int,
|
||||
val maxLevel: Int,
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
class MultiUseProperty(
|
||||
override var baseValue: Double,
|
||||
override var maxLevel: Int,
|
||||
override var name: String,
|
||||
override var modifier: Operation,
|
||||
override var levelScale: Int = PlayerProperty.levelScale
|
||||
) : AbstractUsageProperty() {
|
||||
@Transient
|
||||
val uses = mutableMapOf<UUID, Int>()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
class NumberProperty(
|
||||
override var baseValue: Double,
|
||||
override var maxLevel: Int,
|
||||
override var name: String,
|
||||
override var modifier: Operation,
|
||||
override var levelScale: Int = PlayerProperty.levelScale,
|
||||
) : AbstractNumberProperty() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.server.database.player.PlayerProvider
|
||||
import io.wispforest.owo.ui.component.Components
|
||||
import io.wispforest.owo.ui.core.Component
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import net.minecraft.item.Items
|
||||
import java.util.*
|
||||
import kotlin.math.cbrt
|
||||
import kotlin.math.pow
|
||||
|
||||
@Serializable
|
||||
sealed class PlayerProperty<T> {
|
||||
abstract var baseValue: T
|
||||
abstract var maxLevel: Int
|
||||
abstract var name: String
|
||||
abstract var levelScale: Int
|
||||
|
||||
@Transient
|
||||
var icon: () -> Component = {
|
||||
Components.item(Items.CLOCK.defaultStack)
|
||||
}
|
||||
|
||||
@Transient
|
||||
lateinit var hero: Hero
|
||||
|
||||
@Transient
|
||||
lateinit var ability: AbstractAbility<*>
|
||||
|
||||
companion object {
|
||||
val levelScale = 250
|
||||
|
||||
val JSON = Json {
|
||||
prettyPrint = true
|
||||
encodeDefaults = true
|
||||
ignoreUnknownKeys = true
|
||||
//explicitNulls = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getValue(uuid: UUID): T
|
||||
abstract fun getValue(int: Int): T
|
||||
abstract fun fromJson(text: String)
|
||||
abstract fun toJson(): String
|
||||
abstract fun toJsonElement(): JsonElement
|
||||
open fun getMaxValue(): T {
|
||||
return getValue(maxLevel)
|
||||
}
|
||||
|
||||
fun addExperience(uuid: UUID, experienceToAdd: Int): Int {
|
||||
val player = getOrLoadPlayer(uuid)
|
||||
val maxExperience = getXpForLevel(maxLevel)
|
||||
|
||||
val buffer = player.experiencePoints + experienceToAdd
|
||||
return if (buffer > maxExperience) {
|
||||
val toAdd = maxExperience - player.experiencePoints // Nur bis zum Maximalwert hinzufügen
|
||||
player.experiencePoints = maxExperience // Korrigiere Erfahrungspunkte auf Maximum
|
||||
toAdd
|
||||
} else {
|
||||
player.experiencePoints += experienceToAdd
|
||||
experienceToAdd
|
||||
}
|
||||
}
|
||||
|
||||
fun isMaxed(uuid: UUID): Boolean {
|
||||
return getLevelInfo(uuid).currentLevel >= maxLevel
|
||||
}
|
||||
|
||||
fun getLevelInfo(uuid: UUID, level: Int? = null): LevelInformation {
|
||||
val player = getOrLoadPlayer(uuid)
|
||||
val currentLevel = Math.min(maxLevel, level ?: calculateLevel(player.experiencePoints))
|
||||
val nextLevel = Math.min(maxLevel, currentLevel + 1)
|
||||
|
||||
val xpCurrentLevel = getXpForLevel(currentLevel)
|
||||
|
||||
val xpNextLevel = if (currentLevel < maxLevel) {
|
||||
getXpForLevel(nextLevel)
|
||||
} else {
|
||||
xpCurrentLevel // Kein weiteres Level, bleibt gleich
|
||||
}
|
||||
|
||||
val xpTillNextLevel = if (currentLevel < maxLevel) {
|
||||
xpNextLevel - player.experiencePoints
|
||||
} else {
|
||||
0 // Kein weiteres Level
|
||||
}
|
||||
|
||||
val percentageTillNextLevel = if (currentLevel < maxLevel) {
|
||||
Math.max(
|
||||
0.0, Math.min(
|
||||
100.0,
|
||||
((player.experiencePoints - xpCurrentLevel).toDouble() / (xpNextLevel - xpCurrentLevel).toDouble()) * 100.0
|
||||
)
|
||||
)
|
||||
} else {
|
||||
100.0 // Max-Level erreicht
|
||||
}
|
||||
|
||||
if (currentLevel == maxLevel - 1 && percentageTillNextLevel >= 100f) {
|
||||
return LevelInformation(
|
||||
maxLevel,
|
||||
maxLevel,
|
||||
xpCurrentLevel,
|
||||
xpNextLevel,
|
||||
xpTillNextLevel,
|
||||
percentageTillNextLevel,
|
||||
player.experiencePoints,
|
||||
maxLevel
|
||||
)
|
||||
}
|
||||
|
||||
return LevelInformation(
|
||||
currentLevel,
|
||||
nextLevel,
|
||||
xpCurrentLevel,
|
||||
xpNextLevel,
|
||||
xpTillNextLevel,
|
||||
percentageTillNextLevel,
|
||||
player.experiencePoints,
|
||||
maxLevel
|
||||
)
|
||||
}
|
||||
|
||||
private fun calculateLevel(xp: Int): Int {
|
||||
return cbrt((xp / levelScale).toDouble()).toInt()
|
||||
}
|
||||
|
||||
private fun getXpForLevel(level: Int): Int {
|
||||
return (levelScale * level.toDouble().pow(3)).toInt()
|
||||
}
|
||||
|
||||
val internalKey get() = name.lowercase().replace(" ", "_")
|
||||
val translationKey get() = "heroes.property.${internalKey}"
|
||||
val descriptionKey get() = "heroes.property.${internalKey}.description"
|
||||
|
||||
private fun getOrLoadPlayer(uuid: UUID): PropertyPlayer {
|
||||
val player = runBlocking { PlayerProvider.get(uuid) }
|
||||
val heroMap = player.heroes.computeIfAbsent(hero.internalKey) { mutableMapOf() }
|
||||
val abilityMap = heroMap.computeIfAbsent(ability.internalKey) { mutableMapOf() }
|
||||
val property = abilityMap.computeIfAbsent(internalKey) { PropertyPlayer() }
|
||||
return property
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PropertyPlayer(
|
||||
var experiencePoints: Int = 0
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package gg.norisk.heroes.common.ability
|
||||
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class SingleUseProperty(
|
||||
override var baseValue: Double,
|
||||
override var maxLevel: Int,
|
||||
override var name: String,
|
||||
override var modifier: Operation,
|
||||
override var levelScale: Int = PlayerProperty.levelScale
|
||||
) : AbstractUsageProperty() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package gg.norisk.heroes.common.ability.operation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class AddValueTotal(var steps: List<Double>) : Operation() {
|
||||
|
||||
constructor(vararg steps: Double) : this(steps.toList())
|
||||
|
||||
override fun getOperatedValue(baseValue: Double, level: Int): Double {
|
||||
// wir doublen alles wegen... jo
|
||||
var valueToReturn = baseValue
|
||||
repeat(level) {
|
||||
val increment = steps.getOrNull(it) ?: error("$steps doesn't have an index for level $it")
|
||||
valueToReturn += increment
|
||||
}
|
||||
|
||||
return valueToReturn
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package gg.norisk.heroes.common.ability.operation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class MultiplyBase(var steps: List<Double>) : Operation() {
|
||||
constructor(vararg steps: Double) : this(steps.toList())
|
||||
override fun getOperatedValue(baseValue: Double, level: Int): Double {
|
||||
//wir doublen alles wegen... jo
|
||||
val increment = steps.getOrNull(level) ?: error("$steps doesn't have an index for level $level")
|
||||
|
||||
return baseValue * increment
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package gg.norisk.heroes.common.ability.operation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class Operation {
|
||||
abstract fun getOperatedValue(baseValue: Double, level: Int): Double
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package gg.norisk.heroes.common.command
|
||||
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.ability.PlayerProperty
|
||||
import gg.norisk.heroes.common.command.EditPropertyCommand.editCommand
|
||||
import gg.norisk.heroes.common.ffa.KitEditorManager
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.hero.HeroManager
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.heroes.common.networking.dto.HeroSelectorPacket
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.heroes.server.database.player.PlayerProvider
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.command.argument.EntityArgumentType
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
import net.silkmc.silk.commands.PermissionLevel
|
||||
import net.silkmc.silk.commands.command
|
||||
import net.silkmc.silk.core.text.literal
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
|
||||
object DebugCommand {
|
||||
fun initServer() {
|
||||
command("heroes") {
|
||||
requiresPermissionLevel(PermissionLevel.OWNER)
|
||||
//if (HeroesManager.isClient) {
|
||||
requires { it.server.saveProperties.dataConfiguration.enabledFeatures.contains(HeroesManager.heroesFlag) }
|
||||
// }
|
||||
runs {
|
||||
Networking.s2cHeroSelectorPacket.send(
|
||||
HeroSelectorPacket(HeroManager.registeredHeroes.keys.toList(), true, KitEditorManager.hasKitWorld),
|
||||
this.source.playerOrThrow
|
||||
)
|
||||
}
|
||||
literal("reload") {
|
||||
runs {
|
||||
HeroManager.reloadHeroes(*HeroManager.registeredHeroes.values.toTypedArray())
|
||||
}
|
||||
}
|
||||
literal("xp") {
|
||||
literal("set") {
|
||||
argument("players", EntityArgumentType.players()) {
|
||||
argument<Int>("xp") { xp ->
|
||||
runsAsync {
|
||||
val players = EntityArgumentType.getPlayers(this, "players")
|
||||
for (player in players) {
|
||||
val cachedPlayer = PlayerProvider.get(player.uuid)
|
||||
cachedPlayer.xp = xp()
|
||||
player.ffaPlayer = cachedPlayer
|
||||
PlayerProvider.save(player.ffaPlayer)
|
||||
this.source.sendMessage("Set Xp of ${player.gameProfile.name} to ${xp()}".literal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
argument<String>("hero") { heroKey ->
|
||||
suggestList { HeroManager.registeredHeroes.keys }
|
||||
literal("ability") {
|
||||
argument<String>("ability") { abilityKey ->
|
||||
suggestList {
|
||||
HeroManager.getHero(heroKey(it))?.abilities?.keys
|
||||
}
|
||||
literal("property") {
|
||||
argument<String>("property") { propertyKey ->
|
||||
suggestList {
|
||||
HeroManager.getHero(heroKey(it))?.abilities
|
||||
?.values
|
||||
?.map { ability -> ability.getAllProperties() }
|
||||
?.flatten()?.map { property -> property.internalKey }
|
||||
}
|
||||
editCommand()
|
||||
literal("add") {
|
||||
argument<Int>("expierencepoints") { xpPoints ->
|
||||
runs {
|
||||
val hero = HeroManager.getHero(heroKey())!!
|
||||
|
||||
val ability = hero.abilities.values
|
||||
.flatMap { ability ->
|
||||
ability.getAllProperties().map { property -> ability to property }
|
||||
}
|
||||
.firstOrNull { (ability, property) ->
|
||||
property.internalKey == propertyKey() && ability.internalKey == abilityKey()
|
||||
}!!
|
||||
|
||||
val player = this.source.playerOrThrow
|
||||
ability.second.addExperience(player.uuid, xpPoints())
|
||||
sendLevelInfo(player, player, ability.second, ability.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
literal("info") {
|
||||
runs {
|
||||
val hero = HeroManager.getHero(heroKey())!!
|
||||
val ability = hero.abilities.values
|
||||
.flatMap { ability ->
|
||||
ability.getAllProperties().map { property -> ability to property }
|
||||
}
|
||||
.firstOrNull { (ability, property) ->
|
||||
property.internalKey == propertyKey() && ability.internalKey == abilityKey()
|
||||
}!!
|
||||
val player = this.source.playerOrThrow
|
||||
sendLevelInfo(player, player, ability.second, ability.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <S> CommandContext<S>.getHeroInformation(): Triple<Hero, AbstractAbility<*>, PlayerProperty<*>> {
|
||||
val hero = HeroManager.getHero(this.getArgument("hero", String::class.java))!!
|
||||
val propertyKey = this.getArgument("property", String::class.java)
|
||||
val abilityKey = this.getArgument("ability", String::class.java)
|
||||
|
||||
val ability = hero.abilities.values
|
||||
.flatMap { ability ->
|
||||
ability.getAllProperties().map { property -> ability to property }
|
||||
}
|
||||
.firstOrNull { (ability, property) ->
|
||||
property.internalKey == propertyKey && ability.internalKey == abilityKey
|
||||
}!!
|
||||
|
||||
return Triple(hero, ability.first, ability.second)
|
||||
}
|
||||
|
||||
fun PlayerProperty<*>.toDebugText(): Text {
|
||||
return literalText {
|
||||
text(Text.translatable(this@toDebugText.translationKey))
|
||||
newLine()
|
||||
text("Max Level: ${this@toDebugText.maxLevel}")
|
||||
newLine()
|
||||
text("Base Value: ${this@toDebugText.baseValue}")
|
||||
}
|
||||
}
|
||||
|
||||
fun PlayerEntity.sendDebugMessage(message: Text) {
|
||||
if (FabricLoader.getInstance().isDevelopmentEnvironment) {
|
||||
sendMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendLevelInfo(
|
||||
player: PlayerEntity,
|
||||
about: PlayerEntity,
|
||||
property: PlayerProperty<*>,
|
||||
ability: AbstractAbility<*>
|
||||
) {
|
||||
val levelInfo = property.getLevelInfo(about.uuid)
|
||||
player.sendMessage(literalText {
|
||||
emptyLine()
|
||||
text("Level Info for ${property.name}") {
|
||||
underline = true
|
||||
}
|
||||
emptyLine()
|
||||
text("Player: ")
|
||||
text(player.name)
|
||||
text(" ${player.uuid}")
|
||||
newLine()
|
||||
text("Ability: ${ability.name}")
|
||||
newLine()
|
||||
text("Current Value: ${property.getValue(about.uuid)}")
|
||||
newLine()
|
||||
text("Current Level: ${levelInfo.currentLevel}/${levelInfo.maxLevel}")
|
||||
newLine()
|
||||
text("Next Level: ${levelInfo.nextLevel}")
|
||||
newLine()
|
||||
text("Step: ${levelInfo.experiencePoints}/${levelInfo.xpNextLevel}")
|
||||
newLine()
|
||||
text("Xp Needed for Upgrade: ${levelInfo.xpTillNextLevel}") { }
|
||||
newLine()
|
||||
text("Progress: ")
|
||||
text(
|
||||
getProgressBar(
|
||||
levelInfo.percentageTillNextLevel,
|
||||
100.0,
|
||||
50,
|
||||
"|".single()
|
||||
)
|
||||
)
|
||||
text(" ${String.format("%.3f", levelInfo.percentageTillNextLevel)}%")
|
||||
})
|
||||
}
|
||||
|
||||
fun getProgressBar(
|
||||
current: Double,
|
||||
max: Double,
|
||||
totalBars: Int,
|
||||
symbol: Char,
|
||||
completedColor: Int = getProgressBarColor(current, max),
|
||||
notCompletedColor: Int = 0xa1a1a1
|
||||
): Text {
|
||||
val percent = current.toFloat() / max
|
||||
val progressBars = (totalBars * percent).toInt()
|
||||
|
||||
return literalText {
|
||||
repeat(progressBars) {
|
||||
text("" + symbol) {
|
||||
color = completedColor
|
||||
}
|
||||
}
|
||||
repeat(totalBars - progressBars) {
|
||||
text("" + symbol) {
|
||||
color = notCompletedColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getProgressBarColor(progress: Double, maxProgress: Double): Int {
|
||||
val percentage = progress / maxProgress * 100.0
|
||||
return when {
|
||||
percentage > 66 -> 0x6fff36
|
||||
percentage > 30 -> 0xfff700
|
||||
else -> 0xff0000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package gg.norisk.heroes.common.command
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
import gg.norisk.heroes.common.ability.AbstractNumberProperty
|
||||
import gg.norisk.heroes.common.ability.PlayerProperty
|
||||
import gg.norisk.heroes.common.ability.operation.AddValueTotal
|
||||
import gg.norisk.heroes.common.ability.operation.MultiplyBase
|
||||
import gg.norisk.heroes.common.command.DebugCommand.getHeroInformation
|
||||
import gg.norisk.heroes.server.config.ConfigManagerServer
|
||||
import net.minecraft.server.command.ServerCommandSource
|
||||
import net.silkmc.silk.commands.ArgumentCommandBuilder
|
||||
import net.silkmc.silk.commands.LiteralCommandBuilder
|
||||
import net.silkmc.silk.core.text.broadcastText
|
||||
import java.awt.Color
|
||||
|
||||
object EditPropertyCommand {
|
||||
fun ArgumentCommandBuilder<ServerCommandSource, String>.editCommand() {
|
||||
literal("edit") {
|
||||
baseValue()
|
||||
maxLevel()
|
||||
levelScale()
|
||||
operation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun LiteralCommandBuilder<ServerCommandSource>.operation() {
|
||||
literal("operation") {
|
||||
literal("type") {
|
||||
argument<String>("type") { typeString ->
|
||||
suggestList {
|
||||
val type = (it.getHeroInformation().third as AbstractNumberProperty).modifier::class.simpleName
|
||||
val set = mutableSetOf(type, MultiplyBase::class.simpleName, AddValueTotal::class.simpleName)
|
||||
set.toList()
|
||||
}
|
||||
runs {
|
||||
val (hero, ability, property) = this.getHeroInformation()
|
||||
val oldModifier = (property as? AbstractNumberProperty?)?.modifier
|
||||
val source = this.source.displayName
|
||||
val newValue = when (oldModifier) {
|
||||
is AddValueTotal -> {
|
||||
oldModifier.steps
|
||||
}
|
||||
|
||||
is MultiplyBase -> {
|
||||
oldModifier.steps
|
||||
}
|
||||
|
||||
null -> TODO()
|
||||
}
|
||||
when(typeString()) {
|
||||
AddValueTotal::class.simpleName -> {
|
||||
(property as? AbstractNumberProperty?)?.modifier = AddValueTotal(newValue)
|
||||
}
|
||||
MultiplyBase::class.simpleName -> {
|
||||
(property as? AbstractNumberProperty?)?.modifier = MultiplyBase(newValue)
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
hero.save()
|
||||
hero.load()
|
||||
}.onSuccess {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, modifier")
|
||||
newLine()
|
||||
text("- ${oldModifier::class.simpleName}") {
|
||||
color = Color.RED.rgb
|
||||
}
|
||||
newLine()
|
||||
text("+ ${(property as? AbstractNumberProperty?)!!.modifier::class.simpleName}") {
|
||||
color = Color.GREEN.rgb
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, modifier")
|
||||
newLine()
|
||||
text("ERROR ${it.message}")
|
||||
}
|
||||
it.printStackTrace()
|
||||
}.also {
|
||||
ConfigManagerServer.sendHeroSettings(hero)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
literal("list") {
|
||||
argument<String>("values", StringArgumentType.string()) { valuesString ->
|
||||
suggestList {
|
||||
when (val modifier = (it.getHeroInformation().third as? AbstractNumberProperty?)?.modifier) {
|
||||
is AddValueTotal -> listOf("\"${modifier.steps}\"")
|
||||
is MultiplyBase -> listOf("\"${modifier.steps}\"")
|
||||
else -> listOf("\"SCHREIB NORISK AN\"")
|
||||
}
|
||||
}
|
||||
runs {
|
||||
val (hero, ability, property) = this.getHeroInformation()
|
||||
val modifier = (property as? AbstractNumberProperty?)?.modifier
|
||||
val possibleList = valuesString()
|
||||
var oldValue: Any? = null
|
||||
val source = this.source.displayName
|
||||
when (modifier) {
|
||||
is AddValueTotal -> {
|
||||
oldValue = modifier.steps
|
||||
modifier.steps = PlayerProperty.JSON.decodeFromString<List<Double>>(possibleList)
|
||||
}
|
||||
|
||||
is MultiplyBase -> {
|
||||
oldValue = modifier.steps
|
||||
modifier.steps = PlayerProperty.JSON.decodeFromString<List<Double>>(possibleList)
|
||||
}
|
||||
|
||||
null -> TODO()
|
||||
}
|
||||
|
||||
runCatching {
|
||||
hero.save()
|
||||
hero.load()
|
||||
}.onSuccess {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, modifier")
|
||||
newLine()
|
||||
|
||||
val newValue = when (modifier) {
|
||||
is AddValueTotal -> {
|
||||
modifier.steps
|
||||
}
|
||||
|
||||
is MultiplyBase -> {
|
||||
modifier.steps
|
||||
}
|
||||
}
|
||||
|
||||
text("- $oldValue") {
|
||||
color = Color.RED.rgb
|
||||
}
|
||||
newLine()
|
||||
text("+ $newValue") {
|
||||
color = Color.GREEN.rgb
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, modifier")
|
||||
newLine()
|
||||
text("ERROR ${it.message}")
|
||||
}
|
||||
it.printStackTrace()
|
||||
}.also {
|
||||
ConfigManagerServer.sendHeroSettings(hero)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LiteralCommandBuilder<ServerCommandSource>.levelScale() {
|
||||
literal("levelScale") {
|
||||
argument<Int>("value") { value ->
|
||||
suggestList {
|
||||
listOf(it.getHeroInformation().third.levelScale)
|
||||
}
|
||||
runs {
|
||||
val (hero, ability, property) = this.getHeroInformation()
|
||||
if (property is AbstractNumberProperty) {
|
||||
val oldValue = property.levelScale
|
||||
property.levelScale = value()
|
||||
val source = this.source.displayName
|
||||
runCatching {
|
||||
hero.save()
|
||||
hero.load()
|
||||
}.onSuccess {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, levelScale")
|
||||
newLine()
|
||||
text("from $oldValue to ${property.baseValue}")
|
||||
}
|
||||
}.onFailure {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, levelScale")
|
||||
newLine()
|
||||
text("ERROR ${it.message}")
|
||||
}
|
||||
it.printStackTrace()
|
||||
}.also {
|
||||
ConfigManagerServer.sendHeroSettings(hero)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun LiteralCommandBuilder<ServerCommandSource>.maxLevel() {
|
||||
literal("maxLevel") {
|
||||
argument<Int>("value") { value ->
|
||||
suggestList {
|
||||
listOf(it.getHeroInformation().third.maxLevel)
|
||||
}
|
||||
runs {
|
||||
val (hero, ability, property) = this.getHeroInformation()
|
||||
if (property is AbstractNumberProperty) {
|
||||
val oldValue = property.maxLevel
|
||||
property.maxLevel = value()
|
||||
val source = this.source.displayName
|
||||
runCatching {
|
||||
hero.save()
|
||||
hero.load()
|
||||
}.onSuccess {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, maxLevel")
|
||||
newLine()
|
||||
text("from $oldValue to ${property.baseValue}")
|
||||
}
|
||||
}.onFailure {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, maxLevel")
|
||||
newLine()
|
||||
text("ERROR ${it.message}")
|
||||
}
|
||||
it.printStackTrace()
|
||||
}.also {
|
||||
ConfigManagerServer.sendHeroSettings(hero)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LiteralCommandBuilder<ServerCommandSource>.baseValue() {
|
||||
literal("baseValue") {
|
||||
argument<String>("value") { value ->
|
||||
suggestList {
|
||||
listOf(it.getHeroInformation().third.baseValue)
|
||||
}
|
||||
runs {
|
||||
val (hero, ability, property) = this.getHeroInformation()
|
||||
if (property is AbstractNumberProperty) {
|
||||
val oldValue = property.baseValue
|
||||
property.baseValue = value().toDouble()
|
||||
val source = this.source.displayName
|
||||
runCatching {
|
||||
hero.save()
|
||||
hero.load()
|
||||
}.onSuccess {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, baseValue")
|
||||
newLine()
|
||||
text("from $oldValue to ${property.baseValue}")
|
||||
}
|
||||
}.onFailure {
|
||||
this.source.server.broadcastText {
|
||||
text(source)
|
||||
text(" changed ${hero.name}, ${ability.name}, ${property.name}, baseValue")
|
||||
newLine()
|
||||
text("ERROR ${it.message}")
|
||||
}
|
||||
it.printStackTrace()
|
||||
}.also {
|
||||
ConfigManagerServer.sendHeroSettings(hero)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package gg.norisk.heroes.common.cooldown
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CooldownInfo(
|
||||
val entityId: Int,
|
||||
val duration: Long,
|
||||
val startTime: Long?,
|
||||
val currentTime: Long,
|
||||
val multipleUsesInfo: MultipleUsesInfo?,
|
||||
val heroKey: String,
|
||||
val abilityKey: String,
|
||||
val endTime: Long?,
|
||||
var durationString: String? = null,
|
||||
) {
|
||||
val hasEnded get() = endTime?.let { System.nanoTime() > it } ?: true
|
||||
val remaining get() = endTime?.let { it - System.nanoTime() } ?: 0
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MultipleUsesInfo(
|
||||
val currentUse: Int,
|
||||
val maxUses: Int
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package gg.norisk.heroes.common.database
|
||||
|
||||
import net.fabricmc.api.EnvType
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
|
||||
abstract class AbstractProvider<K, V> {
|
||||
protected val cache = hashMapOf<K, V>()
|
||||
abstract suspend fun save(data: V)
|
||||
abstract suspend fun get(uuid: K): V
|
||||
|
||||
abstract suspend fun onPlayerJoin(player: ServerPlayerEntity)
|
||||
abstract suspend fun onPlayerLeave(player: ServerPlayerEntity)
|
||||
|
||||
abstract fun getCachedClient(uuid: K): V?
|
||||
|
||||
protected suspend fun getCached(uuid: K): V? {
|
||||
if (FabricLoader.getInstance().environmentType == EnvType.CLIENT && !FabricLoader.getInstance().isDevelopmentEnvironment) {
|
||||
val cachedOnClient = getCachedClient(uuid)
|
||||
if (cachedOnClient != null) {
|
||||
return cachedOnClient
|
||||
}
|
||||
}
|
||||
return cache[uuid]
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package gg.norisk.heroes.common.database.inventory
|
||||
|
||||
import gg.norisk.heroes.common.database.AbstractProvider
|
||||
import gg.norisk.heroes.common.player.InventorySorting
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.heroes.common.utils.PlayStyle
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractInventoryProvider(val playStyle: PlayStyle) : AbstractProvider<UUID, InventorySorting?>() {
|
||||
|
||||
override suspend fun onPlayerJoin(player: ServerPlayerEntity) {}
|
||||
override suspend fun onPlayerLeave(player: ServerPlayerEntity) {}
|
||||
|
||||
override fun getCachedClient(uuid: UUID): InventorySorting? {
|
||||
val ffaPlayer = MinecraftClient.getInstance().world?.getPlayerByUuid(uuid)?.ffaPlayer
|
||||
return ffaPlayer?.inventorySorting
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package gg.norisk.heroes.common.database.inventory
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.player.InventorySorting
|
||||
import gg.norisk.heroes.common.utils.PlayStyle
|
||||
import gg.norisk.heroes.common.utils.createIfNotExists
|
||||
import gg.norisk.heroes.server.config.ConfigManagerServer.JSON
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.util.*
|
||||
|
||||
class JsonInventoryProvider : AbstractInventoryProvider(PlayStyle.current) {
|
||||
private val file get() = HeroesManager.baseDirectory.resolve("player-inventory-database.json").createIfNotExists()
|
||||
|
||||
private fun loadDatabase(): MutableSet<InventorySorting> {
|
||||
var database = mutableSetOf<InventorySorting>()
|
||||
runCatching {
|
||||
if (file.exists()) {
|
||||
database = JSON.decodeFromString(file.readText())
|
||||
}
|
||||
}.onFailure {
|
||||
if (file.readText().isBlank()) {
|
||||
file.writeText("[]")
|
||||
}
|
||||
logger.info("Error Reading File ${file.absolutePath}")
|
||||
it.printStackTrace()
|
||||
}
|
||||
return database
|
||||
}
|
||||
|
||||
private suspend fun find(uuid: UUID): InventorySorting? {
|
||||
val database = loadDatabase()
|
||||
return database.find { it.userId == uuid && it.playStyle == playStyle }
|
||||
}
|
||||
|
||||
override suspend fun get(uuid: UUID): InventorySorting? {
|
||||
val inventory = getCached(uuid) ?: find(uuid)
|
||||
cache[uuid] = inventory
|
||||
return inventory
|
||||
}
|
||||
|
||||
override suspend fun save(data: InventorySorting?) {
|
||||
if (data == null) {
|
||||
logger.info("Cant save Inventory `null`")
|
||||
return
|
||||
}
|
||||
|
||||
if (data.main.isEmpty() && data.armor.isEmpty() && data.offhand.isEmpty()) {
|
||||
logger.info("${data.userId}'s inventory is empty. not saving")
|
||||
return
|
||||
}
|
||||
val database = loadDatabase()
|
||||
database.removeIf { it.userId == data.userId }
|
||||
database.add(data)
|
||||
if (!file.exists()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
file.createNewFile()
|
||||
}
|
||||
}
|
||||
file.writeText(JSON.encodeToString(database))
|
||||
logger.info("Saved InventorySorting for ${data.userId} to $file")
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package gg.norisk.heroes.common.database.player
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.database.AbstractProvider
|
||||
import gg.norisk.heroes.common.player.FFAPlayer
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.heroes.server.database.inventory.InventoryProvider
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import java.util.UUID
|
||||
|
||||
abstract class AbstractPlayerProvider : AbstractProvider<UUID, FFAPlayer>() {
|
||||
|
||||
override fun getCachedClient(uuid: UUID): FFAPlayer? {
|
||||
val ffaPlayer = MinecraftClient.getInstance().world?.getPlayerByUuid(uuid)?.ffaPlayer
|
||||
return ffaPlayer
|
||||
}
|
||||
|
||||
override suspend fun onPlayerJoin(player: ServerPlayerEntity) {
|
||||
val ffaPlayer = get(player.uuid)
|
||||
cache[player.uuid] = ffaPlayer
|
||||
player.ffaPlayer = ffaPlayer
|
||||
logger.info("Loaded Database Player ${player.gameProfile.name}")
|
||||
|
||||
InventoryProvider.onPlayerJoin(player)
|
||||
}
|
||||
|
||||
override suspend fun onPlayerLeave(player: ServerPlayerEntity) {
|
||||
if (cache.containsKey(player.uuid)) {
|
||||
save(cache.computeIfAbsent(player.uuid) { FFAPlayer(player.uuid) })
|
||||
cache.remove(player.uuid)
|
||||
logger.info("Saving Database Player ${player.gameProfile.name}")
|
||||
} else {
|
||||
logger.warn("Cache didn't contain any data about ${player.gameProfile.name} (${player.gameProfile.id}), not saving any data")
|
||||
}
|
||||
|
||||
InventoryProvider.onPlayerLeave(player)
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package gg.norisk.heroes.common.database.player
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.player.FFAPlayer
|
||||
import gg.norisk.heroes.server.config.ConfigManagerServer.JSON
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.util.*
|
||||
|
||||
class JsonPlayerProvider : AbstractPlayerProvider() {
|
||||
private val file get() = HeroesManager.baseDirectory.resolve("player-database.json")
|
||||
|
||||
private suspend fun loadDatabase(): MutableSet<FFAPlayer> {
|
||||
var database = mutableSetOf<FFAPlayer>()
|
||||
runCatching {
|
||||
if (file.exists()) {
|
||||
database = JSON.decodeFromString(file.readText())
|
||||
}
|
||||
}.onFailure {
|
||||
if (file.readText().isBlank()) {
|
||||
file.writeText("[]")
|
||||
}
|
||||
logger.error("Error Reading ${file.absolutePath}")
|
||||
it.printStackTrace()
|
||||
}
|
||||
return database
|
||||
}
|
||||
|
||||
private suspend fun findPlayer(uuid: UUID): FFAPlayer? {
|
||||
val database = loadDatabase()
|
||||
return database.find { it.uuid == uuid }
|
||||
}
|
||||
|
||||
override suspend fun get(uuid: UUID): FFAPlayer {
|
||||
val player = getCached(uuid) ?: findPlayer(uuid) ?: FFAPlayer(uuid)
|
||||
cache[uuid] = player
|
||||
return player
|
||||
}
|
||||
|
||||
override suspend fun save(data: FFAPlayer) {
|
||||
val database = loadDatabase()
|
||||
database.removeIf { it.uuid == data.uuid }
|
||||
database.add(data.copy(inventorySorting = null))
|
||||
if (!file.exists()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
file.createNewFile()
|
||||
}
|
||||
}
|
||||
file.writeText(JSON.encodeToString(database.map { it.copy(inventorySorting = null) }))
|
||||
logger.info("Saved Database Player for ${data.uuid} to $file")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package gg.norisk.heroes.common.events
|
||||
|
||||
import net.minecraft.client.input.Input
|
||||
import net.silkmc.silk.core.event.Event
|
||||
|
||||
open class AfterTickInputEvent(val input: Input)
|
||||
open class MouseScrollEvent(val window: Long, val horizontal: Double, val vertical: Double)
|
||||
|
||||
val mouseScrollEvent = Event.onlySync<MouseScrollEvent>()
|
||||
|
||||
val afterTickInputEvent = Event.onlySync<AfterTickInputEvent>()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package gg.norisk.heroes.common.events
|
||||
|
||||
import net.minecraft.client.render.VertexConsumerProvider
|
||||
import net.minecraft.client.render.chunk.SectionBuilder
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.entity.Entity
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.entity.data.DataTracker
|
||||
import net.minecraft.entity.data.TrackedData
|
||||
import net.silkmc.silk.core.event.Cancellable
|
||||
import net.silkmc.silk.core.event.Event
|
||||
import net.silkmc.silk.core.event.EventScopeProperty
|
||||
|
||||
object EntityEvents {
|
||||
class EntityTrackedDataSetEvent(val entity: Entity, val data: TrackedData<*>)
|
||||
open class LivingEntityEvent(val livingEntity: LivingEntity)
|
||||
class InitDataTrackerEvent(livingEntity: LivingEntity, val dataTracker: DataTracker) :
|
||||
LivingEntityEvent(livingEntity)
|
||||
|
||||
open class EntityRendererEvent(
|
||||
val entity: Entity,
|
||||
val f: Float,
|
||||
val g: Float,
|
||||
val matrixStack: MatrixStack,
|
||||
val vertexConsumerProvider: VertexConsumerProvider,
|
||||
val light: Int
|
||||
) : Cancellable {
|
||||
override val isCancelled = EventScopeProperty(false)
|
||||
}
|
||||
|
||||
open class ComputeFallDamageEvent(
|
||||
val fallDistance: Float,
|
||||
val damageMultiplier: Float,
|
||||
val originalFallDamage: Int,
|
||||
livingEntity: LivingEntity
|
||||
) : LivingEntityEvent(
|
||||
livingEntity
|
||||
) {
|
||||
var fallDamage: Int? = null
|
||||
}
|
||||
|
||||
val computeFallDamageEvent = Event.onlySync<ComputeFallDamageEvent>()
|
||||
val onTrackedDataSetEvent = Event.onlySync<EntityTrackedDataSetEvent>()
|
||||
val entityRendererEvent = Event.onlySync<EntityRendererEvent>()
|
||||
val livingEntityTickMovementEvent = Event.onlySync<LivingEntityEvent>()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package gg.norisk.heroes.common.events
|
||||
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.silkmc.silk.core.annotations.ExperimentalSilkApi
|
||||
import net.silkmc.silk.core.event.Cancellable
|
||||
import net.silkmc.silk.core.event.Event
|
||||
import net.silkmc.silk.core.event.EventScopeProperty
|
||||
|
||||
@OptIn(ExperimentalSilkApi::class)
|
||||
object HeroEvents {
|
||||
open class HeroChangeEvent(val player: PlayerEntity)
|
||||
|
||||
val heroChangeEvent = Event.onlySync<HeroChangeEvent>()
|
||||
|
||||
open class HeroSelectEvent(val player: PlayerEntity, val hero: Hero, var canSelect: Boolean = false)
|
||||
|
||||
val heroSelectEvent = Event.onlySync<HeroSelectEvent>()
|
||||
|
||||
open class PreKitEditorEvent(val player: ServerPlayerEntity) : Cancellable {
|
||||
override val isCancelled: EventScopeProperty<Boolean> = EventScopeProperty(false)
|
||||
}
|
||||
|
||||
val preKitEditorEvent = Event.onlySync<PreKitEditorEvent>()
|
||||
|
||||
open class HeroDeathEvent(val player: ServerPlayerEntity, var isValidDeath: Boolean)
|
||||
|
||||
val heroDeathEvent = Event.onlySync<HeroDeathEvent>()
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package gg.norisk.heroes.common.ffa
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.isServer
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.HeroesManager.prefix
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import gg.norisk.heroes.common.player.InventorySorting.Companion.loadInventory
|
||||
import gg.norisk.heroes.common.events.HeroEvents
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.heroes.common.networking.dto.HeroSelectorPacket
|
||||
import gg.norisk.heroes.common.player.InventorySorting
|
||||
import gg.norisk.heroes.common.player.InventorySorting.Companion.CURRENT_VERSION
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.heroes.common.utils.PlayStyle
|
||||
import gg.norisk.heroes.server.database.player.PlayerProvider
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.Items
|
||||
import net.minecraft.network.packet.s2c.play.PositionFlag
|
||||
import net.minecraft.particle.ItemStackParticleEffect
|
||||
import net.minecraft.particle.ParticleTypes
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
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.BlockPos
|
||||
import net.minecraft.util.math.Vec3d
|
||||
import net.minecraft.world.GameMode
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
import net.silkmc.silk.core.text.broadcastText
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
object KitEditorManager {
|
||||
var world: ServerWorld? = null
|
||||
var resetInventory: (PlayerEntity) -> Unit = {
|
||||
it.inventory.clear()
|
||||
it.inventory.main.set(0, Items.STONE_SWORD.defaultStack)
|
||||
}
|
||||
var onBack: (ServerPlayerEntity) -> Unit = {
|
||||
it.teleport(it.server.overworld, 0.0, 100.0, 0.0, PositionFlag.VALUES, 0f, 0f)
|
||||
}
|
||||
private val kitEditorSpawn = Vec3d(0.5, 90.5, 0.5)
|
||||
|
||||
val hasKitWorld get() = world != null
|
||||
|
||||
fun init() {
|
||||
if (!isServer) return
|
||||
ServerLifecycleEvents.SERVER_STARTED.register {
|
||||
for (world in it.worlds) {
|
||||
logger.info("Found Worlds: $world ${world.registryKey.value}")
|
||||
if (world.registryKey.value == "kit-editor".toId()) {
|
||||
this.world = world
|
||||
break
|
||||
}
|
||||
}
|
||||
logger.info("Found Kit Editor World $world")
|
||||
}
|
||||
|
||||
ServerTickEvents.END_WORLD_TICK.register {
|
||||
if (it == world) {
|
||||
for (player in it.players) {
|
||||
val distance = player.squaredDistanceTo(kitEditorSpawn)
|
||||
//player.sendMessage("Distance: $distance".literal)
|
||||
if (distance > 300) {
|
||||
teleportToKitEditorSpawn(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerWorldEvents.LOAD.register(ServerWorldEvents.Load { server, world ->
|
||||
if (world.registryKey.value == "kit-editor".toId()) {
|
||||
this.world = world
|
||||
this.world?.worldBorder?.size = 10000.0
|
||||
server.broadcastText("LOADED KIT EDITOR WORLD")
|
||||
server.broadcastText("LOADED KIT EDITOR WORLD")
|
||||
/*/world.timeOfDay = 6000
|
||||
world.gameRules.get(GameRules.DO_DAYLIGHT_CYCLE).set(false, server)
|
||||
world.gameRules.get(GameRules.DO_WEATHER_CYCLE).set(false, server)
|
||||
world.gameRules.get(GameRules.SPECTATORS_GENERATE_CHUNKS).set(false, server)
|
||||
world.gameRules.get(GameRules.DO_MOB_SPAWNING).set(false, server)
|
||||
world.gameRules.get(GameRules.DO_ENTITY_DROPS).set(false, server)*/
|
||||
}
|
||||
})
|
||||
|
||||
ServerEntityEvents.ENTITY_LOAD.register(ServerEntityEvents.Load { entity, world ->
|
||||
val player = entity as? ServerPlayerEntity? ?: return@Load
|
||||
if (world == this.world) {
|
||||
player.changeGameMode(GameMode.ADVENTURE)
|
||||
mcCoroutineTask(sync = false, client = false) {
|
||||
val ffaPlayer = PlayerProvider.get(player.uuid)
|
||||
println("Loaded ${ffaPlayer}")
|
||||
if (ffaPlayer.inventorySorting == null) {
|
||||
resetInventory.invoke(player)
|
||||
ffaPlayer.inventorySorting = player.toDatabaseInventory()
|
||||
}
|
||||
mcCoroutineTask(sync = true, client = false) {
|
||||
player.loadInventory(ffaPlayer.inventorySorting!!)
|
||||
}
|
||||
entity.sendMessage(Text.translatable("ffa.mechanic.kit.editor.enter"))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ServerEntityEvents.ENTITY_UNLOAD.register(ServerEntityEvents.Unload { entity, world ->
|
||||
val player = entity as? ServerPlayerEntity? ?: return@Unload
|
||||
if (world == this.world) {
|
||||
val inventory = player.toDatabaseInventory()
|
||||
mcCoroutineTask(sync = false, client = true) {
|
||||
val ffaPlayer = PlayerProvider.get(player.uuid)
|
||||
ffaPlayer.inventorySorting = inventory
|
||||
player.ffaPlayer = ffaPlayer
|
||||
mcCoroutineTask(sync = false, client = false) {
|
||||
PlayerProvider.save(ffaPlayer)
|
||||
println("Saved ${ffaPlayer}")
|
||||
entity.sendMessage(Text.translatable("ffa.mechanic.kit.editor.save"))
|
||||
}
|
||||
entity.sendMessage(Text.translatable("ffa.mechanic.kit.editor.left"))
|
||||
mcCoroutineTask(sync = true, client = false) {
|
||||
player.loadInventory(inventory)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Networking.c2sKitEditorRequestPacket.receiveOnServer { packet, context ->
|
||||
mcCoroutineTask(sync = true, client = false) {
|
||||
val player = context.player
|
||||
val kitEditorWorld = world ?: return@mcCoroutineTask
|
||||
val event = HeroEvents.PreKitEditorEvent(player)
|
||||
HeroEvents.preKitEditorEvent.invoke(event)
|
||||
if (!event.isCancelled.get()) {
|
||||
Networking.s2cHeroSelectorPacket.send(
|
||||
HeroSelectorPacket(
|
||||
emptyList(),
|
||||
false,
|
||||
hasKitWorld
|
||||
), player
|
||||
)
|
||||
teleportToKitEditorSpawn(player)
|
||||
player.sendMessage(literalText {
|
||||
text(prefix)
|
||||
text(Text.translatable("ffa.mechanic.kit.editor.inventory_instruction"))
|
||||
})
|
||||
kitEditorWorld.setBlockState(BlockPos(0, 89, 0), Blocks.GOLD_BLOCK.defaultState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onBack(player: ServerPlayerEntity) {
|
||||
onBack.invoke(player)
|
||||
}
|
||||
|
||||
fun onReset(player: ServerPlayerEntity) {
|
||||
resetInventory.invoke(player)
|
||||
}
|
||||
|
||||
private fun teleportToKitEditorSpawn(player: ServerPlayerEntity) {
|
||||
player.teleport(
|
||||
world,
|
||||
kitEditorSpawn.x,
|
||||
kitEditorSpawn.y,
|
||||
kitEditorSpawn.z,
|
||||
PositionFlag.VALUES,
|
||||
0f,
|
||||
0f
|
||||
)
|
||||
player.playSoundToPlayer(SoundEvents.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 0.3f, 1f)
|
||||
player.serverWorld.syncWorldEvent(2003, player.blockPos, 0)
|
||||
}
|
||||
|
||||
private fun spawnEnderEyeBreak(blockPos: BlockPos, player: ServerPlayerEntity) {
|
||||
val d: Double = blockPos.getX().toDouble() + 0.5
|
||||
val e: Double = blockPos.getY().toDouble()
|
||||
val f: Double = blockPos.getZ().toDouble() + 0.5
|
||||
|
||||
for (k in 0..7) {
|
||||
player.serverWorld.spawnParticles(
|
||||
player,
|
||||
ItemStackParticleEffect(ParticleTypes.ITEM, ItemStack(Items.ENDER_EYE)),
|
||||
false,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
1,
|
||||
player.world.random.nextGaussian() * 0.15,
|
||||
player.world.random.nextDouble() * 0.2,
|
||||
player.world.random.nextGaussian() * 0.15,
|
||||
0.0
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
var g = 0.0
|
||||
while (g < Math.PI * 2) {
|
||||
player.serverWorld.spawnParticles(
|
||||
player,
|
||||
ParticleTypes.PORTAL,
|
||||
false,
|
||||
d + cos(g) * 5.0,
|
||||
e - 0.4,
|
||||
f + sin(g) * 5.0,
|
||||
1,
|
||||
cos(g) * -5.0,
|
||||
0.0,
|
||||
sin(g) * -5.0,
|
||||
0.0
|
||||
)
|
||||
player.serverWorld.spawnParticles(
|
||||
player,
|
||||
ParticleTypes.PORTAL,
|
||||
false,
|
||||
d + cos(g) * 5.0,
|
||||
e - 0.4,
|
||||
f + sin(g) * 5.0,
|
||||
1,
|
||||
cos(g) * -5.0,
|
||||
0.0,
|
||||
sin(g) * -5.0,
|
||||
0.0
|
||||
)
|
||||
g += Math.PI / 20
|
||||
}
|
||||
}
|
||||
|
||||
private fun ServerPlayerEntity.toDatabaseInventory(): InventorySorting {
|
||||
return InventorySorting(
|
||||
uuid,
|
||||
PlayStyle.current,
|
||||
CURRENT_VERSION,
|
||||
inventory.armor.toTypedArray(),
|
||||
inventory.offHand.toTypedArray(),
|
||||
inventory.main.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package gg.norisk.heroes.common.ffa.experience
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.HeroesManager.prefix
|
||||
import gg.norisk.heroes.common.player.ffaPlayer
|
||||
import gg.norisk.heroes.common.utils.createIfNotExists
|
||||
import gg.norisk.heroes.server.config.ConfigManagerServer.JSON
|
||||
import gg.norisk.heroes.server.database.player.PlayerProvider
|
||||
import kotlinx.serialization.encodeToString
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
import net.silkmc.silk.core.text.literalText
|
||||
import java.awt.Color
|
||||
|
||||
object Experience {
|
||||
private val configFile = HeroesManager.baseDirectory.resolve("xp-config.json").createIfNotExists()
|
||||
|
||||
fun init() {
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
fun add(player: ServerPlayerEntity, reason: ExperienceReason, printMessage: Boolean = false) {
|
||||
mcCoroutineTask(sync = false, client = false) {
|
||||
val receivedXp = reason.value
|
||||
val ffaPlayer = PlayerProvider.get(player.uuid)
|
||||
ffaPlayer.xp += receivedXp
|
||||
player.ffaPlayer = ffaPlayer
|
||||
|
||||
if (printMessage) {
|
||||
player.sendMessage(literalText {
|
||||
text(prefix)
|
||||
text("+$receivedXp XP") {
|
||||
color = Color.GREEN.rgb
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
PlayerProvider.save(ffaPlayer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadConfig() {
|
||||
val currentConfig = loadFromFile()
|
||||
createDefaultConfig(currentConfig.isEmpty())
|
||||
|
||||
currentConfig.forEach { configReason ->
|
||||
val reason = ExperienceRegistry.reasons.firstOrNull { reason -> reason.key == configReason.key }
|
||||
if (reason == null) {
|
||||
logger.warn("Found invalid reason with key `${configReason.key}` in config")
|
||||
return@forEach
|
||||
}
|
||||
reason.value = configReason.value
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFromFile(): MutableSet<ExperienceReason> {
|
||||
return runCatching<MutableSet<ExperienceReason>> {
|
||||
JSON.decodeFromString(configFile.readText())
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.onSuccess {
|
||||
logger.info("Loaded Xp Config")
|
||||
}.getOrDefault(mutableSetOf())
|
||||
}
|
||||
|
||||
private fun createDefaultConfig(force: Boolean) {
|
||||
if (force) {
|
||||
configFile.createNewFile()
|
||||
configFile.writeText(JSON.encodeToString(ExperienceRegistry.reasons))
|
||||
logger.info("Created Default Xp Config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PlayerEntity.addXp(reason: ExperienceReason, printMessage: Boolean = false) {
|
||||
Experience.add(this as ServerPlayerEntity, reason, printMessage)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package gg.norisk.heroes.common.ffa.experience
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ExperienceReason(val key: String, var value: Int)
|
||||
@@ -0,0 +1,21 @@
|
||||
package gg.norisk.heroes.common.ffa.experience
|
||||
|
||||
object ExperienceRegistry {
|
||||
val reasons = mutableSetOf<ExperienceReason>()
|
||||
|
||||
val KILLED_PLAYER = register("killed_player", 200)
|
||||
val PLAYER_DEATH = register("player_death", 25)
|
||||
val SOUP_EATEN = register("soup_eaten", 5)
|
||||
val SMALL_ABILITY_USE = register("small_ability_use", 5)
|
||||
val RECRAFT = register("soup_recraft", 5)
|
||||
val END_KILL_STREAK = register("end_kill_streak", 1000)
|
||||
val DEALING_DAMAGE = register("dealing_damage", 1)
|
||||
val TAKING_DAMAGE = register("taking_damage", 1)
|
||||
val IDLE = register("idle", 1)
|
||||
|
||||
fun register(key: String, value: Int): ExperienceReason {
|
||||
return ExperienceReason(key, value).apply {
|
||||
reasons.add(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package gg.norisk.heroes.common.hero
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import gg.norisk.heroes.common.ability.PlayerProperty.Companion.JSON
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Identifier
|
||||
import java.io.File
|
||||
|
||||
open class Hero(val name: String) {
|
||||
companion object {
|
||||
/**
|
||||
* Creates a new lazy hero delegate.
|
||||
*
|
||||
* @param config the config of this hero
|
||||
* @param builder the [HeroBuilder]
|
||||
*/
|
||||
inline operator fun invoke(
|
||||
name: String,
|
||||
crossinline builder: HeroBuilder.() -> Unit
|
||||
) = lazy {
|
||||
Hero(name).apply {
|
||||
HeroBuilder(this).apply(builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var internalCallbacks = InternalCallbacks()
|
||||
val internalKey = name.lowercase().replace(' ', '_')
|
||||
val icon = "textures/hero/${internalKey}/icon.png".toId()
|
||||
val description = Text.translatable("text.hero.$internalKey.description")
|
||||
var overlaySkin: Identifier? = null
|
||||
|
||||
val abilities = hashMapOf<String, AbstractAbility<*>>()
|
||||
var color: Int = 0x4291AD
|
||||
|
||||
fun registerAbility(ability: AbstractAbility<*>) {
|
||||
ability.hero = this
|
||||
// REMOVED AbilityKeyBindManager.initializeKeyBind(ability)
|
||||
abilities[ability.internalKey] = ability
|
||||
}
|
||||
|
||||
fun getUsableAbilities(player: PlayerEntity): List<AbstractAbility<*>> {
|
||||
return abilities.values.toList()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HeroJson(
|
||||
val internalKey: String,
|
||||
val properties: Map<String, JsonArray>
|
||||
)
|
||||
|
||||
fun load(heroJson: HeroJson? = null) {
|
||||
if (baseFile.exists()) {
|
||||
runCatching {
|
||||
val loaded = heroJson ?: JSON.decodeFromString<HeroJson>(baseFile.readText())
|
||||
for ((key, element) in loaded.properties) {
|
||||
val ability = abilities[key]
|
||||
for (jsonElement in element) {
|
||||
val name = jsonElement.jsonObject["name"] ?: continue
|
||||
val property = ability?.getAllProperties()?.find { it.name == name.jsonPrimitive.content }
|
||||
property?.fromJson(jsonElement.toString())
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
logger.error("Error Loading Hero $internalKey ${it.message}")
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val baseFolder get() = File(HeroesManager.baseDirectory, "heroes/hero").apply { mkdirs() }
|
||||
private val baseFile get() = File(baseFolder, "$internalKey.json")
|
||||
|
||||
fun save() {
|
||||
baseFile.writeText(JSON.encodeToString(toHeroJson()))
|
||||
logger.info("Successfully saved $internalKey")
|
||||
}
|
||||
|
||||
fun toHeroJson(): HeroJson {
|
||||
val properties = buildMap {
|
||||
for ((key, ability) in abilities) {
|
||||
put(key, JsonArray(ability.getAllProperties().map { it.toJsonElement() }))
|
||||
}
|
||||
}
|
||||
return HeroJson(internalKey, properties)
|
||||
}
|
||||
|
||||
inner class InternalCallbacks {
|
||||
var getSkin: ((player: PlayerEntity) -> Identifier?)? = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package gg.norisk.heroes.common.hero
|
||||
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.util.Identifier
|
||||
import net.silkmc.silk.core.event.Event
|
||||
import net.silkmc.silk.core.event.EventPriority
|
||||
import net.silkmc.silk.core.event.MutableEventScope
|
||||
|
||||
class HeroBuilder(val hero: Hero) {
|
||||
var color: Int
|
||||
get() = hero.color
|
||||
set(value) {
|
||||
hero.color = value
|
||||
}
|
||||
var overlaySkin: Identifier?
|
||||
get() = hero.overlaySkin
|
||||
set(value) {
|
||||
hero.overlaySkin = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given [callback] if the player of the
|
||||
* [playerGetter] is the hero.
|
||||
*/
|
||||
inline fun <reified T> Event<T>.heroPlayerEvent(
|
||||
crossinline playerGetter: (T) -> PlayerEntity?,
|
||||
priority: EventPriority = EventPriority.NORMAL,
|
||||
crossinline callback: context(MutableEventScope) (event: T) -> Unit
|
||||
) {
|
||||
this.listen(priority) {
|
||||
val player = playerGetter(it) ?: return@listen
|
||||
if (player.getHero() != hero) return@listen
|
||||
callback.invoke(MutableEventScope, it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given [callback] when the event is called
|
||||
*/
|
||||
inline fun <reified T> Event<T>.heroEvent(
|
||||
priority: EventPriority = EventPriority.NORMAL,
|
||||
crossinline callback: context(MutableEventScope) (event: T) -> Unit
|
||||
) {
|
||||
this.listen(priority) {
|
||||
callback.invoke(MutableEventScope, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSkin(callback: (player: PlayerEntity) -> Identifier) {
|
||||
hero.internalCallbacks.getSkin = callback
|
||||
}
|
||||
|
||||
fun ability(ability: AbstractAbility<*>) {
|
||||
hero.registerAbility(ability)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package gg.norisk.heroes.common.hero
|
||||
|
||||
import gg.norisk.datatracker.entity.getSyncedData
|
||||
import gg.norisk.datatracker.entity.setSyncedData
|
||||
import gg.norisk.heroes.common.HeroesManager.isClient
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.hero.HeroManager.HERO_KEY
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import gg.norisk.heroes.common.hero.ability.task.AbilityCoroutineManager
|
||||
import gg.norisk.heroes.server.config.ConfigManagerServer
|
||||
import gg.norisk.heroes.server.hero.ability.AbilityManagerServer
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.silkmc.silk.core.Silk.server
|
||||
import net.silkmc.silk.core.text.broadcastText
|
||||
|
||||
object HeroManager {
|
||||
val registeredHeroes: MutableMap<String, Hero> = mutableMapOf()
|
||||
const val HERO_KEY = "hero"
|
||||
|
||||
fun getHero(internalKey: String) = registeredHeroes[internalKey.replace(' ', '_')]
|
||||
|
||||
fun registerHero(hero: Hero): Boolean {
|
||||
logger.info("Register Hero ${hero.name}... on $this")
|
||||
registeredHeroes[hero.internalKey] = hero
|
||||
hero.abilities.values.forEach(AbstractAbility<*>::init)
|
||||
return true
|
||||
}
|
||||
|
||||
fun reloadHeroes(vararg heroes: Hero) {
|
||||
for (hero in heroes) {
|
||||
runCatching {
|
||||
hero.load()
|
||||
hero.save()
|
||||
ConfigManagerServer.sendHeroSettings(hero)
|
||||
}.onSuccess {
|
||||
server?.broadcastText("Loaded Hero ${hero.name}")
|
||||
}.onFailure {
|
||||
server?.broadcastText("Error loading Hero ${hero.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PlayerEntity.setHero(hero: Hero?) {
|
||||
if (isClient && this == MinecraftClient.getInstance().player) {
|
||||
AbilityCoroutineManager.cancelClientJobs()
|
||||
} else {
|
||||
AbilityCoroutineManager.cancelServerJobs(this)
|
||||
AbilityManagerServer.clear(this)
|
||||
}
|
||||
getHero()?.abilities?.forEach { (name, ability) ->
|
||||
ability.clearCooldown(this)
|
||||
ability.onDisable(this)
|
||||
}
|
||||
this.setSyncedData(HERO_KEY, hero?.internalKey ?: "NONE")
|
||||
getHero()?.abilities?.forEach { (name, ability) -> ability.onEnable(this) }
|
||||
}
|
||||
|
||||
fun PlayerEntity.getHero(): Hero? {
|
||||
return HeroManager.getHero(this.getSyncedData<String>(HERO_KEY) ?: "NONE")
|
||||
}
|
||||
|
||||
fun PlayerEntity.isHero(hero: Hero?) = this.getHero() == hero
|
||||
@@ -0,0 +1,33 @@
|
||||
package gg.norisk.heroes.common.hero.ability
|
||||
|
||||
import gg.norisk.heroes.common.serialization.UUIDSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
data class AbilityPacket<C : AbilityPacketDescription>(
|
||||
@Serializable(with = UUIDSerializer::class)
|
||||
val playerUuid: UUID,
|
||||
val heroKey: String,
|
||||
val abilityKey: String,
|
||||
val description: C
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SkillPropertyPacket(
|
||||
val heroKey: String,
|
||||
val abilityKey: String,
|
||||
val propertyKey: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
sealed class AbilityPacketDescription {
|
||||
@Serializable
|
||||
object Start : AbilityPacketDescription()
|
||||
|
||||
@Serializable
|
||||
open class Use : AbilityPacketDescription()
|
||||
|
||||
@Serializable
|
||||
object End : AbilityPacketDescription()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package gg.norisk.heroes.common.hero.ability
|
||||
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
|
||||
class AbilityScope(val executingPlayer: PlayerEntity) {
|
||||
var applyCooldown = true
|
||||
var broadcastPacket = false
|
||||
|
||||
fun cancelCooldown() {
|
||||
applyCooldown = false
|
||||
}
|
||||
|
||||
fun applyCooldown() {
|
||||
applyCooldown = true
|
||||
}
|
||||
|
||||
fun cancelBroadcasting() {
|
||||
broadcastPacket = false
|
||||
}
|
||||
|
||||
fun broadcast() {
|
||||
broadcastPacket = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package gg.norisk.heroes.common.hero.ability
|
||||
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.ability.*
|
||||
import gg.norisk.heroes.common.ability.operation.AddValueTotal
|
||||
import gg.norisk.heroes.common.ability.operation.MultiplyBase
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import gg.norisk.heroes.common.cooldown.CooldownInfo
|
||||
import gg.norisk.heroes.common.cooldown.MultipleUsesInfo
|
||||
import gg.norisk.heroes.common.ffa.experience.ExperienceRegistry
|
||||
import gg.norisk.heroes.common.ffa.experience.addXp
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import gg.norisk.heroes.common.networking.Networking
|
||||
import gg.norisk.heroes.server.config.ConfigManagerServer.JSON
|
||||
import gg.norisk.utils.DevUtils.uniqueId
|
||||
import io.wispforest.owo.ui.component.Components
|
||||
import io.wispforest.owo.ui.core.Component
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import net.fabricmc.api.EnvType
|
||||
import net.fabricmc.api.Environment
|
||||
import net.minecraft.client.option.KeyBinding
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.Items
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Identifier
|
||||
import net.silkmc.silk.core.task.mcCoroutineTask
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.time.Duration.Companion.nanoseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
abstract class AbstractAbility<T : Any>(val name: String) {
|
||||
lateinit var hero: Hero
|
||||
val internalKey = name.lowercase().replace(' ', '_')
|
||||
val description by lazy { Text.translatable("hero.${hero.internalKey}.ability.$internalKey.description") }
|
||||
var condition: ((PlayerEntity) -> Boolean)? = null
|
||||
|
||||
var showInKeybindHud: Boolean = true
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
var keyBind: KeyBinding? = null
|
||||
var properties = listOf<PlayerProperty<*>>()
|
||||
private val cooldowns = ConcurrentHashMap<UUID, CooldownInfo>()
|
||||
private val cooldownTasks = ConcurrentHashMap<UUID, Job>()
|
||||
var cooldownProperty: CooldownProperty = buildCooldown(5.0, 5, AddValueTotal(-0.1, -0.4, -0.2, -0.8, -1.5, -1.0))
|
||||
var usageProperty: AbstractUsageProperty = SingleUseProperty(0.0, 0, "Use", MultiplyBase(listOf(0.0))).apply {
|
||||
icon = {
|
||||
Components.item(Items.STONE_PICKAXE.defaultStack)
|
||||
}
|
||||
}
|
||||
|
||||
//atm used for holdcooldown
|
||||
open val extraProperties: List<PlayerProperty<*>> = emptyList()
|
||||
private var allProperties: List<PlayerProperty<*>>? = null
|
||||
|
||||
fun getAllProperties(): List<PlayerProperty<*>> {
|
||||
//Todo das cachen?
|
||||
return buildList {
|
||||
add(cooldownProperty)
|
||||
add(usageProperty)
|
||||
addAll(extraProperties)
|
||||
addAll(properties)
|
||||
}
|
||||
}
|
||||
|
||||
open fun getCustomActivation(): Text {
|
||||
return Text.translatable("heroes.ability.$internalKey.custom_activation")
|
||||
}
|
||||
|
||||
open fun hasUnlocked(player: PlayerEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
ähnlich wie condition aber nur ServerSide Condition Check
|
||||
*/
|
||||
open fun canUse(player: ServerPlayerEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
open fun getUnlockCondition(): Text {
|
||||
return Text.empty()
|
||||
}
|
||||
|
||||
open fun getIconComponent(): Component {
|
||||
return Components.item(Items.DIAMOND_SWORD.defaultStack)
|
||||
}
|
||||
|
||||
open fun getBackgroundTexture(): Identifier {
|
||||
return Identifier.of("textures/block/stone.png")
|
||||
}
|
||||
|
||||
fun handleCooldown(player: ServerPlayerEntity): Boolean {
|
||||
if (hasCooldown(player)) {
|
||||
// Client an den cooldown erinnern!
|
||||
getCooldown(player)?.let { cooldownInfo ->
|
||||
//player.sendDebugMessage("Cooldown: ${cooldownInfo.remaining}".literal)
|
||||
Networking.s2cCooldownPacket.send(cooldownInfo, player)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun removeCooldown(player: PlayerEntity) {
|
||||
cooldowns.remove(player.uuid)
|
||||
}
|
||||
|
||||
fun setCooldown(cooldownInfo: CooldownInfo, player: PlayerEntity) {
|
||||
if (cooldownInfo.duration == 0L && cooldownInfo.startTime == 0L && cooldownInfo.currentTime == 0L) {
|
||||
cooldowns.remove(player.uuid)
|
||||
} else {
|
||||
cooldowns[player.uuid] = cooldownInfo
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCooldown(player: PlayerEntity) {
|
||||
cooldownTasks[player.uuid]?.cancel()
|
||||
if (cooldowns.remove(player.uuid) != null) {
|
||||
if (player is ServerPlayerEntity) {
|
||||
Networking.s2cCooldownPacket.send(
|
||||
CooldownInfo(
|
||||
player.id,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
hero.internalKey,
|
||||
internalKey,
|
||||
null
|
||||
), player
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addCooldown(player: PlayerEntity) {
|
||||
if (player !is ServerPlayerEntity) return
|
||||
if (cooldownProperty.name == "NoCooldown") return
|
||||
//has cooldown
|
||||
if (handleCooldown(player)) return
|
||||
val uuid = player.uuid
|
||||
val currentTime = System.nanoTime()
|
||||
var startTime: Long? = null
|
||||
|
||||
var multipleUsesInfo: MultipleUsesInfo? = null
|
||||
if (usageProperty is MultiUseProperty) {
|
||||
val currentUse = (usageProperty as MultiUseProperty).uses.getOrDefault(player.uuid, 0) + 1
|
||||
(usageProperty as MultiUseProperty).uses[player.uuid] = currentUse
|
||||
val maxUses = usageProperty.getValue(player.uuid).toInt()
|
||||
multipleUsesInfo = MultipleUsesInfo(currentUse, maxUses)
|
||||
if (currentUse == maxUses) {
|
||||
startTime = currentTime
|
||||
(usageProperty as MultiUseProperty).uses[player.uuid] = 0
|
||||
}
|
||||
} else if (usageProperty is SingleUseProperty) {
|
||||
startTime = currentTime
|
||||
}
|
||||
|
||||
val value = cooldownProperty.getValue(player.uuid)
|
||||
logger.info("Sending Cooldown $value to ${player.gameProfile.name}")
|
||||
//player.sendDebugMessage("Value: $value Level: ${cooldownProperty.getLevelInfo(player.uuid)}".literal)
|
||||
//player.sendDebugMessage("Property: $cooldownProperty".literal)
|
||||
val duration = value.seconds.inWholeNanoseconds
|
||||
val cooldownInfo = CooldownInfo(
|
||||
player.id,
|
||||
duration,
|
||||
startTime,
|
||||
currentTime,
|
||||
multipleUsesInfo,
|
||||
hero.internalKey,
|
||||
internalKey,
|
||||
startTime?.let { it + duration }
|
||||
).apply {
|
||||
this.durationString = getCooldownText(this)
|
||||
}
|
||||
player.addXp(ExperienceRegistry.SMALL_ABILITY_USE, true)
|
||||
cooldowns[uuid] = cooldownInfo
|
||||
Networking.s2cCooldownPacket.send(cooldownInfo, player)
|
||||
|
||||
//player.sendDebugMessage("Sending Cooldown: $cooldownInfo".literal)
|
||||
|
||||
if (cooldownInfo.remaining > 0) {
|
||||
cooldownTasks[uuid]?.cancel()
|
||||
cooldownTasks[uuid] = mcCoroutineTask(sync = false, client = false) {
|
||||
//NO DELAY IN CREATIVE MODE FOR TESTING?
|
||||
//player.sendMessage("START".literal.withColor(Color.red.rgb))
|
||||
//player.sendMessage(getCooldownText(cooldownInfo)?.literal)
|
||||
if (!player.isCreative) {
|
||||
delay(value.seconds.inWholeMilliseconds)
|
||||
}
|
||||
//player.sendMessage("END".literal.withColor(Color.red.rgb))
|
||||
//player.sendMessage(getCooldownText(cooldownInfo)?.literal)
|
||||
cooldowns -= uuid
|
||||
Networking.s2cCooldownPacket.send(
|
||||
CooldownInfo(
|
||||
player.id,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
multipleUsesInfo,
|
||||
hero.internalKey,
|
||||
internalKey,
|
||||
null
|
||||
), player
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCooldown(player: PlayerEntity): CooldownInfo? {
|
||||
return cooldowns[player.uuid]
|
||||
}
|
||||
|
||||
fun hasCooldown(player: PlayerEntity): Boolean {
|
||||
val cooldownInfo = getCooldown(player) ?: return false
|
||||
//player.sendDebugMessage("Cooldown: $cooldownInfo".literal)
|
||||
return cooldownInfo.remaining > 0
|
||||
}
|
||||
|
||||
fun init() {
|
||||
for (property in getAllProperties()) {
|
||||
property.ability = this
|
||||
property.hero = this.hero
|
||||
}
|
||||
}
|
||||
|
||||
open fun onEnable(player: PlayerEntity) {
|
||||
|
||||
}
|
||||
|
||||
open fun onDisable(player: PlayerEntity) {
|
||||
}
|
||||
|
||||
open fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
|
||||
}
|
||||
|
||||
open fun onTick(player: PlayerEntity) {
|
||||
}
|
||||
|
||||
protected fun buildMultipleUses(baseValue: Double, maxLevel: Int, operation: Operation): MultiUseProperty {
|
||||
return MultiUseProperty(baseValue, maxLevel, "Use", operation)
|
||||
}
|
||||
|
||||
protected fun buildCooldown(baseValue: Double, maxLevel: Int, operation: Operation): CooldownProperty {
|
||||
return CooldownProperty(baseValue, maxLevel, "Cooldown", operation)
|
||||
}
|
||||
|
||||
protected fun buildNoCooldown(): CooldownProperty {
|
||||
return CooldownProperty(0.0, 0, "NoCooldown", MultiplyBase())
|
||||
}
|
||||
|
||||
fun getCooldownText(cooldown: CooldownInfo): String? {
|
||||
val remaining = cooldown.remaining
|
||||
if (remaining > 0) {
|
||||
val builder = StringBuilder()
|
||||
remaining.nanoseconds.toComponents { days, hours, minutes, seconds, nanoseconds ->
|
||||
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(".")
|
||||
builder.append((nanoseconds / 1_000_000).toString().padStart(3, '0').take(1)) // Nur 2 Stellen
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
if (usageProperty is MultiUseProperty) {
|
||||
val multipleUseInfo = cooldown.multipleUsesInfo ?: return null
|
||||
val currentUse = multipleUseInfo.currentUse
|
||||
val maxUses = multipleUseInfo.maxUses
|
||||
val remainingUses = maxUses - currentUse
|
||||
return "$remainingUses/$maxUses"
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package gg.norisk.heroes.common.hero.ability
|
||||
|
||||
import gg.norisk.heroes.common.hero.Hero
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
|
||||
interface IAbilityManager {
|
||||
fun init()
|
||||
|
||||
fun useAbility(
|
||||
player: PlayerEntity,
|
||||
hero: Hero,
|
||||
ability: AbstractAbility<*>,
|
||||
description: AbilityPacketDescription.Use
|
||||
): Boolean
|
||||
|
||||
fun useAbility(player: PlayerEntity, ability: AbstractAbility<*>, description: AbilityPacketDescription.Use)
|
||||
|
||||
fun registerAbility(ability: AbstractAbility<*>)
|
||||
|
||||
fun isUsingAbility(player: PlayerEntity, ability: AbstractAbility<*>): Boolean
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package gg.norisk.heroes.common.hero.ability.implementation
|
||||
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
|
||||
class Ability<C : Any>(name: String) : AbstractAbility<C>(name) {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package gg.norisk.heroes.common.hero.ability.implementation
|
||||
|
||||
open class HoldAbility(name: String) : ToggleAbility(name) {
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package gg.norisk.heroes.common.hero.ability.implementation
|
||||
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
|
||||
open class PressAbility(name: String) : AbstractAbility<Any>(name) {
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package gg.norisk.heroes.common.hero.ability.implementation
|
||||
|
||||
import gg.norisk.heroes.common.ability.CooldownProperty
|
||||
import gg.norisk.heroes.common.ability.PlayerProperty
|
||||
import gg.norisk.heroes.common.ability.operation.AddValueTotal
|
||||
import gg.norisk.heroes.common.ability.operation.Operation
|
||||
import gg.norisk.heroes.common.hero.ability.AbilityScope
|
||||
import gg.norisk.heroes.common.hero.ability.AbstractAbility
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
|
||||
open class ToggleAbility(name: String) : AbstractAbility<Any>(name) {
|
||||
override fun onStart(player: PlayerEntity, abilityScope: AbilityScope) {
|
||||
|
||||
}
|
||||
|
||||
open fun onUse(player: PlayerEntity) {
|
||||
|
||||
}
|
||||
|
||||
open fun onEnd(player: PlayerEntity, abilityEndInformation: AbilityEndInformation) {
|
||||
|
||||
}
|
||||
|
||||
var maxDurationProperty = buildMaxDuration(10.0, 5, AddValueTotal(0.1, 0.4, 0.2, 0.8, 1.5, 1.0))
|
||||
|
||||
data class AbilityEndInformation(var applyCooldown: Boolean = true)
|
||||
|
||||
protected fun buildMaxDuration(baseValue: Double, maxLevel: Int, operation: Operation): CooldownProperty {
|
||||
return CooldownProperty(
|
||||
baseValue, maxLevel,
|
||||
"Max Duration",
|
||||
operation
|
||||
)
|
||||
}
|
||||
|
||||
override val extraProperties: List<PlayerProperty<*>>
|
||||
get() = listOf(maxDurationProperty)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package gg.norisk.heroes.common.hero.ability.task
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.hero.ability.AbilityScope
|
||||
import kotlinx.coroutines.*
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.silkmc.silk.core.annotations.DelicateSilkApi
|
||||
import net.silkmc.silk.core.kotlin.ticks
|
||||
import net.silkmc.silk.core.task.*
|
||||
import java.util.*
|
||||
import kotlin.time.Duration
|
||||
|
||||
object AbilityCoroutineManager {
|
||||
val playerJobs = hashMapOf<UUID, MutableList<Job>>()
|
||||
|
||||
fun cancelServerJobs(player: PlayerEntity) {
|
||||
playerJobs[player.uuid]?.forEach(Job::cancel)
|
||||
playerJobs.remove(player.uuid)
|
||||
}
|
||||
|
||||
fun cancelClientJobs() {
|
||||
val uuid = playerJobs.keys.firstOrNull()
|
||||
playerJobs[uuid]?.forEach(Job::cancel)
|
||||
playerJobs.remove(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(DelicateSilkApi::class)
|
||||
inline fun abilityCoroutineTask(
|
||||
executingPlayer: PlayerEntity,
|
||||
sync: Boolean = true,
|
||||
client: Boolean = false,
|
||||
scope: CoroutineScope = if (sync) {
|
||||
if (client) mcClientCoroutineScope else mcCoroutineScope
|
||||
} else silkCoroutineScope,
|
||||
howOften: Long = 1,
|
||||
period: Duration = 1.ticks,
|
||||
delay: Duration = Duration.ZERO,
|
||||
crossinline task: suspend CoroutineScope.(task: CoroutineTask) -> Unit
|
||||
): Job {
|
||||
val uuid = if (client) MinecraftClient.getInstance().player!!.uuid else executingPlayer.uuid
|
||||
return mcCoroutineTask(sync, client, scope, howOften, period, delay) { coroutineTask ->
|
||||
if (isActive && !executingPlayer.isAlive) {
|
||||
logger.info("${executingPlayer.name.literalString} is currently dead, cancelling coroutine job")
|
||||
cancel()
|
||||
}
|
||||
ensureActive()
|
||||
task(this, coroutineTask)
|
||||
AbilityCoroutineManager.playerJobs[uuid]?.remove(this.coroutineContext.job)
|
||||
}.also { job ->
|
||||
AbilityCoroutineManager.playerJobs.computeIfAbsent(uuid) { mutableListOf() }.add(job)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package gg.norisk.heroes.common.hero.utils
|
||||
|
||||
object ColorUtils {
|
||||
fun hexAsRgb(hexColor: Int): Triple<Int, Int, Int> {
|
||||
val red = (hexColor shr 16) and 0xFF
|
||||
val green = (hexColor shr 8) and 0xFF
|
||||
val blue = hexColor and 0xFF
|
||||
return Triple(red, green, blue)
|
||||
}
|
||||
|
||||
fun isLightColor(hexColor: Int): Boolean {
|
||||
val (red, green, blue) = hexAsRgb(hexColor)
|
||||
|
||||
// Calculate luminance
|
||||
val luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255
|
||||
|
||||
// Determine if color is light or dark
|
||||
return luminance > 0.5
|
||||
}
|
||||
|
||||
fun isDarkColor(hexColor: Int) = !isLightColor(hexColor)
|
||||
|
||||
fun darkenHexColor(hexColor: Int, factor: Double): Int {
|
||||
// Extract RGB components
|
||||
val (red, green, blue) = hexAsRgb(hexColor)
|
||||
|
||||
// Darken each component
|
||||
val darkenedRed = (red * (1 - factor)).toInt()
|
||||
val darkenedGreen = (green * (1 - factor)).toInt()
|
||||
val darkenedBlue = (blue * (1 - factor)).toInt()
|
||||
|
||||
// Combine components and return the darkened color
|
||||
return (darkenedRed shl 16) or (darkenedGreen shl 8) or darkenedBlue
|
||||
}
|
||||
|
||||
fun lightenHexColor(hexColor: Int, factor: Double): Int {
|
||||
// Extract RGB components
|
||||
val (red, green, blue) = hexAsRgb(hexColor)
|
||||
|
||||
// Darken each component
|
||||
val lightenedRed = (red + (255 - red) * factor).toInt()
|
||||
val lightenedGreen = (green + (255 - green) * factor).toInt()
|
||||
val lightenedBlue = (blue + (255 - blue) * factor).toInt()
|
||||
|
||||
// Combine components and return the darkened color
|
||||
return (lightenedRed shl 16) or (lightenedGreen shl 8) or lightenedBlue
|
||||
}
|
||||
|
||||
fun contrast(hexColor: Int): Int {
|
||||
return if (isDarkColor(hexColor)) lightenHexColor(hexColor, 0.33)
|
||||
else darkenHexColor(hexColor, 0.33)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package gg.norisk.heroes.common.networking
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.silkmc.silk.network.packet.s2cPacket
|
||||
|
||||
interface CameraShakeEvent {
|
||||
fun isValid(t: Double): Boolean
|
||||
fun getCameraShakeMagnitude(t: Double): Double
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BoomShake(private var magnitude: Double, private var sustain: Double, private var fade: Double) :
|
||||
CameraShakeEvent {
|
||||
override fun isValid(t: Double): Boolean = t < sustain + fade
|
||||
override fun getCameraShakeMagnitude(t: Double): Double {
|
||||
return when {
|
||||
t <= sustain -> magnitude
|
||||
else -> magnitude * (1 - (t - sustain) / fade)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cameraShakePacket = s2cPacket<BoomShake>("camera-shake".toId())
|
||||
@@ -0,0 +1,30 @@
|
||||
package gg.norisk.heroes.common.networking
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import gg.norisk.heroes.common.cooldown.CooldownInfo
|
||||
import gg.norisk.heroes.common.hero.ability.AbilityPacket
|
||||
import gg.norisk.heroes.common.hero.ability.AbilityPacketDescription
|
||||
import gg.norisk.heroes.common.hero.ability.SkillPropertyPacket
|
||||
import gg.norisk.heroes.common.networking.dto.HeroSelectorPacket
|
||||
import gg.norisk.heroes.common.networking.dto.MousePacket
|
||||
import net.silkmc.silk.network.packet.c2sPacket
|
||||
import net.silkmc.silk.network.packet.s2cPacket
|
||||
|
||||
object Networking {
|
||||
val c2sAbilityPacket = c2sPacket<AbilityPacket<out AbilityPacketDescription>>("use-ability".toId())
|
||||
val s2cAbilityPacket = s2cPacket<AbilityPacket<out AbilityPacketDescription>>("use-ability".toId())
|
||||
|
||||
val c2sSkillProperty = c2sPacket<SkillPropertyPacket>("skill-property".toId())
|
||||
val s2cHeroSelectorPacket = s2cPacket<HeroSelectorPacket>("hero-selector-s2c".toId())
|
||||
val c2sHeroSelectorPacket = c2sPacket<String>("hero-selector-c2s".toId())
|
||||
val c2sKitEditorRequestPacket = c2sPacket<Unit>("kit-editor-request".toId())
|
||||
|
||||
val s2cCooldownPacket = s2cPacket<CooldownInfo>("cooldown".toId())
|
||||
|
||||
val mousePacket = c2sPacket<MousePacket>("mouse-packet".toId())
|
||||
val mouseScrollPacket = c2sPacket<Boolean>("mouse-scroll".toId())
|
||||
|
||||
//warum String?
|
||||
// java.lang.IllegalStateException: This serializer can be used only with Json format.Expected Encoder to be JsonEncoder, got class kotlinx.serialization.cbor.internal.CborMapWriter
|
||||
val s2cHeroSettingsPacket = s2cPacket<String>("hero-settings".toId())
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package gg.norisk.heroes.common.networking.dto
|
||||
|
||||
import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction
|
||||
import java.time.Duration
|
||||
import kotlin.math.*
|
||||
|
||||
class AnimationInterpolator(val start: Float, val end: Float, var dur: Duration) {
|
||||
var startTime: Long
|
||||
var easing = Easing.LINEAR
|
||||
val forward = true
|
||||
|
||||
init {
|
||||
this.startTime = System.nanoTime()
|
||||
}
|
||||
|
||||
constructor(start: Float, end: Float, duration: Duration, easing: Easing) : this(start, end, duration) {
|
||||
this.easing = easing
|
||||
}
|
||||
|
||||
fun setDuration(dur: Duration) {
|
||||
this.dur = dur
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
this.startTime = System.nanoTime()
|
||||
}
|
||||
|
||||
fun get(): Float {
|
||||
val currentTime = System.nanoTime()
|
||||
val delta = currentTime - startTime
|
||||
val nanoDuration = dur.toNanos()
|
||||
var animDelta = delta.toFloat() / nanoDuration
|
||||
animDelta = max(0.0, min(1.0, animDelta.toDouble())).toFloat()
|
||||
if (!forward) {
|
||||
animDelta = (1 - animDelta).toFloat()
|
||||
}
|
||||
animDelta = easing.apply(animDelta.toDouble())
|
||||
return start + (end - start) * animDelta
|
||||
}
|
||||
|
||||
val isDone: Boolean
|
||||
get() = System.nanoTime() - startTime >= dur.toNanos()
|
||||
|
||||
enum class Easing(val floatFunction: Double2DoubleFunction) {
|
||||
LINEAR(Double2DoubleFunction { x: Double -> x }),
|
||||
SINE_IN(Double2DoubleFunction { x: Double -> 1 - cos(x * Math.PI / 2) }),
|
||||
SINE_OUT(Double2DoubleFunction { x: Double -> sin(x * Math.PI / 2) }),
|
||||
SINE_IN_OUT(Double2DoubleFunction { x: Double -> -(cos(Math.PI * x) - 1) / 2 }),
|
||||
|
||||
CUBIC_IN(Double2DoubleFunction { x: Double -> x.pow(3.0) }),
|
||||
CUBIC_OUT(Double2DoubleFunction { x: Double -> 1 - (1 - x).pow(3.0) }),
|
||||
CUBIC_IN_OUT(Double2DoubleFunction { x: Double -> if (x < 0.5) 4 * x * x * x else 1 - (-2 * x + 2).pow(3.0) / 2 }),
|
||||
|
||||
QUINT_IN(Double2DoubleFunction { x: Double -> x.pow(5.0) }),
|
||||
QUINT_OUT(Double2DoubleFunction { x: Double -> 1 - (1 - x).pow(5.0) }),
|
||||
QUINT_IN_OUT(Double2DoubleFunction { x: Double ->
|
||||
if (x < 0.5) 16 * x * x * x * x * x else 1 - (-2 * x + 2).pow(
|
||||
5.0
|
||||
) / 2
|
||||
}),
|
||||
|
||||
CIRC_IN(Double2DoubleFunction { x: Double -> 1 - sqrt(1 - x.pow(2.0)) }),
|
||||
CIRC_OUT(Double2DoubleFunction { x: Double -> sqrt(1 - (x - 1).pow(2.0)) }),
|
||||
CIRC_IN_OUT(Double2DoubleFunction { x: Double ->
|
||||
if (x < 0.5) (1 - sqrt(1 - (2 * x).pow(2.0))) / 2 else (sqrt(
|
||||
1 - (-2 * x + 2).pow(2.0)
|
||||
) + 1) / 2
|
||||
}),
|
||||
|
||||
ELASTIC_IN(Double2DoubleFunction { x: Double ->
|
||||
val c4 = 2 * Math.PI / 3
|
||||
if (x == 0.0) 0.0 else if (x == 1.0) 1.0 else -2.0.pow(10 * x - 10) * sin((x * 10 - 10.75) * c4)
|
||||
}),
|
||||
ELASTIC_OUT(Double2DoubleFunction { x: Double ->
|
||||
val c4 = 2 * Math.PI / 3
|
||||
if (x == 0.0) 0.0 else if (x == 1.0) 1.0 else 2.0.pow(-10 * x) * sin((x * 10 - 0.75) * c4) + 1
|
||||
}),
|
||||
ELASTIC_IN_OUT(Double2DoubleFunction { x: Double ->
|
||||
val c5 = 2 * Math.PI / 4.5
|
||||
val sin = sin((20 * x - 11.125) * c5)
|
||||
if (x == 0.0) 0.0 else if (x == 1.0) 1.0 else if (x < 0.5) -(2.0.pow(20 * x - 10) * sin) / 2 else 2.0.pow(-20 * x + 10) * sin / 2 + 1
|
||||
}),
|
||||
|
||||
QUAD_IN(Double2DoubleFunction { x: Double -> x * x }),
|
||||
QUAD_OUT(Double2DoubleFunction { x: Double -> 1 - (1 - x) * (1 - x) }),
|
||||
QUAD_IN_OUT(Double2DoubleFunction { x: Double -> if (x < 0.5) 2 * x * x else 1 - (-2 * x + 2).pow(2.0) / 2 }),
|
||||
|
||||
QUART_IN(Double2DoubleFunction { x: Double -> x * x * x * x }),
|
||||
QUART_OUT(Double2DoubleFunction { x: Double -> 1 - (1 - x).pow(4.0) }),
|
||||
QUART_IN_OUT(Double2DoubleFunction { x: Double -> if (x < 0.5) 8 * x * x * x * x else 1 - (-2 * x + 2).pow(4.0) / 2 }),
|
||||
|
||||
EXPO_IN(Double2DoubleFunction { x: Double -> if (x == 0.0) 0.0 else 2.0.pow(10 * x - 10) }),
|
||||
EXPO_OUT(Double2DoubleFunction { x: Double -> if (x == 1.0) 1.0 else 1 - 2.0.pow(-10 * x) }),
|
||||
EXPO_IN_OUT(Double2DoubleFunction { x: Double ->
|
||||
if (x == 0.0) 0.0 else if (x == 1.0) 1.0 else if (x < 0.5) 2.0.pow(
|
||||
20 * x - 10
|
||||
) / 2 else (2 - 2.0.pow(-20 * x + 10)) / 2
|
||||
}),
|
||||
|
||||
BACK_IN(Double2DoubleFunction { x: Double ->
|
||||
val c1 = 1.70158
|
||||
val c3 = c1 + 1
|
||||
c3 * x * x * x - c1 * x * x
|
||||
}),
|
||||
BACK_OUT(Double2DoubleFunction { x: Double ->
|
||||
val c1 = 1.70158
|
||||
val c3 = c1 + 1
|
||||
1 + c3 * (x - 1).pow(3.0) + c1 * (x - 1).pow(2.0)
|
||||
}),
|
||||
BACK_IN_OUT(Double2DoubleFunction { x: Double ->
|
||||
val c1 = 1.70158
|
||||
val c2 = c1 * 1.525
|
||||
if (x < 0.5) (2 * x).pow(2.0) * ((c2 + 1) * 2 * x - c2) / 2 else ((2 * x - 2).pow(2.0) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2
|
||||
}),
|
||||
|
||||
BOUNCE_OUT(Double2DoubleFunction { x: Double ->
|
||||
var x = x
|
||||
val n1 = 7.5625
|
||||
val d1 = 2.75
|
||||
if (x < 1 / d1) {
|
||||
return@Double2DoubleFunction n1 * x * x
|
||||
} else if (x < 2 / d1) {
|
||||
return@Double2DoubleFunction n1 * ((1.5 / d1).let { x -= it; x }) * x + 0.75
|
||||
} else if (x < 2.5 / d1) {
|
||||
return@Double2DoubleFunction n1 * ((2.25 / d1).let { x -= it; x }) * x + 0.9375
|
||||
} else {
|
||||
return@Double2DoubleFunction n1 * ((2.625 / d1).let { x -= it; x }) * x + 0.984375
|
||||
}
|
||||
}),
|
||||
BOUNCE_IN(Double2DoubleFunction { x: Double -> (1 - BOUNCE_OUT.apply(x)).toDouble() }),
|
||||
BOUNCE_IN_OUT(Double2DoubleFunction { x: Double ->
|
||||
if (x < 0.5) ((1 - BOUNCE_OUT.apply(1 - 2 * x)) / 2).toDouble() else ((1 + BOUNCE_OUT.apply(
|
||||
2 * x - 1
|
||||
)) / 2).toDouble()
|
||||
});
|
||||
|
||||
fun apply(f: Double): Float {
|
||||
return floatFunction[f].toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package gg.norisk.heroes.common.networking.dto
|
||||
|
||||
import gg.norisk.heroes.common.serialization.BlockPosSerializer
|
||||
import gg.norisk.heroes.common.serialization.BlockStateSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
@Serializable
|
||||
data class BlockInfoSmall(
|
||||
@Serializable(with = BlockStateSerializer::class) val state: BlockState,
|
||||
@Serializable(with = BlockPosSerializer::class) val pos: BlockPos
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package gg.norisk.heroes.common.networking.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class HeroSelectorPacket(
|
||||
val heroes: List<String>,
|
||||
val isActive: Boolean,
|
||||
var isKitEditorEnabled: Boolean
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
package gg.norisk.heroes.common.networking.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
enum class MouseType {
|
||||
LEFT, MIDDLE, RIGHT
|
||||
}
|
||||
|
||||
enum class MouseAction {
|
||||
CLICK, RELEASE, HOLD
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MousePacket(val type: MouseType, val action: MouseAction) {
|
||||
fun isLeft(): Boolean = type == MouseType.LEFT
|
||||
fun isRight(): Boolean = type == MouseType.RIGHT
|
||||
fun isMiddle(): Boolean = type == MouseType.MIDDLE
|
||||
|
||||
fun isHolding(): Boolean = action == MouseAction.HOLD
|
||||
fun isReleased(): Boolean = action == MouseAction.RELEASE
|
||||
fun isClicked(): Boolean = action == MouseAction.CLICK
|
||||
|
||||
fun isHoldingLeftClick(): Boolean = isLeft() && isHolding()
|
||||
fun isHoldingRightClick(): Boolean = isRight() && isHolding()
|
||||
fun isHoldingMiddleClick(): Boolean = isMiddle() && isHolding()
|
||||
|
||||
override fun toString(): String {
|
||||
return "[$type, $action]"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package gg.norisk.heroes.common.player
|
||||
|
||||
import gg.norisk.datatracker.entity.getSyncedData
|
||||
import gg.norisk.datatracker.entity.setSyncedData
|
||||
import gg.norisk.heroes.common.HeroesManager.logger
|
||||
import gg.norisk.heroes.common.ability.PropertyPlayer
|
||||
import gg.norisk.heroes.common.serialization.ItemStackSerializer
|
||||
import gg.norisk.heroes.common.serialization.UUIDSerializer
|
||||
import gg.norisk.heroes.common.utils.PlayStyle
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class FFAPlayer(
|
||||
@SerialName("_id")
|
||||
@Serializable(with = UUIDSerializer::class)
|
||||
val uuid: UUID,
|
||||
var xp: Int = 0,
|
||||
var kills: Int = 0,
|
||||
var deaths: Int = 0,
|
||||
var currentKillStreak: Int = 0,
|
||||
var highestKillStreak: Int = 0,
|
||||
var bounty: Int = 0,
|
||||
var heroes: MutableMap<String, MutableMap<String, MutableMap<String, PropertyPlayer>>> = mutableMapOf(),
|
||||
var inventorySorting: InventorySorting? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class InventorySorting(
|
||||
@Serializable(with = UUIDSerializer::class)
|
||||
val userId: UUID,
|
||||
val playStyle: PlayStyle,
|
||||
val version: Int,
|
||||
val armor: Array<@Serializable(with = ItemStackSerializer::class) ItemStack> = Array(4) { ItemStack.EMPTY },
|
||||
val offhand: Array<@Serializable(with = ItemStackSerializer::class) ItemStack> = Array(1) { ItemStack.EMPTY },
|
||||
val main: Array<@Serializable(with = ItemStackSerializer::class) ItemStack> = Array(36) { ItemStack.EMPTY },
|
||||
) {
|
||||
companion object {
|
||||
var CURRENT_VERSION = 0
|
||||
|
||||
fun ServerPlayerEntity.loadInventory(inventorySorting: InventorySorting) {
|
||||
logger.info("Loading inventory $inventorySorting")
|
||||
inventory.clear()
|
||||
inventorySorting.armor.forEachIndexed { index, itemStack ->
|
||||
inventory.armor[index] = itemStack.copy()
|
||||
}
|
||||
inventorySorting.main.forEachIndexed { index, itemStack ->
|
||||
inventory.main[index] = itemStack.copy()
|
||||
}
|
||||
inventorySorting.offhand.forEachIndexed { index, itemStack ->
|
||||
inventory.offHand[index] = itemStack.copy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val FFA_PLAYER = "HeroApi:FfaPlayer"
|
||||
var PlayerEntity.ffaPlayer: FFAPlayer
|
||||
get() = this.getSyncedData<FFAPlayer>(FFA_PLAYER) ?: FFAPlayer(this.uuid)
|
||||
set(value) = this.setSyncedData(FFA_PLAYER, value, (this as? ServerPlayerEntity?))
|
||||
|
||||
private const val FFA_BOUNTY = "HeroApi:Bounty"
|
||||
var PlayerEntity.ffaBounty: Int
|
||||
get() = this.getSyncedData<Int>(FFA_BOUNTY) ?: 0
|
||||
set(value) = this.setSyncedData(FFA_BOUNTY, value)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package gg.norisk.heroes.common.registry
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.toId
|
||||
import net.minecraft.registry.Registries
|
||||
import net.minecraft.registry.Registry
|
||||
import net.minecraft.sound.SoundEvent
|
||||
|
||||
object SoundRegistry {
|
||||
var FLYING = "flying".register()
|
||||
|
||||
fun init() {
|
||||
}
|
||||
|
||||
private fun String.register() = Registry.register(Registries.SOUND_EVENT, this.toId(), SoundEvent.of(this.toId()))
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package gg.norisk.heroes.common.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.*
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
object BlockPosSerializer : KSerializer<BlockPos> {
|
||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("BlockPos") {
|
||||
element<Int>("x")
|
||||
element<Int>("y")
|
||||
element<Int>("z")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: BlockPos) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
encodeIntElement(descriptor, 0, value.x)
|
||||
encodeIntElement(descriptor, 1, value.y)
|
||||
encodeIntElement(descriptor, 2, value.z)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): BlockPos {
|
||||
return decoder.decodeStructure(descriptor) {
|
||||
var x = 0
|
||||
var y = 0
|
||||
var z = 0
|
||||
while (true) {
|
||||
when (val index = decodeElementIndex(descriptor)) {
|
||||
0 -> x = decodeIntElement(descriptor, 0)
|
||||
1 -> y = decodeIntElement(descriptor, 1)
|
||||
2 -> z = decodeIntElement(descriptor, 2)
|
||||
CompositeDecoder.DECODE_DONE -> break
|
||||
else -> throw SerializationException("Unknown index $index")
|
||||
}
|
||||
}
|
||||
BlockPos(x, y, z)
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package gg.norisk.heroes.common.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.nbt.NbtHelper
|
||||
import net.minecraft.nbt.StringNbtReader
|
||||
import net.minecraft.registry.Registries
|
||||
|
||||
object BlockStateSerializer : KSerializer<BlockState> {
|
||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("BlockState")
|
||||
|
||||
override fun serialize(encoder: Encoder, value: BlockState) {
|
||||
val nbt = NbtHelper.fromBlockState(value)
|
||||
encoder.encodeString(nbt.asString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): BlockState {
|
||||
val nbtString = decoder.decodeString()
|
||||
val nbt = StringNbtReader.parse(nbtString)
|
||||
val blockState = NbtHelper.toBlockState(Registries.BLOCK.tagCreatingWrapper, nbt)
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package gg.norisk.heroes.common.serialization
|
||||
|
||||
import gg.norisk.heroes.common.HeroesManager.isServer
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import net.fabricmc.api.EnvType
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.nbt.NbtHelper
|
||||
import net.silkmc.silk.core.Silk.serverOrThrow
|
||||
|
||||
object ItemStackSerializer : KSerializer<ItemStack> {
|
||||
private val emptyItemStack = "EMPTY"
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ItemStack", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ItemStack) {
|
||||
if (value.isEmpty) {
|
||||
encoder.encodeString(emptyItemStack)
|
||||
return
|
||||
}
|
||||
val registryManager =
|
||||
if (isServer) serverOrThrow.registryManager else MinecraftClient.getInstance().world?.registryManager
|
||||
val nbt = value.encode(registryManager) as NbtCompound
|
||||
val string = NbtHelper.toNbtProviderString(nbt)
|
||||
encoder.encodeString(string)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): ItemStack {
|
||||
val registryManager =
|
||||
if (isServer) serverOrThrow.registryManager else MinecraftClient.getInstance().world?.registryManager
|
||||
val string = decoder.decodeString()
|
||||
if (string == emptyItemStack) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
val nbt = NbtHelper.fromNbtProviderString(string)
|
||||
return ItemStack.fromNbt(registryManager, nbt).orElseThrow()
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package gg.norisk.heroes.common.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
@PublishedApi
|
||||
internal object StringSerializer : KSerializer<String> {
|
||||
override val descriptor: SerialDescriptor = String.serializer().descriptor
|
||||
override fun serialize(encoder: Encoder, value: String): Unit = String.serializer().serialize(encoder, value)
|
||||
override fun deserialize(decoder: Decoder): String = String.serializer().deserialize(decoder)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal object BooleanSerializer : KSerializer<Boolean> {
|
||||
override val descriptor: SerialDescriptor = Boolean.serializer().descriptor
|
||||
override fun serialize(encoder: Encoder, value: Boolean): Unit = Boolean.serializer().serialize(encoder, value)
|
||||
override fun deserialize(decoder: Decoder): Boolean = Boolean.serializer().deserialize(decoder)
|
||||
}
|
||||
|
||||
|
||||
@PublishedApi
|
||||
internal object IntSerializer : KSerializer<Int> {
|
||||
override val descriptor: SerialDescriptor = Int.serializer().descriptor
|
||||
override fun serialize(encoder: Encoder, value: Int): Unit = Int.serializer().serialize(encoder, value)
|
||||
override fun deserialize(decoder: Decoder): Int = Int.serializer().deserialize(decoder)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal object FloatSerializer : KSerializer<Float> {
|
||||
override val descriptor: SerialDescriptor = Float.serializer().descriptor
|
||||
override fun serialize(encoder: Encoder, value: Float): Unit = Float.serializer().serialize(encoder, value)
|
||||
override fun deserialize(decoder: Decoder): Float = Float.serializer().deserialize(decoder)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal object DoubleSerializer : KSerializer<Double> {
|
||||
override val descriptor: SerialDescriptor = Double.serializer().descriptor
|
||||
override fun serialize(encoder: Encoder, value: Double): Unit = Double.serializer().serialize(encoder, value)
|
||||
override fun deserialize(decoder: Decoder): Double = Double.serializer().deserialize(decoder)
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package gg.norisk.heroes.common.serialization
|
||||
|
||||
import com.mongodb.internal.connection.BsonWriterDecorator
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import org.bson.AbstractBsonReader
|
||||
import org.bson.AbstractBsonWriter
|
||||
import org.bson.codecs.kotlinx.BsonDecoder
|
||||
import org.bson.codecs.kotlinx.BsonEncoder
|
||||
import java.lang.reflect.Field
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
object SerializationHelper {
|
||||
fun isNameState(bsonEncoder: BsonEncoder): Boolean {
|
||||
return WriterHelper.getState(bsonEncoder) == AbstractBsonWriter.State.NAME
|
||||
}
|
||||
|
||||
fun isNameState(bsonDecoder: BsonDecoder): Boolean {
|
||||
return ReaderHelper.isNameState(bsonDecoder)
|
||||
}
|
||||
|
||||
private object WriterHelper {
|
||||
private val BsonEncoder_writer_field: Field by lazy {
|
||||
Class.forName("org.bson.codecs.kotlinx.JsonBsonEncoder").kotlin.memberProperties
|
||||
.find { it.name == "writer" }?.javaField!!.also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
}
|
||||
private val BsonWriterDecorator_bsonWriter_field: Field by lazy {
|
||||
BsonWriterDecorator::class.memberProperties.find { it.name == "bsonWriter" }?.javaField!!.also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
}
|
||||
private val BsonWriterDecorator_state_field: Field by lazy {
|
||||
AbstractBsonWriter::class.memberProperties.find { it.name == "state" }?.javaField!!.also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun `get BsonWriterDecorator of JsonBsonEncoder`(encoder: BsonEncoder): BsonWriterDecorator {
|
||||
val writerDecorator = BsonEncoder_writer_field.get(encoder) as? BsonWriterDecorator
|
||||
?: throw IllegalStateException("writerDecorator is not a ${BsonWriterDecorator::class.qualifiedName}.")
|
||||
return writerDecorator
|
||||
}
|
||||
|
||||
private fun `get AbstractBsonWriter of BsonWriterDecorator`(bsonWriterDecorator: BsonWriterDecorator): AbstractBsonWriter {
|
||||
val abstractBsonWriter =
|
||||
BsonWriterDecorator_bsonWriter_field.get(bsonWriterDecorator) as? AbstractBsonWriter
|
||||
?: throw IllegalStateException("abstractBsonWriter is not a ${AbstractBsonWriter::class.qualifiedName}.")
|
||||
return abstractBsonWriter
|
||||
}
|
||||
|
||||
private fun `get State of AbstractBsonWriter`(abstractBsonWriter: AbstractBsonWriter): AbstractBsonWriter.State {
|
||||
val state = BsonWriterDecorator_state_field.get(abstractBsonWriter) as? AbstractBsonWriter.State
|
||||
?: throw IllegalStateException("abstractBsonWriter is not a ${AbstractBsonWriter::class.qualifiedName}.")
|
||||
return state
|
||||
}
|
||||
|
||||
fun getState(bsonEncoder: BsonEncoder): AbstractBsonWriter.State {
|
||||
val bsonWriterDecoder = `get BsonWriterDecorator of JsonBsonEncoder`(bsonEncoder)
|
||||
val abstractBsonWriter = `get AbstractBsonWriter of BsonWriterDecorator`(bsonWriterDecoder)
|
||||
val state = `get State of AbstractBsonWriter`(abstractBsonWriter)
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
private object ReaderHelper {
|
||||
private val JsonBsonMapDecoderClass: Class<*> by lazy {
|
||||
Class.forName("org.bson.codecs.kotlinx.JsonBsonMapDecoder")
|
||||
}
|
||||
|
||||
/* private val AbstractBsonDecoderClass: Class<*> by lazy {
|
||||
Class.forName("org.bson.codecs.kotlinx.AbstractBsonDecoder")
|
||||
}*/
|
||||
|
||||
private val JsonBsonDocumentDecoder_reader_field: Field by lazy {
|
||||
JsonBsonMapDecoderClass.kotlin.memberProperties.find { it.name == "reader" }?.javaField!!.also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun `BsonDecoder is JsonBsonMapDecoder`(bsonDecoder: BsonDecoder): Boolean {
|
||||
return JsonBsonMapDecoderClass.isInstance(bsonDecoder)
|
||||
}
|
||||
|
||||
private fun `BsonDecoder as JsonBsonDocumentDecoder`(bsonDecoder: BsonDecoder): Any {
|
||||
val jsonBsonDocumentDecoder = JsonBsonMapDecoderClass.cast(bsonDecoder)
|
||||
return jsonBsonDocumentDecoder
|
||||
}
|
||||
|
||||
private fun `get AbstractBsonReader of JsonBsonDocumentDecoder`(jsonBsonDocumentDecoder: Any): AbstractBsonReader {
|
||||
val reader = JsonBsonDocumentDecoder_reader_field.get(jsonBsonDocumentDecoder) as AbstractBsonReader
|
||||
return reader
|
||||
}
|
||||
|
||||
private fun `get State of AbstractBsonReader`(abstractBsonReader: AbstractBsonReader): AbstractBsonReader.State {
|
||||
return abstractBsonReader.state
|
||||
}
|
||||
|
||||
private fun getState(bsonDecoder: BsonDecoder): AbstractBsonReader.State {
|
||||
val jsonBsonDocumentDecoder = `BsonDecoder as JsonBsonDocumentDecoder`(bsonDecoder)
|
||||
val abstractBsonReader = `get AbstractBsonReader of JsonBsonDocumentDecoder`(jsonBsonDocumentDecoder)
|
||||
val state = `get State of AbstractBsonReader`(abstractBsonReader)
|
||||
return state
|
||||
}
|
||||
|
||||
fun isNameState(bsonDecoder: BsonDecoder): Boolean {
|
||||
if (!`BsonDecoder is JsonBsonMapDecoder`(bsonDecoder)) return false
|
||||
return getState(bsonDecoder) == AbstractBsonReader.State.NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user