chore(history): squash 100 commit(s) from 2026-09-24
- fix(backend): case-insensitive unique email, revoke PUBLIC schema access, pin Argon2id params - test(backend): assert password length cap boundary (256 ok, 257 rejected) - docs(backend): plan — typed JWT token kinds so refresh/device tokens cannot pass as access tokens - feat(backend): JWT access/refresh token issue and verify - feat(backend): accounts repository (create/find_by_email/find_by_id) - refactor(gui): LoVisualAddonManagerScreen 989→10 файлов addon/ (8.5.2) - docs(backend): plan — fix sqlx::migrate! path in integration tests - feat(backend): POST /auth/register and /auth/login - refactor(settings): SettingsPanelComponent 715→81 + 6 helpers (8.5.2) - docs(backend): plan — harden device flow (single-use codes, bounded store, 404/429) - feat(backend): OAuth device authorization grant for mod login - fix(backend): first confirm wins for device codes - docs(backend): plan — split Task 8 (refactor) and Task 9 (avatars), harden avatar handling - refactor(mixins): LocalPlayerMixin 691→111 + 4 handlers (8.5.2) - refactor(visuals): Trails 687→130 (8.5.2) - refactor(render): ItemBatchRenderer 677->100 (8.5.2) - refactor(visuals): ReimaginedVisual 674→118 + 5 helpers (8.5.2) - refactor(config): ConfigSerializer 668→91 + 4 helpers (8.5.2) - refactor(hud): DynamicIsland 661→158 + 4 helpers (8.5.2) - refactor(render): GlStencilFramebufferSupport 666→169 (8.5.2) - refactor(gui): MenuScreen 669→128 + 4 helpers (8.5.2) - refactor(render): UiStyle 644→170 + 3 helpers (8.5.2) - refactor(gui): ModuleComponent 613→98 + 4 helpers (8.5.2) - refactor(media): MediaSessionService 616→200 + 4 helpers (8.5.2) - refactor(gui): RelationsComponent 661→59 + 4 helpers (8.5.2) - chore(license): strip GPL file headers from all Java sources - refactor(aiming): PointTracker 583→168 + 2 helpers (8.5.2) - refactor(gui): LoVisualProxyManagerScreen 591→132 + 2 helpers (8.5.2) - refactor(visuals): KillEffect 588→96 + 4 helpers (8.5.2) - refactor(render): MeshBuilder +4 helpers (8.5.2) - refactor(visuals): extract WorldParticlesRender helper (8.5.2) - refactor(world): ExplosionDamageUtil 551→116 + 2 helpers (8.5.2) - refactor(visuals): TazikHat 596->179 + Model + Palette in hats/tazik (8.5.2) - chore(license): strip GPL header from remaining 30 files and make strip script variant-aware - refactor(gui): ThemeComponent 561->166 + CardRenderer + ScrollState (8.5.2) - refactor(hud): CustomHotbar 556→178 + Renderer + Selection + SelectionGradient (8.5.2) - refactor(gui): ThemeCardRenderer perf + readability polish - refactor(clickgui): CooldownRulesSetting 596->198 + Editor + DetailRenderer (8.5.2) - refactor(hud): HudNotifier 561->200 + Painter + runtime/HudNotifierRuntime (8.5.2) - refactor(theme): Themes 555->168 + impl/Transition + impl/Blending + impl/ProfileCodec (8.5.2) - refactor(theme): EditableClickGuiTheme 205->185 + JavaDoc (8.5.2) - refactor(theme): ThemeStore 491->128 + store/ThemeStoreJson + store/ThemeStoreIO (8.5.2) - refactor(clickgui): ClickGuiRenderer 604->200 compacted one-line delegators + JavaDoc (8.5.2) - refactor(mainmenu): LoVisualMainMenuScreen 551->161 + impl/Painter + impl/Renderer + impl/TextUtil (8.5.2) - refactor(clickgui): ClickGuiTextEditorState 531->187 + impl/EditorCaret + impl/EditorPainter (8.5.2) - refactor(tab): TabListModel 525->139 + model/Collector + model/Reader + model/Signature + model/TextSplitter (8.5.2) - refactor(backend): shared bearer helper and test helpers, build_app takes Config, validate JWT secret strength - refactor(module): ModuleManager 521->198 + impl/Registrar + impl/Dispatcher (8.5.2) - feat(backend): avatar upload with decode, square crop, PNG re-encode and S3 storage - refactor(clip): ClipFunction 512->146 + impl/Geometry + impl/Debug (8.5.2) - docs(backend): implementation plans for gateway (auth hardening, gRPC, rate limits) and configs-service - refactor(iris-patch): ShaderPatchEngine 499->146 + impl/Repo (8.5.2) - chore(frontend): add router, react-query, fonts and vitest; dev proxy to gateway - refactor(hud): ScriptedListHudPanel 499->158 + panel/Props + panel/Signature (8.5.2) - refactor(hud): BaseHudElement 499->199 + impl/Registry + impl/Namer + impl/Prewarm (8.5.2) - refactor(clickgui): Setting 498->170 + impl/Localization + impl/I18n (8.5.2) - refact(viewmodel): split swing animations into camera/swing package - refact(kineticlyrics): split module into stage, playback and modes - rename(holeesp): module HoleESP -> CrystalHoles - refact(crystalholes): split module into crystal scanner, renderer and safety - refact(addonmanager): split manager into lifecycle, runtime, descriptors and profiles - refact(accountconfig): split config into store, session and value helpers - refactor(render): CustomTextRenderer 229->195, extract glyph-pass into GradientTexts helper - docs(TODO): mark AddonManager split done; close 9.2 refactor gate - refactor(media): LinuxMediaSession 441->148, split reader + track/seek state - refactor(nametags): split NameTags into facade + impl helpers - refactor(clickgui): split MainSettingsComponent into facade + scroll + model - refactor(hud): split CustomBar into facade, model and BarSettings - docs(frontend): implementation plan with design system from the mod theme - feat(frontend): design tokens from the mod theme, fonts and shared UI kit - fix(accounts): run migrations on startup, offload Argon2, validate register input, JSON error shape - docs(gateway): plan note on splitting auth handlers before refresh endpoints - feat(frontend): API client with silent refresh, error descriptions and test helpers - style(mod): group compact one-line bulk query methods in ModuleManager - feat(frontend): session restore, login and registration with client-side validation - refactor(hud): split CustomHealthBar into facade + painter + script renderer - docs(mod): record the 2026-09-24 HUD/settings split wave in TODO phase 8.5 - refactor(rhi): split GlStencilShapeClipBackend into facade + native-state + pass-lifecycle helpers - refactor(rhi): split VulkanRenderStateBridge into facade + MSAA and stencil state helpers - refactor(backtrack): split BacktrackController into facade + model + impl helpers - refactor(svg): split SvgPathParser into facade + arc geometry + command/curve helpers - refactor(mixin): split ClientPacketListenerMixin into hook-only mixin + handlers - refactor(renderer3d): un-nest batch bindings + culling into sibling impl types - refactor(renderwarp): extract static factories + geometry into impl helpers - feat(backend): add common crate with shared JWT, internal gateway contract and accounts proto - refactor(guimixin): move hook bodies into handlers, keep mixin as hooks + shadows - feat(accounts): accept only gateway traffic, read identity from gateway header - refactor(cacheduiscriptruntime): extract engine, hashing and frame stats into impl - refactor(customskyboxrenderer): extract projection, shader passes and sun into impl - refactor(betterchatstoremanager): extract persistence, key/path and hover helpers into impl - feat(accounts): rotating opaque refresh tokens in httpOnly cookie, /auth/refresh and /auth/logout - refactor(targetesp): extract crystal rendering subsystem into impl/TargetEspCrystalRenderer - refactor(betterchathovercache): extract disk codec and lookup indexing into impl/ChatHoverCacheCodec - refactor(microsoftauth): split HTTP transport, device-code and Xbox flows into impl/ - refactor(pvpcooldowns): extract local item-rule engine and defaults into impl/PvpCooldownRules - refactor(lovisual): extract HUD/world render orchestration into HudRender helper - refactor(statuseffectheuristics): extract palette/inference into ParticlePalette and color utils into ParticleColors - refactor(dropesp): extract overlay/label render subsystem into impl/DropEspOverlayRenderer - refactor(proxy): extract SOCKS handshake message builders into ProxyProtocolMessages - refactor(eagleutil): promote EdgeRecovery controller and RecoveryMode to top-level class
This commit is contained in:
parent
9d08fa910a
commit
72bc4c7148
1897 changed files with 36199 additions and 39289 deletions
|
|
@ -0,0 +1,91 @@
|
|||
package dev.loki.lovisual.util.player.effect.heuristics;
|
||||
|
||||
import dev.loki.lovisual.mixins.accessors.state.EffectParticleEffectAccessor;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.particles.ColorParticleOption;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.world.effect.MobEffect;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Palette-independent particle color utilities: pulling an RGB color (or a directly-tagged effect)
|
||||
* out of a {@link ParticleOptions}, brightness normalization, HSV hue and the palette color arrays
|
||||
* derived from a set of mob effects. Used by {@link ParticlePalette} during inference.
|
||||
*/
|
||||
final class ParticleColors {
|
||||
private ParticleColors() {
|
||||
}
|
||||
|
||||
static Holder<MobEffect> directEffectFromParticle(ParticleOptions particle) {
|
||||
if (particle == ParticleTypes.RAID_OMEN) return MobEffects.RAID_OMEN;
|
||||
if (particle == ParticleTypes.TRIAL_OMEN) return MobEffects.TRIAL_OMEN;
|
||||
return null;
|
||||
}
|
||||
|
||||
static int extractColor(ParticleOptions particle) {
|
||||
if (particle instanceof EffectParticleEffectAccessor effect) {
|
||||
return effect.lovisual$getColor() & 0xFFFFFF;
|
||||
}
|
||||
if (particle instanceof ColorParticleOption tinted) {
|
||||
int r = Math.min(255, Math.max(0, Math.round(tinted.getRed() * 255f)));
|
||||
int g = Math.min(255, Math.max(0, Math.round(tinted.getGreen() * 255f)));
|
||||
int b = Math.min(255, Math.max(0, Math.round(tinted.getBlue() * 255f)));
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int[] computeColors(List<Holder<MobEffect>> palette) {
|
||||
int[] colors = new int[palette.size()];
|
||||
for (int i = 0; i < palette.size(); i++) {
|
||||
colors[i] = palette.get(i).value().getColor();
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
static int[] computeNormalizedColors(int[] colors) {
|
||||
int[] normalized = new int[colors.length];
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
normalized[i] = normalizeColor(colors[i]);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
static int normalizeColor(int color) {
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
int max = Math.max(r, Math.max(g, b));
|
||||
if (max <= 0) return 0;
|
||||
float scale = 255f / (float) max;
|
||||
int nr = Math.min(255, Math.round(r * scale));
|
||||
int ng = Math.min(255, Math.round(g * scale));
|
||||
int nb = Math.min(255, Math.round(b * scale));
|
||||
return (nr << 16) | (ng << 8) | nb;
|
||||
}
|
||||
|
||||
// Basic HSV hue calculation (degrees 0-360)
|
||||
static float hueDegrees(int r, int g, int b) {
|
||||
float rf = r / 255f;
|
||||
float gf = g / 255f;
|
||||
float bf = b / 255f;
|
||||
float max = Math.max(rf, Math.max(gf, bf));
|
||||
float min = Math.min(rf, Math.min(gf, bf));
|
||||
float delta = max - min;
|
||||
if (delta <= 0f) return 0f;
|
||||
float hue;
|
||||
if (max == rf) {
|
||||
hue = (gf - bf) / delta;
|
||||
} else if (max == gf) {
|
||||
hue = 2f + (bf - rf) / delta;
|
||||
} else {
|
||||
hue = 4f + (rf - gf) / delta;
|
||||
}
|
||||
hue *= 60f;
|
||||
if (hue < 0f) hue += 360f;
|
||||
return hue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
package dev.loki.lovisual.util.player.effect.heuristics;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.effect.MobEffect;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Registry-backed effect/palette and nearest-color inference used by {@link StatusEffectHeuristics}
|
||||
* to guess a mob effect from a colored entity-effect particle. Holds the dynamic palette and its
|
||||
* precomputed/normalized colors plus the color-matching inference; palette-independent color math
|
||||
* and particle extraction live in {@link ParticleColors}.
|
||||
*/
|
||||
final class ParticlePalette {
|
||||
static final int COLOR_THRESHOLD = 42000;
|
||||
static final int PRIORITY_THRESHOLD = 120000; // allow rougher matches for hard-to-distinguish colors
|
||||
static final int AMBIGUITY_DELTA = 2000;
|
||||
static final double AMBIGUITY_RATIO = 1.05; // accept best if second-best is clearly worse
|
||||
|
||||
// Dynamic palette of effects + colors from the registry to survive version bumps.
|
||||
private static final List<Holder<MobEffect>> PALETTE = buildPalette();
|
||||
private static final int[] COLORS = ParticleColors.computeColors(PALETTE);
|
||||
private static final int[] NORMALIZED_COLORS = ParticleColors.computeNormalizedColors(COLORS);
|
||||
private static final Set<Holder<MobEffect>> PRIORITY_EFFECTS = Set.of(
|
||||
MobEffects.STRENGTH,
|
||||
MobEffects.RESISTANCE
|
||||
);
|
||||
private static final Set<Holder<MobEffect>> FORCE_PICK_EFFECTS = Set.of(
|
||||
MobEffects.STRENGTH,
|
||||
MobEffects.RESISTANCE
|
||||
);
|
||||
|
||||
private ParticlePalette() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses the mob effect owning the nearest palette color to {@code color}, or {@code null} when
|
||||
* the color is empty, ambiguous beyond the acceptance ratio, out of the allowed threshold, or a
|
||||
* night-vision effect that must not be inferred heuristically.
|
||||
*/
|
||||
static Holder<MobEffect> guess(int color) {
|
||||
if (color == 0) return null;
|
||||
// Normalize brightness so darker/lighter particles still map to the same hue bucket.
|
||||
int normalizedColor = ParticleColors.normalizeColor(color);
|
||||
Nearest nearest = nearest(normalizedColor, NORMALIZED_COLORS);
|
||||
boolean priority = isPriority(nearest.idx); // e.g. strength/resistance
|
||||
boolean ambiguous = nearest.secondDiff - nearest.diff < AMBIGUITY_DELTA;
|
||||
|
||||
// Prefer fire resistance when it's one of the ambiguous pair so we don't mislabel it as strength.
|
||||
int chosenIdx = nearest.idx;
|
||||
int chosenDiff = nearest.diff;
|
||||
// Strength vs fire-resistance: both share orange/red; bias using channel dominance.
|
||||
if ((isStrength(nearest.idx) && isFire(nearest.secondIdx)) || (isStrength(nearest.secondIdx) && isFire(nearest.idx))) {
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
// Strength tint is deep red; fire resistance skews orange (more green). Bias toward fire only when clearly orange.
|
||||
float hue = ParticleColors.hueDegrees(r, g, b);
|
||||
boolean looksFire = (g >= r * 0.8f) && hue >= 25f;
|
||||
if (looksFire) {
|
||||
chosenIdx = isFire(nearest.idx) ? nearest.idx : nearest.secondIdx;
|
||||
chosenDiff = isFire(nearest.idx) ? nearest.diff : nearest.secondDiff;
|
||||
} else {
|
||||
chosenIdx = isStrength(nearest.idx) ? nearest.idx : nearest.secondIdx;
|
||||
chosenDiff = isStrength(nearest.idx) ? nearest.diff : nearest.secondDiff;
|
||||
}
|
||||
priority = isPriority(chosenIdx);
|
||||
ambiguous = false; // resolved manually
|
||||
} else if (ambiguous && (isFire(nearest.idx) ^ isFire(nearest.secondIdx))) {
|
||||
chosenIdx = isFire(nearest.idx) ? nearest.idx : nearest.secondIdx;
|
||||
chosenDiff = isFire(nearest.idx) ? nearest.diff : nearest.secondDiff;
|
||||
priority = isPriority(chosenIdx);
|
||||
ambiguous = false;
|
||||
} else if (ambiguous && !priority && isPriority(nearest.secondIdx)) {
|
||||
chosenIdx = nearest.secondIdx;
|
||||
chosenDiff = nearest.secondDiff;
|
||||
priority = true;
|
||||
ambiguous = false; // force pick priority
|
||||
}
|
||||
|
||||
// For priority effects (strength/resistance) allow even if ambiguous, with wide threshold.
|
||||
// Treat ambiguous matches as acceptable when the best is clearly closer than the runner-up (ratio check).
|
||||
if (ambiguous && !priority) {
|
||||
double ratio = nearest.secondDiff / (double) Math.max(1, nearest.diff);
|
||||
if (ratio < AMBIGUITY_RATIO) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean allowed = priority
|
||||
? chosenDiff < PRIORITY_THRESHOLD
|
||||
: chosenDiff < COLOR_THRESHOLD;
|
||||
|
||||
if (chosenIdx < 0 || chosenIdx >= PALETTE.size() || !allowed) {
|
||||
return null;
|
||||
}
|
||||
Holder<MobEffect> guessed = PALETTE.get(chosenIdx);
|
||||
if (isNightVision(chosenIdx)) return null; // do not infer night vision heuristically
|
||||
|
||||
return guessed;
|
||||
}
|
||||
|
||||
private static boolean isPriority(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return PRIORITY_EFFECTS.contains(e);
|
||||
}
|
||||
|
||||
private static boolean isFire(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return e.equals(MobEffects.FIRE_RESISTANCE);
|
||||
}
|
||||
|
||||
private static boolean isStrength(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return e.equals(MobEffects.STRENGTH);
|
||||
}
|
||||
|
||||
// If ambiguous, force-pick these instead of skipping
|
||||
private static boolean isForcePick(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return FORCE_PICK_EFFECTS.contains(e);
|
||||
}
|
||||
|
||||
private static boolean isNightVision(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return e.equals(MobEffects.NIGHT_VISION);
|
||||
}
|
||||
|
||||
private static Nearest nearest(int color, int[] palette) {
|
||||
int best = -1, second = -1;
|
||||
int bestDiff = Integer.MAX_VALUE;
|
||||
int secondDiff = Integer.MAX_VALUE;
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
for (int i = 0; i < palette.length; i++) {
|
||||
int c = palette[i];
|
||||
int dr = ((c >> 16) & 0xFF) - r;
|
||||
int dg = ((c >> 8) & 0xFF) - g;
|
||||
int db = (c & 0xFF) - b;
|
||||
int diff = dr * dr + dg * dg + db * db;
|
||||
if (diff < bestDiff) {
|
||||
secondDiff = bestDiff;
|
||||
second = best;
|
||||
bestDiff = diff;
|
||||
best = i;
|
||||
} else if (diff < secondDiff) {
|
||||
secondDiff = diff;
|
||||
second = i;
|
||||
}
|
||||
}
|
||||
// require reasonably close match
|
||||
return new Nearest(best, bestDiff, second, secondDiff);
|
||||
}
|
||||
|
||||
private static Holder<MobEffect> at(int idx) {
|
||||
if (idx < 0 || idx >= PALETTE.size()) return null;
|
||||
return PALETTE.get(idx);
|
||||
}
|
||||
|
||||
private static List<Holder<MobEffect>> buildPalette() {
|
||||
try {
|
||||
return BuiltInRegistries.MOB_EFFECT.listElements().collect(Collectors.toCollection(ArrayList::new));
|
||||
} catch (Exception e) {
|
||||
// Fallback to static list to avoid nulls during very early load
|
||||
return Arrays.asList(
|
||||
MobEffects.SPEED,
|
||||
MobEffects.SLOWNESS,
|
||||
MobEffects.HASTE,
|
||||
MobEffects.MINING_FATIGUE,
|
||||
MobEffects.STRENGTH,
|
||||
MobEffects.JUMP_BOOST,
|
||||
MobEffects.NAUSEA,
|
||||
MobEffects.REGENERATION,
|
||||
MobEffects.RESISTANCE,
|
||||
MobEffects.FIRE_RESISTANCE,
|
||||
MobEffects.WATER_BREATHING,
|
||||
MobEffects.INVISIBILITY,
|
||||
MobEffects.BLINDNESS,
|
||||
MobEffects.NIGHT_VISION,
|
||||
MobEffects.HUNGER,
|
||||
MobEffects.WEAKNESS,
|
||||
MobEffects.POISON,
|
||||
MobEffects.SLOW_FALLING,
|
||||
MobEffects.DOLPHINS_GRACE,
|
||||
MobEffects.CONDUIT_POWER,
|
||||
MobEffects.HERO_OF_THE_VILLAGE,
|
||||
MobEffects.WITHER,
|
||||
MobEffects.HEALTH_BOOST,
|
||||
MobEffects.SATURATION,
|
||||
MobEffects.LUCK,
|
||||
MobEffects.UNLUCK,
|
||||
MobEffects.GLOWING,
|
||||
MobEffects.LEVITATION,
|
||||
MobEffects.BAD_OMEN,
|
||||
MobEffects.DARKNESS,
|
||||
MobEffects.TRIAL_OMEN,
|
||||
MobEffects.RAID_OMEN,
|
||||
MobEffects.WIND_CHARGED,
|
||||
MobEffects.WEAVING,
|
||||
MobEffects.OOZING,
|
||||
MobEffects.INFESTED,
|
||||
MobEffects.BREATH_OF_THE_NAUTILUS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private record Nearest(int idx, int diff, int secondIdx, int secondDiff) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,16 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.effect.heuristics;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.particles.ColorParticleOption;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.effect.MobEffect;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import dev.loki.lovisual.mixins.accessors.state.EffectParticleEffectAccessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import dev.loki.lovisual.util.player.effect.heuristics.StatusEffectInference;
|
||||
import dev.loki.lovisual.util.player.effect.tracker.StatusEffectTracker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Heuristics to guess effects from colored entity-effect particles.
|
||||
*/
|
||||
|
|
@ -36,91 +20,15 @@ public enum StatusEffectHeuristics {
|
|||
private static final java.util.Map<Integer, java.util.Map<Holder<MobEffect>, Long>> LAST_SEEN = new java.util.HashMap<>();
|
||||
private static final long HEURISTIC_TIMEOUT_MS = 10000L;
|
||||
|
||||
// Dynamic palette of effects + colors from the registry to survive version bumps.
|
||||
private static final List<Holder<MobEffect>> PALETTE = buildPalette();
|
||||
private static final int[] COLORS = computeColors(PALETTE);
|
||||
private static final int[] NORMALIZED_COLORS = computeNormalizedColors(COLORS);
|
||||
private static final Set<Holder<MobEffect>> PRIORITY_EFFECTS = Set.of(
|
||||
MobEffects.STRENGTH,
|
||||
MobEffects.RESISTANCE
|
||||
);
|
||||
private static final Set<Holder<MobEffect>> FORCE_PICK_EFFECTS = Set.of(
|
||||
MobEffects.STRENGTH,
|
||||
MobEffects.RESISTANCE
|
||||
);
|
||||
|
||||
// Loose thresholds: particle tint varies a lot across versions; keep wide but still reject wild mismatches.
|
||||
private static final int COLOR_THRESHOLD = 42000;
|
||||
private static final int PRIORITY_THRESHOLD = 120000; // allow rougher matches for hard-to-distinguish colors
|
||||
private static final int AMBIGUITY_DELTA = 2000;
|
||||
private static final double AMBIGUITY_RATIO = 1.05; // accept best if second-best is clearly worse
|
||||
|
||||
public static void handle(ParticleOptions particle, double px, double py, double pz) {
|
||||
Holder<MobEffect> direct = directEffectFromParticle(particle);
|
||||
Holder<MobEffect> direct = ParticleColors.directEffectFromParticle(particle);
|
||||
if (direct != null) {
|
||||
registerHeuristic(direct, px, py, pz);
|
||||
return;
|
||||
}
|
||||
|
||||
int color = extractColor(particle);
|
||||
if (color == 0) return;
|
||||
// Normalize brightness so darker/lighter particles still map to the same hue bucket.
|
||||
int normalizedColor = normalizeColor(color);
|
||||
Nearest nearest = nearest(normalizedColor, NORMALIZED_COLORS);
|
||||
boolean priority = isPriority(nearest.idx); // e.g. strength/resistance
|
||||
boolean ambiguous = nearest.secondDiff - nearest.diff < AMBIGUITY_DELTA;
|
||||
|
||||
// Prefer fire resistance when it's one of the ambiguous pair so we don't mislabel it as strength.
|
||||
int chosenIdx = nearest.idx;
|
||||
int chosenDiff = nearest.diff;
|
||||
// Strength vs fire-resistance: both share orange/red; bias using channel dominance.
|
||||
if ((isStrength(nearest.idx) && isFire(nearest.secondIdx)) || (isStrength(nearest.secondIdx) && isFire(nearest.idx))) {
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
// Strength tint is deep red; fire resistance skews orange (more green). Bias toward fire only when clearly orange.
|
||||
float hue = hueDegrees(r, g, b);
|
||||
boolean looksFire = (g >= r * 0.8f) && hue >= 25f;
|
||||
if (looksFire) {
|
||||
chosenIdx = isFire(nearest.idx) ? nearest.idx : nearest.secondIdx;
|
||||
chosenDiff = isFire(nearest.idx) ? nearest.diff : nearest.secondDiff;
|
||||
} else {
|
||||
chosenIdx = isStrength(nearest.idx) ? nearest.idx : nearest.secondIdx;
|
||||
chosenDiff = isStrength(nearest.idx) ? nearest.diff : nearest.secondDiff;
|
||||
}
|
||||
priority = isPriority(chosenIdx);
|
||||
ambiguous = false; // resolved manually
|
||||
} else if (ambiguous && (isFire(nearest.idx) ^ isFire(nearest.secondIdx))) {
|
||||
chosenIdx = isFire(nearest.idx) ? nearest.idx : nearest.secondIdx;
|
||||
chosenDiff = isFire(nearest.idx) ? nearest.diff : nearest.secondDiff;
|
||||
priority = isPriority(chosenIdx);
|
||||
ambiguous = false;
|
||||
} else if (ambiguous && !priority && isPriority(nearest.secondIdx)) {
|
||||
chosenIdx = nearest.secondIdx;
|
||||
chosenDiff = nearest.secondDiff;
|
||||
priority = true;
|
||||
ambiguous = false; // force pick priority
|
||||
}
|
||||
|
||||
// For priority effects (strength/resistance) allow even if ambiguous, with wide threshold.
|
||||
// Treat ambiguous matches as acceptable when the best is clearly closer than the runner-up (ratio check).
|
||||
if (ambiguous && !priority) {
|
||||
double ratio = nearest.secondDiff / (double) Math.max(1, nearest.diff);
|
||||
if (ratio < AMBIGUITY_RATIO) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean allowed = priority
|
||||
? chosenDiff < PRIORITY_THRESHOLD
|
||||
: chosenDiff < COLOR_THRESHOLD;
|
||||
|
||||
if (chosenIdx < 0 || chosenIdx >= PALETTE.size() || !allowed) {
|
||||
return;
|
||||
}
|
||||
Holder<MobEffect> guessed = PALETTE.get(chosenIdx);
|
||||
if (isNightVision(chosenIdx)) return; // do not infer night vision heuristically
|
||||
|
||||
Holder<MobEffect> guessed = ParticlePalette.guess(ParticleColors.extractColor(particle));
|
||||
if (guessed == null) return;
|
||||
registerHeuristic(guessed, px, py, pz);
|
||||
}
|
||||
|
||||
|
|
@ -174,37 +82,6 @@ public enum StatusEffectHeuristics {
|
|||
StatusEffectTracker.apply(target.getId(), guessed, amplifier, heuristicDurationTicks, false, true, true, true, hideDuration);
|
||||
}
|
||||
|
||||
private static boolean isPriority(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return PRIORITY_EFFECTS.contains(e);
|
||||
}
|
||||
|
||||
private static boolean isFire(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return e.equals(MobEffects.FIRE_RESISTANCE);
|
||||
}
|
||||
|
||||
private static boolean isStrength(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return e.equals(MobEffects.STRENGTH);
|
||||
}
|
||||
|
||||
// If ambiguous, force-pick these instead of skipping
|
||||
private static boolean isForcePick(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return FORCE_PICK_EFFECTS.contains(e);
|
||||
}
|
||||
|
||||
private static boolean isNightVision(int idx) {
|
||||
Holder<MobEffect> e = at(idx);
|
||||
if (e == null) return false;
|
||||
return e.equals(MobEffects.NIGHT_VISION);
|
||||
}
|
||||
|
||||
private static void markSeen(int entityId, Holder<MobEffect> effect, long time) {
|
||||
var map = LAST_SEEN.computeIfAbsent(entityId, k -> new java.util.HashMap<>());
|
||||
map.put(effect, time);
|
||||
|
|
@ -231,157 +108,4 @@ public enum StatusEffectHeuristics {
|
|||
}
|
||||
removeEntities.forEach(LAST_SEEN::remove);
|
||||
}
|
||||
|
||||
private static int[] computeColors(List<Holder<MobEffect>> palette) {
|
||||
int[] colors = new int[palette.size()];
|
||||
for (int i = 0; i < palette.size(); i++) {
|
||||
colors[i] = palette.get(i).value().getColor();
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
private static int[] computeNormalizedColors(int[] colors) {
|
||||
int[] normalized = new int[colors.length];
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
normalized[i] = normalizeColor(colors[i]);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static int normalizeColor(int color) {
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
int max = Math.max(r, Math.max(g, b));
|
||||
if (max <= 0) return 0;
|
||||
float scale = 255f / (float) max;
|
||||
int nr = Math.min(255, Math.round(r * scale));
|
||||
int ng = Math.min(255, Math.round(g * scale));
|
||||
int nb = Math.min(255, Math.round(b * scale));
|
||||
return (nr << 16) | (ng << 8) | nb;
|
||||
}
|
||||
|
||||
// Basic HSV hue calculation (degrees 0-360)
|
||||
private static float hueDegrees(int r, int g, int b) {
|
||||
float rf = r / 255f;
|
||||
float gf = g / 255f;
|
||||
float bf = b / 255f;
|
||||
float max = Math.max(rf, Math.max(gf, bf));
|
||||
float min = Math.min(rf, Math.min(gf, bf));
|
||||
float delta = max - min;
|
||||
if (delta <= 0f) return 0f;
|
||||
float hue;
|
||||
if (max == rf) {
|
||||
hue = (gf - bf) / delta;
|
||||
} else if (max == gf) {
|
||||
hue = 2f + (bf - rf) / delta;
|
||||
} else {
|
||||
hue = 4f + (rf - gf) / delta;
|
||||
}
|
||||
hue *= 60f;
|
||||
if (hue < 0f) hue += 360f;
|
||||
return hue;
|
||||
}
|
||||
|
||||
private static Nearest nearest(int color, int[] palette) {
|
||||
int best = -1, second = -1;
|
||||
int bestDiff = Integer.MAX_VALUE;
|
||||
int secondDiff = Integer.MAX_VALUE;
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
for (int i = 0; i < palette.length; i++) {
|
||||
int c = palette[i];
|
||||
int dr = ((c >> 16) & 0xFF) - r;
|
||||
int dg = ((c >> 8) & 0xFF) - g;
|
||||
int db = (c & 0xFF) - b;
|
||||
int diff = dr * dr + dg * dg + db * db;
|
||||
if (diff < bestDiff) {
|
||||
secondDiff = bestDiff;
|
||||
second = best;
|
||||
bestDiff = diff;
|
||||
best = i;
|
||||
} else if (diff < secondDiff) {
|
||||
secondDiff = diff;
|
||||
second = i;
|
||||
}
|
||||
}
|
||||
// require reasonably close match
|
||||
return new Nearest(best, bestDiff, second, secondDiff);
|
||||
}
|
||||
|
||||
private static Holder<MobEffect> at(int idx) {
|
||||
if (idx < 0 || idx >= PALETTE.size()) return null;
|
||||
return PALETTE.get(idx);
|
||||
}
|
||||
|
||||
private static List<Holder<MobEffect>> buildPalette() {
|
||||
try {
|
||||
return BuiltInRegistries.MOB_EFFECT.listElements().collect(Collectors.toCollection(ArrayList::new));
|
||||
} catch (Exception e) {
|
||||
// Fallback to static list to avoid nulls during very early load
|
||||
return Arrays.asList(
|
||||
MobEffects.SPEED,
|
||||
MobEffects.SLOWNESS,
|
||||
MobEffects.HASTE,
|
||||
MobEffects.MINING_FATIGUE,
|
||||
MobEffects.STRENGTH,
|
||||
MobEffects.JUMP_BOOST,
|
||||
MobEffects.NAUSEA,
|
||||
MobEffects.REGENERATION,
|
||||
MobEffects.RESISTANCE,
|
||||
MobEffects.FIRE_RESISTANCE,
|
||||
MobEffects.WATER_BREATHING,
|
||||
MobEffects.INVISIBILITY,
|
||||
MobEffects.BLINDNESS,
|
||||
MobEffects.NIGHT_VISION,
|
||||
MobEffects.HUNGER,
|
||||
MobEffects.WEAKNESS,
|
||||
MobEffects.POISON,
|
||||
MobEffects.SLOW_FALLING,
|
||||
MobEffects.DOLPHINS_GRACE,
|
||||
MobEffects.CONDUIT_POWER,
|
||||
MobEffects.HERO_OF_THE_VILLAGE,
|
||||
MobEffects.WITHER,
|
||||
MobEffects.HEALTH_BOOST,
|
||||
MobEffects.SATURATION,
|
||||
MobEffects.LUCK,
|
||||
MobEffects.UNLUCK,
|
||||
MobEffects.GLOWING,
|
||||
MobEffects.LEVITATION,
|
||||
MobEffects.BAD_OMEN,
|
||||
MobEffects.DARKNESS,
|
||||
MobEffects.TRIAL_OMEN,
|
||||
MobEffects.RAID_OMEN,
|
||||
MobEffects.WIND_CHARGED,
|
||||
MobEffects.WEAVING,
|
||||
MobEffects.OOZING,
|
||||
MobEffects.INFESTED,
|
||||
MobEffects.BREATH_OF_THE_NAUTILUS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static Holder<MobEffect> directEffectFromParticle(ParticleOptions particle) {
|
||||
if (particle == ParticleTypes.RAID_OMEN) return MobEffects.RAID_OMEN;
|
||||
if (particle == ParticleTypes.TRIAL_OMEN) return MobEffects.TRIAL_OMEN;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int extractColor(ParticleOptions particle) {
|
||||
if (particle instanceof EffectParticleEffectAccessor effect) {
|
||||
return effect.lovisual$getColor() & 0xFFFFFF;
|
||||
}
|
||||
if (particle instanceof ColorParticleOption tinted) {
|
||||
int r = Math.min(255, Math.max(0, Math.round(tinted.getRed() * 255f)));
|
||||
int g = Math.min(255, Math.max(0, Math.round(tinted.getGreen() * 255f)));
|
||||
int b = Math.min(255, Math.max(0, Math.round(tinted.getBlue() * 255f)));
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private record Nearest(int idx, int diff, int secondIdx, int secondDiff) {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.effect.heuristics;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.effect.tracker;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.effect.tracker;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.effect.tracker;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.health;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.health;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.interaction;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.interaction;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.interaction;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.interaction;
|
||||
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.inventory.firework;
|
||||
|
||||
import dev.loki.lovisual.mixins.accessors.inventory.PlayerInventoryAccessor;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.movement;
|
||||
|
||||
import net.minecraft.client.KeyMapping;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.movement;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.movement;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.movement;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation.remote;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation.remote;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.player.simulation;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue