mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 21:19:52 +01:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eafbc87e7 | ||
|
|
f84460d637 | ||
|
|
917d6affea | ||
|
|
cb2d795461 | ||
|
|
6e934e0ff1 | ||
|
|
375adadbe5 | ||
|
|
082a756f1b | ||
|
|
9fc8a8e2a2 | ||
|
|
1413475679 | ||
|
|
86504e685e | ||
|
|
9df81044d1 | ||
|
|
c5fd7591ea | ||
|
|
cd8e43f945 | ||
|
|
8df112aab4 | ||
|
|
94c04ce102 | ||
|
|
3a170202ca | ||
|
|
46937693a0 | ||
|
|
b391d0b036 | ||
|
|
1061e003f7 | ||
|
|
a2b573abca | ||
|
|
4571d141e6 | ||
|
|
d70225b03c |
No files matched your search
@@ -0,0 +1,20 @@
|
||||
# BlitzKit 1.30.7
|
||||
|
||||
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
|
||||
|
||||
## Features
|
||||
|
||||
- Dragging items in the Tier List now automatically scrolls the screen if you reach the top/bottom edge of the screen
|
||||
|
||||
## Fixes
|
||||
|
||||
- [Mark Greene] Clicking on the armor multiple times with HE no longer crashes BlitzKit
|
||||
- [Mark Greene] Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration
|
||||
- The lighting animation no longer stops prematurely when BlitzKit is unfocused
|
||||
- The spot light angle has been increased to more evenly light up larger tanks
|
||||
- BlitzKit once again uses soft shadows, reducing the pixelated shadows
|
||||
- Fixed the weird patterns that formed on the tracks of tanks under self-shadows
|
||||
- Increased the floor size and light decay to create the illusion that the floor is infinite
|
||||
- [Mark Greene] Safari no longer crashes when unloading textures for unloaded tanks
|
||||
- The gun depression transition between two constraints (like often behind tanks) now uses a gentler zoned and can never be 0
|
||||
- [Mark Greene] Armor plate names in Static Armor are now properly capitalized in English and translated to other languages
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"version": "1.30.6",
|
||||
"version": "1.30.7",
|
||||
"license": "BlitzKit License",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
"hp": "{value} 生命值",
|
||||
"x": "x{value}",
|
||||
"percentage": "{value}%",
|
||||
"deg_s": "{value}度°/秒",
|
||||
"deg_s": "{value}度每秒",
|
||||
"plus": "+{value}"
|
||||
},
|
||||
"gun_types": {
|
||||
"auto_loader": "弹夹型坦克",
|
||||
"auto_reloader": "自动装填型坦克",
|
||||
"auto_reloader": "弹鼓型坦克",
|
||||
"regular": "单发"
|
||||
},
|
||||
"shells": {
|
||||
"ap": "穿甲弹",
|
||||
"ap_cr": "硬式复合穿甲弹",
|
||||
"hc": "反战车高爆弹",
|
||||
"hc": "破甲弹",
|
||||
"he": "高爆弹"
|
||||
},
|
||||
"player_count_period": {
|
||||
@@ -60,23 +60,23 @@
|
||||
"3": "平均",
|
||||
"4": "高于平均",
|
||||
"5": "好",
|
||||
"6": "非常好",
|
||||
"6": "好",
|
||||
"7": "很好",
|
||||
"8": "极品",
|
||||
"9": "超级强势玩家"
|
||||
"8": "非常好",
|
||||
"9": "极品"
|
||||
},
|
||||
"bkni_percentile": {
|
||||
"0": "Super malum",
|
||||
"1": "Malum",
|
||||
"2": "非常差",
|
||||
"0": "垃圾",
|
||||
"1": "非常差",
|
||||
"2": "很差",
|
||||
"3": "差",
|
||||
"4": "低于平均",
|
||||
"5": "平均",
|
||||
"6": "高于平均",
|
||||
"6": "较好",
|
||||
"7": "好",
|
||||
"8": "非常好",
|
||||
"9": "極品",
|
||||
"10": "超级强势玩家"
|
||||
"8": "很好",
|
||||
"9": "非常好",
|
||||
"10": "极品"
|
||||
},
|
||||
"nations": {
|
||||
"ussr": "苏联",
|
||||
@@ -101,7 +101,7 @@
|
||||
"other": "X系"
|
||||
},
|
||||
"tank_class_short": {
|
||||
"-1": "未识别",
|
||||
"-1": "未知",
|
||||
"0": "轻型坦克",
|
||||
"1": "中型坦克",
|
||||
"2": "重型战车",
|
||||
@@ -114,14 +114,14 @@
|
||||
"3": "坦克歼击车"
|
||||
},
|
||||
"tank_class_medium": {
|
||||
"-1": "未识别",
|
||||
"-1": "未知",
|
||||
"0": "轻型坦克",
|
||||
"1": "中型坦克",
|
||||
"2": "重型坦克",
|
||||
"3": "坦克歼击车"
|
||||
},
|
||||
"tree_type": {
|
||||
"-1": "未识别",
|
||||
"-1": "未知",
|
||||
"0": "科技树",
|
||||
"1": "金币车",
|
||||
"2": "收藏车"
|
||||
@@ -156,7 +156,7 @@
|
||||
"normalized_shots": "开火次数",
|
||||
"normalized_spotted": "侦查次数",
|
||||
"normalized_survived_battles": "存活率",
|
||||
"normalized_win_and_survived": "存活胜利率",
|
||||
"normalized_win_and_survived": "胜利并幸存的概率",
|
||||
"normalized_wins": "胜率",
|
||||
"normalized_xp": "经验值",
|
||||
"normalized_battle_life_time": "战斗时间",
|
||||
@@ -194,7 +194,7 @@
|
||||
"tank_id_not_found": "# 找不到坦克\n无法找到ID为 “{search}” 的坦克。 请尝试输入坦克的名称而不是数字。",
|
||||
"no_tank_stats": "# 没有这辆战车的数据\n这个玩家没有可用资料。 这可能不是你要找的玩家。",
|
||||
"get_help": "Discord的协助服务器",
|
||||
"uncaught_error": "# BlitzKit 发生错误!\n我们对此感到抱歉。 欢迎加入[官方Discord](https://discord.gg/nDt7AjGJQH),以方便帮助你解决问题。",
|
||||
"uncaught_error": "# BlitzKit 发生错误!\n我们对此感到抱歉。 欢迎加入[【官方Discord】](https://discord.gg/nDt7AjGJQH),以方便帮助你解决问题。",
|
||||
"preview": "# 此命令处于预览\n\n您只能在[官方Discord服务器](https://discord.gg/nDt7AjGJQH)访问此命令。该命令将很快可用。",
|
||||
"query_too_small": "# 搜寻内容过短\n您的搜寻必需至少包含 3 个字元。",
|
||||
"query_too_long": "# 搜索内容过长\n您搜索的内容必须少于 100 个字符。"
|
||||
@@ -293,7 +293,7 @@
|
||||
"career": "玩家资料",
|
||||
"today": "今日:",
|
||||
"days": "{days} 天",
|
||||
"custom": "{day_1} - {day_2} 天"
|
||||
"custom": "天数 {day_1} 至 {day_2}"
|
||||
},
|
||||
"filters": {
|
||||
"none": "无筛选器",
|
||||
@@ -320,7 +320,7 @@
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"dead": "# 统计指令现已停用\nBlitzKit Discord 机器人此前依赖 BlitzStars 获取周期性的统计数据。由于 BlitzStars 已启用严格的 Cloudflare 防护,导致 BlitzKit 无法继续使用其 API。这已导致以下指令失效:\n/统计\n/今日\n/损坏\n/演变\n/完整统计\n请使用 [Aftermath](<https://amth.one/invite>) \n作为完全独立于 BlitzStars 的替代方案。由 BlitzKit 托管的统计指令将在近期内恢复。",
|
||||
"dead": "# 统计指令现已停用\nBlitzKit Discord 机器人此前依赖 BlitzStars 获取周期性的统计数据。由于 BlitzStars 已启用严格的 Cloudflare 防护,导致 BlitzKit 无法继续使用其 API。这已导致以下指令失效:\n\n/stats - 统计\n/today - 今日情况\n/breakdown - 被摧毁\n/evolution - 变化情况\n/full-stats - 完整统计\n\n请使用 [Aftermath](<https://amth.one/invite>) 。\n作为完全独立于 BlitzStars 的替代方案,由 BlitzKit 托管的统计指令将在近期内恢复。",
|
||||
"replay": {
|
||||
"name": "回放",
|
||||
"description": "显示重播结果",
|
||||
@@ -347,17 +347,17 @@
|
||||
"body": {
|
||||
"subtitle": "研发",
|
||||
"total": "全部",
|
||||
"research": "研发:{cost}",
|
||||
"upgrades": "升级:{cost}",
|
||||
"purchase": "购买:{cost}",
|
||||
"equipment": "装备:{cost}",
|
||||
"estimation": "# 这是一个估计值!\n\n这个列表可能包含已经研发过的战车,因为 Wargaming 提供了不正确的资料。 如果可以,请尝试在同一研发线路中提供一个 “起始战车” 。"
|
||||
"research": "研发费用:{cost}",
|
||||
"upgrades": "升级费用:{cost}",
|
||||
"purchase": "购买费用:{cost}",
|
||||
"equipment": "装备费用:{cost}",
|
||||
"estimation": "# 这是一个估算值!\n\n因为 Wargaming 提供了不正确的资料,所以该列表可能包含已经研发过的战车。 如果可以,请尝试在同一研发线路中提供一个 “起始战车” 。"
|
||||
},
|
||||
"errors": {
|
||||
"non_tech_tree": "# \"{tank}\" 不在科技树里\n\n你不能研发一个 加值/收藏家 坦克。",
|
||||
"tier_1": "# {tank} 是一级坦克\n\n一级坦克一直是对所有玩家免费解锁的。",
|
||||
"non_tech_tree": "# \"{tank}\" 不在科技树里\n\n你不能研发 金币车 或 收藏车。",
|
||||
"tier_1": "# {tank} 是一阶坦克\n\n一阶坦克一直是对所有玩家免费解锁的。",
|
||||
"already_researched": "# \"{tank}\" 已完成研发\n\n请尝试在同一研发线路上提供“起始坦克”, 以查看全部您已研发过的坦克。",
|
||||
"start_end_equal": "# \"{tank}\" 重复提供了\n\n请确认 \"初始坦克\" 和 \"目标坦克\" 是不同的。",
|
||||
"start_end_equal": "# \"{tank}\" 被重复提供\n\n请确认 \"初始坦克\" 和 \"目标坦克\" 是不同的。",
|
||||
"tanks_not_on_line": "# “{tank_1}” 和 “{tank_2}” 不在同一个研发线路上\n\n请确认“起始坦克” 和 “目标坦克” 在同一研发线路中。",
|
||||
"unordered": "# “{tank_1}” 阶级比 “{tank_2}” 高\n\n请以提供 {tank_2} 作为起始坦克,因为它的阶级更低。"
|
||||
}
|
||||
@@ -572,7 +572,7 @@
|
||||
},
|
||||
"owned_tanks": {
|
||||
"name": "已拥有的坦克",
|
||||
"description": "在一个等级中显示所有已拥有的坦克",
|
||||
"description": "显示该阶级中您所拥有的全部战车",
|
||||
"body": {
|
||||
"title": "# {name} 所拥有的 {tier} 阶坦克\n\n"
|
||||
}
|
||||
@@ -593,7 +593,7 @@
|
||||
"description": "检查机器人是否处于活动状态。",
|
||||
"body": {
|
||||
"pong": "Pong 🏓",
|
||||
"pong_with_time": "Pong 🏓 - {time} 微秒"
|
||||
"pong_with_time": "Pong 🏓 - {time} 毫秒"
|
||||
},
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
@@ -809,7 +809,7 @@
|
||||
"mixer": {
|
||||
"name": "组装",
|
||||
"button": "混合搭配",
|
||||
"description": "混合坦克模块",
|
||||
"description": "混搭不同坦克间的模块",
|
||||
"seo_description": "《坦克世界:闪击战》的车体, 炮塔, 主炮混合搭配",
|
||||
"screenshot": "屏幕截图",
|
||||
"randomize": "随机搭配"
|
||||
@@ -984,7 +984,7 @@
|
||||
"equalizer": {
|
||||
"title": "Equalizer",
|
||||
"equalize": "Equalize",
|
||||
"beta": "Beta"
|
||||
"beta": "测试版"
|
||||
},
|
||||
"modules": {
|
||||
"title": "坦克模块",
|
||||
@@ -1018,8 +1018,8 @@
|
||||
"title": "属性",
|
||||
"compare": {
|
||||
"label": "比较",
|
||||
"class": "{tier} {class}",
|
||||
"tier": "All tier {tier}",
|
||||
"class": "{tier} 阶的 {class}",
|
||||
"tier": "所有{tier} 阶的",
|
||||
"all": "所有坦克"
|
||||
},
|
||||
"values": {
|
||||
@@ -1102,8 +1102,8 @@
|
||||
"shellCapacity": "携弹量",
|
||||
"gunType": "炮管类型",
|
||||
"penetrationAt250m": "两百五十米处的穿透力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"burstShells": "爆破计数",
|
||||
"burstInterShell": "爆破弹"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1156,7 +1156,7 @@
|
||||
"skills": {
|
||||
"clear": "清除",
|
||||
"maximize": "最大化",
|
||||
"equalize": "Equalize"
|
||||
"equalize": "平衡"
|
||||
},
|
||||
"modules": {
|
||||
"stock": "白板",
|
||||
|
||||
@@ -22,7 +22,7 @@ export function ArmorPlateDisplay() {
|
||||
const highlightArmor = Tankopedia.use((state) => state.highlightArmor);
|
||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||
const input = useRef<HTMLInputElement>(null);
|
||||
const { locale } = useLocale();
|
||||
const { locale, strings } = useLocale();
|
||||
|
||||
if (highlightArmor === undefined) return null;
|
||||
|
||||
@@ -44,7 +44,10 @@ export function ArmorPlateDisplay() {
|
||||
>
|
||||
<Flex direction="column">
|
||||
<Text weight="bold">
|
||||
{layerTypeNames[highlightArmor.type]}{" "}
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
.element[layerTypeNames[highlightArmor.type]]
|
||||
}{" "}
|
||||
{highlightArmor.thickness.toFixed(0)}
|
||||
<Text size="1" weight="regular">
|
||||
mm
|
||||
|
||||
@@ -42,10 +42,10 @@ const TRACER_THIN = TRACER_THICK / 2;
|
||||
|
||||
export function ShotDisplay() {
|
||||
const shot = Tankopedia.use((state) => state.shot);
|
||||
const inTracer = useRef<Mesh>(null!);
|
||||
const outTracer = useRef<Mesh>(null!);
|
||||
const splashRadiusWrapper = useRef<Group>(null!);
|
||||
const splashRadiusMaterial = useRef<MeshBasicMaterial>(null!);
|
||||
const inTracer = useRef<Mesh>(null);
|
||||
const outTracer = useRef<Mesh>(null);
|
||||
const splashRadiusWrapper = useRef<Group>(null);
|
||||
const splashRadiusMaterial = useRef<MeshBasicMaterial>(null);
|
||||
const { locale } = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -63,10 +63,10 @@ export function ShotDisplay() {
|
||||
outlineMaterial,
|
||||
).computeLineDistances();
|
||||
|
||||
splashRadiusWrapper.current.add(outline);
|
||||
splashRadiusWrapper.current?.add(outline);
|
||||
|
||||
return () => {
|
||||
splashRadiusWrapper.current.remove(outline);
|
||||
splashRadiusWrapper.current?.remove(outline);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -255,11 +255,11 @@ export function ShotDisplay() {
|
||||
}
|
||||
|
||||
interface AnimatorProps {
|
||||
inTracer: RefObject<Mesh>;
|
||||
outTracer: RefObject<Mesh>;
|
||||
inTracer: RefObject<Mesh | null>;
|
||||
outTracer: RefObject<Mesh | null>;
|
||||
|
||||
splashRadiusWrapper: RefObject<Group>;
|
||||
splashRadiusMaterial: RefObject<MeshBasicMaterial>;
|
||||
splashRadiusWrapper: RefObject<Group | null>;
|
||||
splashRadiusMaterial: RefObject<MeshBasicMaterial | null>;
|
||||
|
||||
inLength: number;
|
||||
outLength: number;
|
||||
@@ -283,17 +283,23 @@ function Animator({
|
||||
const tracerT1 = t % 1;
|
||||
const tracerT2 = (t + 0.5) % 1;
|
||||
|
||||
inTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT1 - 0.5), 1);
|
||||
inTracer.current.position.set(0, (1 - tracerT1) * inLength, 0);
|
||||
if (inTracer.current) {
|
||||
inTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT1 - 0.5), 1);
|
||||
inTracer.current.position.set(0, (1 - tracerT1) * inLength, 0);
|
||||
|
||||
if (outTracer.current) {
|
||||
outTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT2 - 0.5), 1);
|
||||
outTracer.current.position.set(0, tracerT2 * outLength, 0);
|
||||
if (outTracer.current) {
|
||||
outTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT2 - 0.5), 1);
|
||||
outTracer.current.position.set(0, tracerT2 * outLength, 0);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
|
||||
if (shot.splashRadius !== undefined) {
|
||||
if (
|
||||
shot.splashRadius !== undefined &&
|
||||
splashRadiusWrapper.current &&
|
||||
splashRadiusMaterial.current
|
||||
) {
|
||||
if (animationStartTime === null) animationStartTime = t;
|
||||
|
||||
const scale =
|
||||
|
||||
+4
-3
@@ -161,9 +161,10 @@ export function SpacedArmorSceneComponent({
|
||||
|
||||
if (
|
||||
!allowRicochet &&
|
||||
noDuplicateIntersections.every((intersection) => {
|
||||
intersection.object.userData.type !== ArmorType.Primary;
|
||||
})
|
||||
noDuplicateIntersections.every(
|
||||
(intersection) =>
|
||||
intersection.object.userData.type !== ArmorType.Primary,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+28
-18
@@ -1,14 +1,20 @@
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { clamp, times } from "lodash-es";
|
||||
import { Quicklime } from "quicklime";
|
||||
import { useEffect, useRef, useState, type RefObject } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import { HemisphereLight, SpotLight, type Group } from "three";
|
||||
import { degToRad, lerp } from "three/src/math/MathUtils.js";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
|
||||
import { HelpingSpotLight } from "../../../../../HelpingSpotLight";
|
||||
|
||||
const ANGLE = degToRad(10);
|
||||
const ANGLE = degToRad(12);
|
||||
const REVEAL_ANIMATION_TIME = 3;
|
||||
const TRANSITION_ANIMATION_TIME = 0.5;
|
||||
|
||||
@@ -17,8 +23,8 @@ const THETA_OFFSET = degToRad(180 - 45);
|
||||
const LIGHT_DISTANCE = 20;
|
||||
const LIGHT_HEIGHT_0 = 4;
|
||||
const LIGHT_HEIGHT_1 = 6;
|
||||
const INTENSITY_0 = 6;
|
||||
const INTENSITY_1 = 3;
|
||||
const INTENSITY_0 = 6 * 9;
|
||||
const INTENSITY_1 = 3 * 9;
|
||||
const HEMISPHERE_INTENSITY = 2;
|
||||
|
||||
export const transitionEvent = new Quicklime<number>(0);
|
||||
@@ -36,8 +42,6 @@ export function Lighting() {
|
||||
|
||||
const [animate, setAnimate] = useState(true);
|
||||
|
||||
console.log(animate);
|
||||
|
||||
useEffect(() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.revealed = true;
|
||||
@@ -89,7 +93,7 @@ export function Lighting() {
|
||||
intensity={intensity}
|
||||
penumbra={1}
|
||||
castShadow={highGraphics}
|
||||
decay={0}
|
||||
decay={1}
|
||||
color="#ffffff"
|
||||
angle={0}
|
||||
/>
|
||||
@@ -111,17 +115,7 @@ interface AnimatorProps {
|
||||
function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) {
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
|
||||
useFrame(({ invalidate }) => {
|
||||
const dt = performance.now() / 1e3 - t0.current;
|
||||
const x = clamp(dt / animationTime.current, 0, 2);
|
||||
|
||||
if (x === 2) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const t = (0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5) ** 2;
|
||||
|
||||
const apply = useCallback((t: number) => {
|
||||
for (const child of wrapper.current.children) {
|
||||
if (child instanceof SpotLight) {
|
||||
child.angle = ANGLE * t;
|
||||
@@ -131,6 +125,22 @@ function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) {
|
||||
}
|
||||
|
||||
transitionEvent.dispatch(t);
|
||||
}, []);
|
||||
|
||||
useFrame(({ invalidate }) => {
|
||||
const dt = performance.now() / 1e3 - t0.current;
|
||||
const x = clamp(dt / animationTime.current, 0, 2);
|
||||
|
||||
if (x === 2) {
|
||||
apply(1);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// https://www.desmos.com/calculator/awsbahxjku
|
||||
const t = (0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5) ** 2;
|
||||
|
||||
apply(t);
|
||||
invalidate();
|
||||
|
||||
if (x >= 1 && Tankopedia.state.display !== requestedDisplay) {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { Quicklime, type QuicklimeEvent } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Mesh, MeshStandardMaterial } from "three";
|
||||
|
||||
const SIZE = 14;
|
||||
const SIZE = 2 ** 4;
|
||||
|
||||
export const screenshotReadyEvent = new Quicklime(false);
|
||||
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
localClippingEnabled: true,
|
||||
preserveDrawingBuffer: true,
|
||||
}}
|
||||
shadows="basic"
|
||||
shadows="soft"
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMissed={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { TankDefinition } from '@blitzkit/core';
|
||||
import { Box } from '@radix-ui/themes';
|
||||
import type { TankDefinition } from "@blitzkit/core";
|
||||
import { Box } from "@radix-ui/themes";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
type PointerEvent as ReactPointerEvent,
|
||||
} from 'react';
|
||||
import { TierList } from '../../stores/tierList';
|
||||
import { TankCard } from '../TankCard';
|
||||
import { tierListCardElements, tierListRowElements } from './Table/constants';
|
||||
} from "react";
|
||||
import { TierList } from "../../stores/tierList";
|
||||
import { TankCard } from "../TankCard";
|
||||
import { tierListCardElements, tierListRowElements } from "./Table/constants";
|
||||
|
||||
const SCROLL_ZONE = 1 / 3;
|
||||
|
||||
type TierListTileProps = {
|
||||
tank: TankDefinition;
|
||||
@@ -26,6 +28,33 @@ type TierListTileProps = {
|
||||
export function TierListTile(props: TierListTileProps) {
|
||||
const card = useRef<HTMLDivElement>(null);
|
||||
const lastPosition = useRef({ x: 0, y: 0 });
|
||||
const autoScroll = useRef(false);
|
||||
const lastT = useRef(0);
|
||||
|
||||
const autoScrollLoop = useCallback(() => {
|
||||
if (!autoScroll.current) return;
|
||||
|
||||
let scrollSpeed = 0;
|
||||
const v = lastPosition.current.y / window.innerHeight;
|
||||
|
||||
if (v <= SCROLL_ZONE) {
|
||||
scrollSpeed = v / SCROLL_ZONE - 1;
|
||||
} else if (v >= 1 - SCROLL_ZONE) {
|
||||
scrollSpeed = 1 - (1 - v) / SCROLL_ZONE;
|
||||
}
|
||||
|
||||
const t = performance.now();
|
||||
const dt = t - lastT.current;
|
||||
lastT.current = t;
|
||||
|
||||
window.scrollBy({
|
||||
behavior: "instant",
|
||||
top: scrollSpeed * dt,
|
||||
});
|
||||
|
||||
requestAnimationFrame(autoScrollLoop);
|
||||
}, []);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(event: ReactPointerEvent<HTMLSpanElement>) => {
|
||||
if (!card.current) return;
|
||||
@@ -34,22 +63,26 @@ export function TierListTile(props: TierListTileProps) {
|
||||
|
||||
const rect = card.current.getBoundingClientRect();
|
||||
|
||||
card.current.style.position = 'fixed';
|
||||
card.current.style.cursor = 'grabbing';
|
||||
card.current.style.zIndex = '1';
|
||||
card.current.style.position = "fixed";
|
||||
card.current.style.cursor = "grabbing";
|
||||
card.current.style.zIndex = "1";
|
||||
card.current.style.left = `${rect.left}px`;
|
||||
card.current.style.top = `${rect.top}px`;
|
||||
|
||||
lastPosition.current = { x: event.clientX, y: event.clientY };
|
||||
|
||||
window.scrollTo({ behavior: 'smooth', top: 124 });
|
||||
window.scrollTo({ behavior: "instant", top: 124 });
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
window.addEventListener("pointermove", handlePointerMove);
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
|
||||
TierList.mutate((draft) => {
|
||||
draft.dragging = true;
|
||||
});
|
||||
|
||||
autoScroll.current = true;
|
||||
lastT.current = performance.now();
|
||||
autoScrollLoop();
|
||||
},
|
||||
[],
|
||||
);
|
||||
@@ -141,12 +174,14 @@ export function TierListTile(props: TierListTileProps) {
|
||||
});
|
||||
}
|
||||
|
||||
card.current.style.position = 'static';
|
||||
card.current.style.zIndex = 'unset';
|
||||
card.current.style.cursor = 'grab';
|
||||
card.current.style.position = "static";
|
||||
card.current.style.zIndex = "unset";
|
||||
card.current.style.cursor = "grab";
|
||||
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
window.removeEventListener("pointermove", handlePointerMove);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
|
||||
autoScroll.current = false;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -168,7 +203,7 @@ export function TierListTile(props: TierListTileProps) {
|
||||
data-tile-index={props.isPlaced ? props.tileIndex : undefined}
|
||||
data-tile-id={props.tank.id}
|
||||
onPointerDown={handlePointerDown}
|
||||
style={{ cursor: 'grab', touchAction: 'none' }}
|
||||
style={{ cursor: "grab", touchAction: "none" }}
|
||||
>
|
||||
<TankCard noLink tank={props.tank} />
|
||||
</Box>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { clamp } from "lodash-es";
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
|
||||
export const DEFAULT_PITCH_TRANSITION = 15;
|
||||
export const DEFAULT_PITCH_TRANSITION = 20;
|
||||
|
||||
export function applyPitchYawLimits(
|
||||
pitch: number, // rad
|
||||
@@ -31,7 +31,7 @@ export function applyPitchYawLimits(
|
||||
let lowerPitch = -degToRad(pitchLimits.max) - depressionBuff;
|
||||
let upperPitch = -degToRad(pitchLimits.min) + elevationBuff;
|
||||
const transition = degToRad(
|
||||
pitchLimits.transition ?? DEFAULT_PITCH_TRANSITION,
|
||||
pitchLimits.transition || DEFAULT_PITCH_TRANSITION,
|
||||
);
|
||||
|
||||
if (pitchLimits.back) {
|
||||
|
||||
@@ -36,7 +36,10 @@ export function useDispose(gltf: GLTF & ObjectMap, path: string) {
|
||||
for (const map of maps) {
|
||||
if (!map) continue;
|
||||
|
||||
map.source.data.close();
|
||||
if (map.source.data instanceof ImageBitmap) {
|
||||
map.source.data.close();
|
||||
}
|
||||
|
||||
map.dispose();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user