mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12ff9a90eb | ||
|
|
5ad5c701c9 |
No files matched your search
@@ -93,7 +93,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
chunk: [default, api]
|
chunk: [default, api, models_tanks, models_legendary, models_maps]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# BlitzKit 1.31.4
|
||||||
|
|
||||||
|
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added Peekaboo DPM [Mark Greene]
|
||||||
|
- Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot
|
||||||
|
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
|
||||||
|
- Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- BlitzKit Session now properly handles tanks removed from the game [Mark Greene]
|
||||||
|
- Removed the Beta badge from the Equalizer button [Mark Greene]
|
||||||
|
- Updated the background image for the Equalizer button to the actual banner of the new game mode
|
||||||
|
- The armor no longer disappears when you turn on Dynamic Armor System [Mark Greene]
|
||||||
|
- Camouflage values in Tankopedia are now more descriptive when shooting [Mark Greene]
|
||||||
|
- The contact shadows on the ground no longer grow & darken when switching between tabs [Mark Greene]
|
||||||
|
- The "Help Translate BlitzKit" text on the home page is now properly centered vertically
|
||||||
|
- The footer now says "Branch" instead of "CDN" [Mark Greene]
|
||||||
|
- The game version in the footer now displays a shorthand version instead of the entire version number [Mark Greene]
|
||||||
|
- The reload visualizer now displays just 1 decimal place [Mark Greene]
|
||||||
|
- The search bar no longer redirects to a seemingly random tank when clicking enter when searching [Mark Greene]
|
||||||
|
- Tank search now properly handles the full names of tanks like the Super Pershing [Mark Greene]
|
||||||
|
- The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [Mark Greene]
|
||||||
|
- The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [Mark Greene]
|
||||||
|
|
||||||
|
## Technical Features
|
||||||
|
|
||||||
|
- Added documentation to the README on setting up and developing BlitzKit locally
|
||||||
|
- Added bug report and feature request forms to GitHub
|
||||||
|
- Switched to the GNU AGPLv3 license
|
||||||
|
|
||||||
|
## Technical Fixes
|
||||||
|
|
||||||
|
- BlitzKit now handles the new ID system for game-modes in WoTB
|
||||||
|
- The directory which preview versions of the game are downloaded to are now created properly
|
||||||
|
- This was the last bug standing in the way of BlitzKit Previews
|
||||||
|
- Welcome back, BlitzKit Previews!
|
||||||
|
- Removed some dead code from the BlitzKit model mangler
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
# BlitzKit 1.32.0
|
|
||||||
|
|
||||||
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### Tankopedia & Armor
|
|
||||||
|
|
||||||
- Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
|
|
||||||
- Listed track repair times in the Tankopedia [Mark Greene]
|
|
||||||
- The penetration chance is now displayed when penetration testing armor [Mark Greene]
|
|
||||||
- Added a new setting to disable automatically loading tanks in Tankopedia [Mark Greene]
|
|
||||||
|
|
||||||
### Tank Performance & Mechanics
|
|
||||||
|
|
||||||
- Added Peekaboo DPM [Mark Greene]
|
|
||||||
- Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot
|
|
||||||
|
|
||||||
### Tank Filters
|
|
||||||
|
|
||||||
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
|
|
||||||
|
|
||||||
### Game Compatibility
|
|
||||||
|
|
||||||
- Added compatibility for tier XI tanks [Mark Greene]
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
### Tankopedia & Armor
|
|
||||||
|
|
||||||
- The armor no longer disappears when you turn on Dynamic Armor System [Mark Greene]
|
|
||||||
- Camouflage values in Tankopedia are now more descriptive when shooting [Mark Greene]
|
|
||||||
- The penetration chance is now displayed when penetration testing armor [Mark Greene]
|
|
||||||
- Listed track repair times in the Tankopedia [Mark Greene]
|
|
||||||
- Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
|
|
||||||
- The flags in the Tankopedia tech tree are no longer offset to the top left [Mark Greene]
|
|
||||||
- The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [Mark Greene]
|
|
||||||
- Removed the vertical sliver of pixels found to the right of the Equalizer toggle in Tankopedia
|
|
||||||
|
|
||||||
### Tank Search & Filters
|
|
||||||
|
|
||||||
- The search bar no longer redirects to a seemingly random tank when clicking enter when searching [Mark Greene]
|
|
||||||
- Tank search now properly handles the full names of tanks like the Super Pershing [Mark Greene]
|
|
||||||
- The tank search now remains consistent in order even when an added filter does not add/remove tanks from the list [Mark Greene]
|
|
||||||
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
|
|
||||||
|
|
||||||
### Visuals & UI
|
|
||||||
|
|
||||||
- Removed the Beta badge from the Equalizer button [Mark Greene]
|
|
||||||
- Updated the background image for the Equalizer button to the actual banner of the new game mode
|
|
||||||
- The contact shadows on the ground no longer grow & darken when switching between tabs [Mark Greene]
|
|
||||||
- Additionally, the contact shadows stays visible at low angles [Mark Greene]
|
|
||||||
- The "Help Translate BlitzKit" text on the home page is now properly centered vertically
|
|
||||||
- The footer now says "Branch" instead of "CDN" [Mark Greene]
|
|
||||||
- The game version in the footer now displays a shorthand version instead of the entire version number [Mark Greene]
|
|
||||||
- The reload visualizer now displays just 1 decimal place [Mark Greene]
|
|
||||||
- Plot controls for the Static armor tab no longer pokes in from the right on non-Static armor tabs in some languages [Mark Greene]
|
|
||||||
- Mixer's controllers now scale better on smaller screens [Mark Greene]
|
|
||||||
- The list of available tanks in the Playlist is no longer excessively wide [Mark Greene]
|
|
||||||
|
|
||||||
### Tank Mechanics & Models
|
|
||||||
|
|
||||||
- The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [Mark Greene]
|
|
||||||
- When moving the tracks of tanks, the textures no longer move at different rates [Mark Greene]
|
|
||||||
|
|
||||||
### Localization
|
|
||||||
|
|
||||||
- Users with a preference on a language are once again redirected to the correct localized pages [Mark Greene]
|
|
||||||
|
|
||||||
### Session & Game Compatibility
|
|
||||||
|
|
||||||
- BlitzKit Session now properly handles tanks removed from the game [Mark Greene]
|
|
||||||
|
|
||||||
## Technical Fixes
|
|
||||||
|
|
||||||
- BlitzKit now handles the new ID system for game-modes in WoTB
|
|
||||||
- The directory which preview versions of the game are downloaded to are now created properly
|
|
||||||
- This was the last bug standing in the way of BlitzKit Previews
|
|
||||||
- Welcome back, BlitzKit Previews!
|
|
||||||
- Removed some dead code from the BlitzKit model mangler
|
|
||||||
- The player discovery loop now persists for longer than 1 iteration [Mark Greene]
|
|
||||||
- Bun is now forced to run scripts with the `--bun` flag
|
|
||||||
|
|
||||||
## Technical Features
|
|
||||||
|
|
||||||
- Added documentation to the README on setting up and developing BlitzKit locally
|
|
||||||
- Added bug report and feature request forms to GitHub
|
|
||||||
- Switched to the GNU AGPLv3 license
|
|
||||||
|
|
||||||
## Technical Fixes
|
|
||||||
|
|
||||||
- BlitzKit now handles the new ID system for game-modes in WoTB
|
|
||||||
- The directory which preview versions of the game are downloaded to are now created properly
|
|
||||||
- This was the last bug standing in the way of BlitzKit Previews
|
|
||||||
- Welcome back, BlitzKit Previews!
|
|
||||||
- Removed some dead code from the BlitzKit model mangler
|
|
||||||
- The player discovery loop now persists for longer than 1 iteration [Mark Greene]
|
|
||||||
- Bun is now forced to run scripts with the `--bun` flag
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Timezones
|
||||||
|
|
||||||
|
What is considered a "day's worth of games" is determined by the reset time. The reset time is generally at midnight everyday after which newer games are counted towards the next day. Reset times vary from region to region:
|
||||||
|
|
||||||
|
- North America: `UTC-5:00` (Central North American Time midnight)
|
||||||
|
- Europe: `UTC+1:00` (Central European Time midnight)
|
||||||
|
- Asia: `UTC+7:00` (Central Asia Standard Time midnight)
|
||||||
|
|
||||||
|
**Reset times are different from Blitz Stars** (Blitz Stars resets at 5:00 am locally relative to the user opening their website). Inconsistencies with Blitz Stars only occur with sleep-deprived players and players who play in the wrong region.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Blitzkit Rating Leaderboard
|
||||||
|
|
||||||
|
A straightforward way of storing the entire World of Tanks Blitz rating leaderboard to disk.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
primary BlitzkitRatingLeaderboard {
|
||||||
|
utf8"BKRL" magic;
|
||||||
|
Header header;
|
||||||
|
primary Body<header> body;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header {
|
||||||
|
uint16 version; // always 1
|
||||||
|
Format format;
|
||||||
|
uint32 count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Format enum<uint8> {
|
||||||
|
Base,
|
||||||
|
Superset1,
|
||||||
|
}
|
||||||
|
|
||||||
|
Body<Header header> match<header.format> {
|
||||||
|
Base {
|
||||||
|
primary {
|
||||||
|
uint32 id;
|
||||||
|
uint16 score;
|
||||||
|
}[header.count] entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
Superset1 {
|
||||||
|
primary {
|
||||||
|
uint32 id;
|
||||||
|
uint16 score;
|
||||||
|
|
||||||
|
uint32 battles;
|
||||||
|
uint32 wins;
|
||||||
|
uint32 survived;
|
||||||
|
|
||||||
|
uint32 damage;
|
||||||
|
uint32 kills;
|
||||||
|
}[header.count] entries;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Discovered Ids
|
||||||
|
|
||||||
|
```c
|
||||||
|
primary DiscoveredIds {
|
||||||
|
utf8"DIDS" magic;
|
||||||
|
Header header;
|
||||||
|
primary Body body;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header {
|
||||||
|
uint16 version; // always 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Body {
|
||||||
|
uint32 count;
|
||||||
|
uint32[count] ids;
|
||||||
|
}
|
||||||
|
```
|
||||||
+170
-1
@@ -1,3 +1,172 @@
|
|||||||
# KitScore
|
# KitScore
|
||||||
|
|
||||||
Remind me to write out the linearized model!
|
## Memo
|
||||||
|
|
||||||
|
KitScore is a performance metric that makes sense. This metric can be applied to any game, virtual or real. Specific pieces of information relating directly to World of Tanks Blitz will be provided within parentheses and with a "WoTB:" prefix.
|
||||||
|
|
||||||
|
## Consumption of Observations
|
||||||
|
|
||||||
|
KitScore is driven solely by the behavior of other players and finding meaning of a new observation within a pool of condensed data based on previously observed data. Generally, we will be provided with a number of statistics (the $x_i$ of an observation) per player (WoTB: $x_\text{frags}$, $x_\text{xp}$, $x_\text{spots}$, etc.) including a target statistic (the $x_j$ of an observation) that all other statistics try to maximize (WoTB: "winrate" which we will defined later as $x_j$).
|
||||||
|
|
||||||
|
If the statistics are cumulative, they will have to be normalized (WoTB: statistics fetched from Wargaming are indeed cumulative even for a single player; $x_\text{frags}=\frac{\Sigma x_\text{frags}}{\Sigma x_\text{battles}}$, $x_\text{spots}=\frac{\Sigma x_\text{spots}}{\Sigma x_\text{battles}}$, $x_j=\frac{\Sigma x_\text{wins}}{\Sigma x_\text{battles}}$, etc.). Each player will have such average statistics, giving us many points of data to work with. If the statistics are not cumulative, then you will be dealing with multiple observations per player which will only help improve accuracy.
|
||||||
|
|
||||||
|
## It's Impossible to "Find What Makes the Metric Tick"
|
||||||
|
|
||||||
|
WN8, a widely adopted metric in the World of Tanks and World of Tanks Blitz communities is plagued with problems. One of them being that people often find and exploit "what makes the metric tick." In other words, people easily exploit the rudimentary nature of WN8 to score undeservedly high.
|
||||||
|
|
||||||
|
In WN8, the gains of some rewarding categories overshadow the losses of others though that may not necessarily be in the best interest in maximizing winrate. KitScore makes this nearly impossible as it punishes a player for scoring well solely high in a certain category. You will never exploit KitScore. Period.
|
||||||
|
|
||||||
|
## Generating Reasonable Values
|
||||||
|
|
||||||
|
TODO: there's like no runaway cases
|
||||||
|
|
||||||
|
## Symbol Interpretation
|
||||||
|
|
||||||
|
| Symbol | Interpretation |
|
||||||
|
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| $A_\text{human}$ | The human readable version of the raw score $A$. |
|
||||||
|
| $A$ | The raw KitScore. |
|
||||||
|
| $C$ | An arbitrary coefficient. |
|
||||||
|
| $I$ | The number of statistics including the target statistic. This means that there are $I-1$ entries for $x_i$ and $1$ entry for $y$ for each observation. |
|
||||||
|
| $i$ | The statistic index of where there are $I$ entries. |
|
||||||
|
| $j$ | A discriminator for the target statistic. There may never be more than $1$ entries for $j$. |
|
||||||
|
| $m$ | The degree of the polynomial regression. |
|
||||||
|
| $n$ | The number of observations. |
|
||||||
|
| $a$ | An "atomic" score which is a function of the $i$th statistic. |
|
||||||
|
| $w$ | The weight of the $i$th atomic statistic. |
|
||||||
|
|
||||||
|
## Computing the Score
|
||||||
|
|
||||||
|
The final metric $A_\text{human}$ is an arbitrarily scaled value of the raw score $A$ where $C$ is any coefficient of your liking. BlitzKit uses $C=10^3$.
|
||||||
|
|
||||||
|
$$
|
||||||
|
A_\text{human}=CA
|
||||||
|
$$
|
||||||
|
|
||||||
|
The final metric $A$ is the weighted average of individual scores $a$ which are functions of the $i$th statistic excluding the target statistic $j$ itself.
|
||||||
|
|
||||||
|
$$
|
||||||
|
A=\frac{\sum_{i \neq j} w_{i}a_{i}}{\sum_{i \neq j} w_{i}}
|
||||||
|
$$
|
||||||
|
|
||||||
|
The "atomic" score $a$ is the difference between the predicted target statistic $P_{i \leftrightarrow j}(x_i)$ acquired using a polynomial regression of degree $m$ and the actual newly observed $x_j$.
|
||||||
|
|
||||||
|
$$
|
||||||
|
a_i=x_j-P_{i \leftrightarrow j}(x_i)
|
||||||
|
$$
|
||||||
|
|
||||||
|
The polynomial regression of $m$th degree can be written as follows:
|
||||||
|
|
||||||
|
$$
|
||||||
|
P(x)=\beta_0x^0+\beta_1x^1+\beta_2x^2+\dots+\beta_mx^m
|
||||||
|
$$
|
||||||
|
|
||||||
|
Polynomial regression coefficients $\beta$ can be acquired through basic linear algebra where an $m \times m$ of constants is multiplied with $m \times 1$ of unknowns which equals another $m \times 1$ of constants.
|
||||||
|
|
||||||
|
```math
|
||||||
|
\begin{bmatrix}
|
||||||
|
\sum_{k=1}^n x_i^0 & \sum_{k=1}^n x_i^1 & \sum_{k=1}^n x_i^2 & \dots & \sum_{k=1}^n x_i^m \\
|
||||||
|
\sum_{k=1}^n x_i^1 & \sum_{k=1}^n x_i^2 & \sum_{k=1}^n x_i^3 & \dots & \sum_{k=1}^n x_i^{m+1} \\
|
||||||
|
\sum_{k=1}^n x_i^2 & \sum_{k=1}^n x_i^3 & \sum_{k=1}^n x_i^4 & \dots & \sum_{k=1}^n x_i^{m+2} \\
|
||||||
|
\vdots & \vdots & \vdots & \ddots & \vdots \\
|
||||||
|
\sum_{k=1}^n x_i^m & \sum_{k=1}^n x_i^{m+1} & \sum_{k=1}^n x_i^{m+1} & \dots & \sum_{k=1}^n x_i^{2m} \\
|
||||||
|
\end{bmatrix}
|
||||||
|
|
||||||
|
\begin{bmatrix}
|
||||||
|
\beta_0 \\
|
||||||
|
\beta_1 \\
|
||||||
|
\beta_2 \\
|
||||||
|
\vdots \\
|
||||||
|
\beta_m
|
||||||
|
\end{bmatrix}
|
||||||
|
|
||||||
|
=
|
||||||
|
|
||||||
|
\begin{bmatrix}
|
||||||
|
\sum_{k=1}^n x_jx_i^0 \\
|
||||||
|
\sum_{k=1}^n x_jx_i^1 \\
|
||||||
|
\sum_{k=1}^n x_jx_i^2 \\
|
||||||
|
\vdots \\
|
||||||
|
\sum_{k=1}^n x_jx_i^m
|
||||||
|
\end{bmatrix}
|
||||||
|
```
|
||||||
|
|
||||||
|
The weight $w$ is a slice of the Pearson corelation matrix. The numerator is the corelation between the $i$th statistic and the target statistic $j$ while the denominator the sum of the corelations between the $i$th statistic and all statistics but the target statistic $j$.
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{i}=\frac{r_{i\leftrightarrow j}^2}{\sum_{i_{k}\neq j}r_{i\leftrightarrow i_{k}}^2}
|
||||||
|
$$
|
||||||
|
|
||||||
|
## Understanding the Corelation Matrix
|
||||||
|
|
||||||
|
Forming the corelation matrix will create an $I \times I$ matrix where. This matrix is best represented as a table. The example below illustrates a matrix/table for $I=4$ which includes $3$ entries for $i$ statistics and $1$ entry for the $j$ target statistic.
|
||||||
|
|
||||||
|
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
|
||||||
|
| ----- | --------------------------- | --------------------------- | --------------------------- | --------------------------- |
|
||||||
|
| $x_1$ | $1$ | $r_{1 \leftrightarrow 2}^2$ | $r_{1 \leftrightarrow 3}^2$ | $r_{1 \leftrightarrow j}^2$ |
|
||||||
|
| $x_2$ | $r_{1 \leftrightarrow 2}^2$ | $1$ | $r_{2 \leftrightarrow 3}^2$ | $r_{2 \leftrightarrow j}^2$ |
|
||||||
|
| $x_3$ | $r_{1 \leftrightarrow 3}^2$ | $r_{2 \leftrightarrow 3}^2$ | $1$ | $r_{3 \leftrightarrow j}^2$ |
|
||||||
|
| $x_j$ | $r_{1 \leftrightarrow j}^2$ | $r_{2 \leftrightarrow j}^2$ | $r_{3 \leftrightarrow j}^2$ | $1$ |
|
||||||
|
|
||||||
|
It should not be a surprise to find $r_{i \leftrightarrow i}^2=1$ which forms a neat diagonal across the table. Note that there are many duplicates within the $I^2$ entries. The "bottom-right triangle" can be eliminated to remove redundancies leaving us with only $\frac{I(I+1)}{2}$ fields occupied.
|
||||||
|
|
||||||
|
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
|
||||||
|
| ----- | ----- | --------------------------- | --------------------------- | --------------------------- |
|
||||||
|
| $x_1$ | $1$ | $r_{1 \leftrightarrow 2}^2$ | $r_{1 \leftrightarrow 3}^2$ | $r_{1 \leftrightarrow j}^2$ |
|
||||||
|
| $x_2$ | | $1$ | $r_{2 \leftrightarrow 3}^2$ | $r_{2 \leftrightarrow j}^2$ |
|
||||||
|
| $x_3$ | | | $1$ | $r_{3 \leftrightarrow j}^2$ |
|
||||||
|
| $x_j$ | | | | $1$ |
|
||||||
|
|
||||||
|
When programming this matrix into an array, $r_{i \leftrightarrow i}^2=1$ will be redundant. I recommend condensing the matrix into an array of size $\frac{I(I+1)}{2}+I=\frac{I(I-1)}{2}$ to avoid all redundant entries. For $I=4$, the array will look something like the one illustrated below (here, $j=4$).
|
||||||
|
|
||||||
|
$$
|
||||||
|
[r_{1 \leftrightarrow 2}^2, r_{1 \leftrightarrow 3}^2, r_{1 \leftrightarrow 4}^2, r_{2 \leftrightarrow 3}^2, r_{2 \leftrightarrow 4}^2, r_{3 \leftrightarrow 4}^2]
|
||||||
|
$$
|
||||||
|
|
||||||
|
## Understanding the Weights
|
||||||
|
|
||||||
|
Some may find the weight equation arbitrary; it evidently isn't. The goal of $w$ is to eliminate over-rewarding the same behavior represented in multiple statistics. It makes all atomic statistics concurrent by leveraging the Pearson corelation matrix.
|
||||||
|
|
||||||
|
A simplified example will clarify the idea. First, we compute a Pearson corelation matrix for $I=3$ statistics. We have $x_1$ and $x_2$, the statistics that are trying to maximize $x_j$. Reminder: a Pearson corelation matrix finds the $r$ value not only between $x_i$ and $x_j$ but also between all combinations of $x_i$ and themselves. Here, $r^2$ is calculated using polynomial regression corelation analysis to the $m$th degree.
|
||||||
|
|
||||||
|
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
|
||||||
|
| ----- | ----- | ----------- | ----- | ----- |
|
||||||
|
| $x_1$ | $1$ | $\boxed{0}$ | $0$ | $1$ |
|
||||||
|
| $x_2$ | | $1$ | $0$ | $1$ |
|
||||||
|
| $x_3$ | | | $1$ | $1$ |
|
||||||
|
| $x_j$ | | | | $1$ |
|
||||||
|
|
||||||
|
In the case above, $r^2_{1 \leftrightarrow 2}=0$ meaning that both statistics have absolutely nothing to do with each other. Hence, the player will be awarded for optimizing both individually. And since $r^2_{1 \leftrightarrow j}=r^2_{2 \leftrightarrow j}$, they should both be rewarded equally; in other words, they should have equal weights.
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{1}=\frac{1}{1 + 0 + 0}=1
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{2}=\frac{1}{0 + 1 + 0}=1
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{3}=\frac{1}{0 + 0 + 1}=1
|
||||||
|
$$
|
||||||
|
|
||||||
|
Now consider a case where $x_1$ and $x_2$ are fully correlated. It would be a bad idea to reward them equally; hence, their weights suffer.
|
||||||
|
|
||||||
|
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
|
||||||
|
| ----- | ----- | ----------- | ----- | ----- |
|
||||||
|
| $x_1$ | $1$ | $\boxed{1}$ | $0$ | $1$ |
|
||||||
|
| $x_2$ | | $1$ | $0$ | $1$ |
|
||||||
|
| $x_3$ | | | $1$ | $1$ |
|
||||||
|
| $x_j$ | | | | $1$ |
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{1}=\frac{1}{1 + 1 + 0}=0.5
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{2}=\frac{1}{1 + 1 + 0}=0.5
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
w_{3}=\frac{1}{0 + 0 + 1}=1
|
||||||
|
$$
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# BlitzKit Regular Tank Stats
|
||||||
|
|
||||||
|
A straightforward way of storing the entire World of Tanks Blitz tank statistics for players to disk.
|
||||||
|
|
||||||
|
## Manifest `manifest.rtsm`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
primary RegularTankStatisticsManifest {
|
||||||
|
utf8"RTSM" magic;
|
||||||
|
Header header;
|
||||||
|
primary Body body;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header {
|
||||||
|
uint16 version; // always 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Body {
|
||||||
|
uint32 time;
|
||||||
|
uint8 base;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chunk `*.rtsc`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
primary RegularTankStatisticsChunk {
|
||||||
|
utf8"RTSC" magic;
|
||||||
|
Header header;
|
||||||
|
primary Body body<header.playerCount>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header {
|
||||||
|
uint16 version; // always 1
|
||||||
|
uint32 playerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Body<uint32 playerCount> {
|
||||||
|
Player[playerCount] players;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player {
|
||||||
|
uint32 id;
|
||||||
|
uint16 tankCount;
|
||||||
|
Tank[tankCount] tanks;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tank {
|
||||||
|
uint32 id;
|
||||||
|
uint32 battleLifeTime;
|
||||||
|
uint32 lastBattleTime;
|
||||||
|
uint8 markOfMastery;
|
||||||
|
uint32 battles;
|
||||||
|
uint32 capturePoints;
|
||||||
|
uint32 damageDealt;
|
||||||
|
uint32 damageReceived;
|
||||||
|
uint32 droppedCapturePoints;
|
||||||
|
uint32 frags;
|
||||||
|
uint32 frags8p;
|
||||||
|
uint32 hits;
|
||||||
|
uint32 losses;
|
||||||
|
uint8 maxFrags;
|
||||||
|
uint16 maxXp;
|
||||||
|
uint32 shots;
|
||||||
|
uint32 spotted;
|
||||||
|
uint32 survivedBattles;
|
||||||
|
uint32 winAndSurvived;
|
||||||
|
uint32 wins;
|
||||||
|
uint32 xp;
|
||||||
|
}
|
||||||
|
```
|
||||||
+22
-3
@@ -4,14 +4,33 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"api": null
|
|
||||||
|
"api": null,
|
||||||
|
|
||||||
|
"models_tanks": null,
|
||||||
|
"models_legendary": null,
|
||||||
|
"models_maps": null
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"default": "blitzkit/deploy-node-beta.blitzkit.app",
|
"default": "blitzkit/deploy-node-beta.blitzkit.app",
|
||||||
"api": "blitzkit/deploy-node-api.beta.blitzkit.app"
|
|
||||||
|
"api": "blitzkit/deploy-node-api.beta.blitzkit.app",
|
||||||
|
|
||||||
|
"models_tanks": "blitzkit/deploy-node-tanks.models.beta.blitzkit.app",
|
||||||
|
"models_legendary": "blitzkit/deploy-node-legendary.models.beta.blitzkit.app",
|
||||||
|
"models_maps": "blitzkit/deploy-node-maps.models.beta.blitzkit.app"
|
||||||
},
|
},
|
||||||
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
|
"default": "/",
|
||||||
|
|
||||||
"api": "https://api.beta.blitzkit.app",
|
"api": "https://api.beta.blitzkit.app",
|
||||||
"static": "https://static.beta.blitzkit.app"
|
|
||||||
|
"models_tanks": "https://tanks.models.beta.blitzkit.app",
|
||||||
|
"models_legendary": "https://legendary.models.beta.blitzkit.app",
|
||||||
|
"models_maps": "https://maps.models.beta.blitzkit.app",
|
||||||
|
|
||||||
|
"static": "https://static.blitzkit.app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,25 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"api": "api"
|
|
||||||
|
"api": "api",
|
||||||
|
|
||||||
|
"models_tanks": "models/tanks",
|
||||||
|
"models_legendary": "models/legendary",
|
||||||
|
"models_maps": "models/maps"
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodes": {},
|
"nodes": {},
|
||||||
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
|
"default": "/",
|
||||||
|
|
||||||
"api": "/api",
|
"api": "/api",
|
||||||
|
|
||||||
|
"models_tanks": "/models/tanks",
|
||||||
|
"models_legendary": "/models/legendary",
|
||||||
|
"models_maps": "/models/maps",
|
||||||
|
|
||||||
"static": "https://static.blitzkit.app"
|
"static": "https://static.blitzkit.app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,14 +4,33 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"api": null
|
|
||||||
|
"api": null,
|
||||||
|
|
||||||
|
"models_tanks": null,
|
||||||
|
"models_legendary": null,
|
||||||
|
"models_maps": null
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"default": "blitzkit/deploy-node-opentest.blitzkit.app",
|
"default": "blitzkit/deploy-node-opentest.blitzkit.app",
|
||||||
"api": "blitzkit/deploy-node-api.opentest.blitzkit.app"
|
|
||||||
|
"api": "blitzkit/deploy-node-api.opentest.blitzkit.app",
|
||||||
|
|
||||||
|
"models_tanks": "blitzkit/deploy-node-tanks.models.opentest.blitzkit.app",
|
||||||
|
"models_legendary": "blitzkit/deploy-node-legendary.models.opentest.blitzkit.app",
|
||||||
|
"models_maps": "blitzkit/deploy-node-maps.models.opentest.blitzkit.app"
|
||||||
},
|
},
|
||||||
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
|
"default": "/",
|
||||||
|
|
||||||
"api": "https://api.opentest.blitzkit.app",
|
"api": "https://api.opentest.blitzkit.app",
|
||||||
"static": "https://static.opentest.blitzkit.app"
|
|
||||||
|
"models_tanks": "https://tanks.models.opentest.blitzkit.app",
|
||||||
|
"models_legendary": "https://legendary.models.opentest.blitzkit.app",
|
||||||
|
"models_maps": "https://maps.models.opentest.blitzkit.app",
|
||||||
|
|
||||||
|
"static": "https://static.blitzkit.app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,14 +4,33 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"api": null
|
|
||||||
|
"api": null,
|
||||||
|
|
||||||
|
"models_tanks": null,
|
||||||
|
"models_legendary": null,
|
||||||
|
"models_maps": null
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"default": "blitzkit/deploy-node-preview.blitzkit.app",
|
"default": "blitzkit/deploy-node-preview.blitzkit.app",
|
||||||
"api": "blitzkit/deploy-node-api.preview.blitzkit.app"
|
|
||||||
|
"api": "blitzkit/deploy-node-api.preview.blitzkit.app",
|
||||||
|
|
||||||
|
"models_tanks": "blitzkit/deploy-node-tanks.models.preview.blitzkit.app",
|
||||||
|
"models_legendary": "blitzkit/deploy-node-legendary.models.preview.blitzkit.app",
|
||||||
|
"models_maps": "blitzkit/deploy-node-maps.models.preview.blitzkit.app"
|
||||||
},
|
},
|
||||||
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
|
"default": "/",
|
||||||
|
|
||||||
"api": "https://api.preview.blitzkit.app",
|
"api": "https://api.preview.blitzkit.app",
|
||||||
"static": "https://static.preview.blitzkit.app"
|
|
||||||
|
"models_tanks": "https://tanks.models.preview.blitzkit.app",
|
||||||
|
"models_legendary": "https://legendary.models.preview.blitzkit.app",
|
||||||
|
"models_maps": "https://maps.models.preview.blitzkit.app",
|
||||||
|
|
||||||
|
"static": "https://static.blitzkit.app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,14 +4,33 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"api": null
|
|
||||||
|
"api": null,
|
||||||
|
|
||||||
|
"models_tanks": null,
|
||||||
|
"models_legendary": null,
|
||||||
|
"models_maps": null
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"default": "blitzkit/deploy-node-blitzkit.app",
|
"default": "blitzkit/deploy-node-blitzkit.app",
|
||||||
"api": "blitzkit/deploy-node-api.blitzkit.app"
|
|
||||||
|
"api": "blitzkit/deploy-node-api.blitzkit.app",
|
||||||
|
|
||||||
|
"models_tanks": "blitzkit/deploy-node-tanks.models.blitzkit.app",
|
||||||
|
"models_legendary": "blitzkit/deploy-node-legendary.models.blitzkit.app",
|
||||||
|
"models_maps": "blitzkit/deploy-node-maps.models.blitzkit.app"
|
||||||
},
|
},
|
||||||
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
|
"default": "/",
|
||||||
|
|
||||||
"api": "https://api.blitzkit.app",
|
"api": "https://api.blitzkit.app",
|
||||||
|
|
||||||
|
"models_tanks": "https://tanks.models.blitzkit.app",
|
||||||
|
"models_legendary": "https://legendary.models.blitzkit.app",
|
||||||
|
"models_maps": "https://maps.models.blitzkit.app",
|
||||||
|
|
||||||
"static": "https://static.blitzkit.app"
|
"static": "https://static.blitzkit.app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkit",
|
"name": "blitzkit",
|
||||||
"version": "1.32.0",
|
"version": "1.31.4",
|
||||||
"license": "BlitzKit License",
|
"license": "BlitzKit License",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -434,7 +434,6 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
|
|||||||
resistance_hard: terrainResistances[0],
|
resistance_hard: terrainResistances[0],
|
||||||
resistance_medium: terrainResistances[1],
|
resistance_medium: terrainResistances[1],
|
||||||
resistance_soft: terrainResistances[2],
|
resistance_soft: terrainResistances[2],
|
||||||
repair_time: track.hysteresisHealth / track.healthRegenPerSec,
|
|
||||||
tier: track.level,
|
tier: track.level,
|
||||||
unlocks: resolveUnlocks(
|
unlocks: resolveUnlocks(
|
||||||
this.blitzModuleTypeToBlitzkit,
|
this.blitzModuleTypeToBlitzkit,
|
||||||
|
|||||||
@@ -4,13 +4,7 @@ import {
|
|||||||
ChassisDefinitionsList,
|
ChassisDefinitionsList,
|
||||||
Crew,
|
Crew,
|
||||||
GunDefinitionsList,
|
GunDefinitionsList,
|
||||||
I_HAT,
|
|
||||||
J_HAT,
|
|
||||||
K_HAT,
|
|
||||||
ModelDefinitions,
|
ModelDefinitions,
|
||||||
parseHierarchies,
|
|
||||||
Sc2ReadStream,
|
|
||||||
ScgReadStream,
|
|
||||||
TankParameters,
|
TankParameters,
|
||||||
toUniqueId,
|
toUniqueId,
|
||||||
TurretDefinitionsList,
|
TurretDefinitionsList,
|
||||||
@@ -18,10 +12,9 @@ import {
|
|||||||
VehicleDefinitionArmor,
|
VehicleDefinitionArmor,
|
||||||
VehicleDefinitionList,
|
VehicleDefinitionList,
|
||||||
VehicleDefinitions,
|
VehicleDefinitions,
|
||||||
VertexAttribute,
|
|
||||||
} from "@blitzkit/core";
|
} from "@blitzkit/core";
|
||||||
import { parse as parsePath } from "path";
|
import { parse as parsePath } from "path";
|
||||||
import { MathUtils, Vector3 as THREEVector3, Vector3Tuple } from "three";
|
import { Vector3Tuple } from "three";
|
||||||
import { Cache } from "./0_base";
|
import { Cache } from "./0_base";
|
||||||
import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions";
|
import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions";
|
||||||
|
|
||||||
@@ -30,87 +23,6 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
|
|||||||
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async collisionBoundingBoxes(
|
|
||||||
nation: string,
|
|
||||||
tankKey: string,
|
|
||||||
turretRotation?: { yaw: number; pitch: number; roll: number },
|
|
||||||
) {
|
|
||||||
const boundingBoxes: Record<
|
|
||||||
string,
|
|
||||||
{ min: Vector3Tuple; max: Vector3Tuple }
|
|
||||||
> = {};
|
|
||||||
const path = `Data/3d/Tanks/CollisionMeshes/${nation}-${tankKey}`;
|
|
||||||
|
|
||||||
if (!(await this.vfs.resolve(`${path}.sc2`))) {
|
|
||||||
throw new Error(`Missing collision mesh for ${nation}-${tankKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sc2 = new Sc2ReadStream(
|
|
||||||
(await this.vfs.file(`${path}.sc2`)).buffer as ArrayBuffer,
|
|
||||||
).sc2();
|
|
||||||
const scg = new ScgReadStream(
|
|
||||||
(await this.vfs.file(`${path}.scg`)).buffer as ArrayBuffer,
|
|
||||||
).scg();
|
|
||||||
|
|
||||||
parseHierarchies(sc2["#hierarchy"], null, (hierarchy, components) => {
|
|
||||||
for (const component of components) {
|
|
||||||
if (component["comp.typename"] !== "RenderComponent") continue;
|
|
||||||
|
|
||||||
const polygonGroup = scg.get(
|
|
||||||
component["rc.renderObj"]["ro.batches"]["0000"]["rb.datasource"],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!polygonGroup) continue;
|
|
||||||
|
|
||||||
const min: Vector3Tuple = [Infinity, Infinity, Infinity];
|
|
||||||
const max: Vector3Tuple = [-Infinity, -Infinity, -Infinity];
|
|
||||||
const rotate =
|
|
||||||
turretRotation && hierarchy.name.toLowerCase().startsWith("turret");
|
|
||||||
|
|
||||||
polygonGroup.vertices.forEach((vertex) => {
|
|
||||||
vertex.forEach(({ attribute, value }) => {
|
|
||||||
if (attribute !== VertexAttribute.VERTEX) return;
|
|
||||||
|
|
||||||
const position = new THREEVector3(...value);
|
|
||||||
|
|
||||||
if (rotate) {
|
|
||||||
position
|
|
||||||
.applyAxisAngle(
|
|
||||||
I_HAT,
|
|
||||||
-MathUtils.degToRad(turretRotation.pitch),
|
|
||||||
)
|
|
||||||
.applyAxisAngle(
|
|
||||||
J_HAT,
|
|
||||||
-MathUtils.degToRad(turretRotation.roll),
|
|
||||||
)
|
|
||||||
.applyAxisAngle(
|
|
||||||
K_HAT,
|
|
||||||
-MathUtils.degToRad(turretRotation.yaw),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const coordinates = position.toArray();
|
|
||||||
|
|
||||||
for (let axis = 0; axis < 3; axis++) {
|
|
||||||
min[axis] = Math.min(min[axis], coordinates[axis]);
|
|
||||||
max[axis] = Math.max(max[axis], coordinates[axis]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
boundingBoxes[hierarchy.name.toLowerCase()] = { min, max };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!boundingBoxes.hull) {
|
|
||||||
throw new Error(`Missing hull collision mesh for ${nation}-${tankKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return boundingBoxes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private assignArmor(
|
private assignArmor(
|
||||||
raw: VehicleDefinitionArmor[string],
|
raw: VehicleDefinitionArmor[string],
|
||||||
id: number,
|
id: number,
|
||||||
@@ -155,12 +67,6 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
|
|||||||
const tankParameters = await this.vfs.yaml<TankParameters>(
|
const tankParameters = await this.vfs.yaml<TankParameters>(
|
||||||
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
||||||
);
|
);
|
||||||
const collisionBoundingBoxes = await this.collisionBoundingBoxes(
|
|
||||||
nation,
|
|
||||||
tankKey,
|
|
||||||
tankDefinition.root.hull.turretInitialRotation,
|
|
||||||
);
|
|
||||||
const hullBoundingBox = collisionBoundingBoxes.hull;
|
|
||||||
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
|
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map(Number) as Vector3Tuple;
|
.map(Number) as Vector3Tuple;
|
||||||
@@ -235,8 +141,12 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
bounding_box: {
|
bounding_box: {
|
||||||
min: this.vector3TupleToBlitzkit(hullBoundingBox.min),
|
min: this.vector3TupleToBlitzkit(
|
||||||
max: this.vector3TupleToBlitzkit(hullBoundingBox.max),
|
tankParameters.collision.hull.bbox.min,
|
||||||
|
),
|
||||||
|
max: this.vector3TupleToBlitzkit(
|
||||||
|
tankParameters.collision.hull.bbox.max,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
turrets: {},
|
turrets: {},
|
||||||
tracks: {},
|
tracks: {},
|
||||||
@@ -298,35 +208,26 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
|
|||||||
|
|
||||||
turret.userString;
|
turret.userString;
|
||||||
|
|
||||||
const turretCollisionName = parsePath(
|
|
||||||
turret.hitTester.collisionModel,
|
|
||||||
).name.toLowerCase();
|
|
||||||
const isFullYaw = -turretYaw[0] + turretYaw[1] === 360;
|
|
||||||
|
|
||||||
if (!collisionBoundingBoxes[turretCollisionName] && isFullYaw) {
|
|
||||||
throw new Error(
|
|
||||||
`Missing turret collision mesh for ${nation}-${tankKey} ${turretKey}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const turretBoundingBox = collisionBoundingBoxes[
|
|
||||||
turretCollisionName
|
|
||||||
] ?? {
|
|
||||||
min: [0, 0, 0] as Vector3Tuple,
|
|
||||||
max: [0, 0, 0] as Vector3Tuple,
|
|
||||||
};
|
|
||||||
|
|
||||||
modelDefinitions.models[tankId].turrets[turretId] = {
|
modelDefinitions.models[tankId].turrets[turretId] = {
|
||||||
armor: turretArmor,
|
armor: turretArmor,
|
||||||
gun_origin: this.vector3TupleToBlitzkit(gunOrigin),
|
gun_origin: this.vector3TupleToBlitzkit(gunOrigin),
|
||||||
model_id: turretModel,
|
model_id: turretModel,
|
||||||
yaw: isFullYaw
|
yaw:
|
||||||
? undefined
|
-turretYaw[0] + turretYaw[1] === 360
|
||||||
: { min: turretYaw[0], max: turretYaw[1] },
|
? undefined
|
||||||
|
: { min: turretYaw[0], max: turretYaw[1] },
|
||||||
guns: {},
|
guns: {},
|
||||||
bounding_box: {
|
bounding_box: {
|
||||||
min: this.vector3TupleToBlitzkit(turretBoundingBox.min),
|
min: this.vector3TupleToBlitzkit(
|
||||||
max: this.vector3TupleToBlitzkit(turretBoundingBox.max),
|
tankParameters.collision[
|
||||||
|
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
|
||||||
|
].bbox.min,
|
||||||
|
),
|
||||||
|
max: this.vector3TupleToBlitzkit(
|
||||||
|
tankParameters.collision[
|
||||||
|
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
|
||||||
|
].bbox.max,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export * from "./sluggify";
|
|||||||
export * from "./staticAsset";
|
export * from "./staticAsset";
|
||||||
export * from "./sum";
|
export * from "./sum";
|
||||||
export * from "./tankDefinitions";
|
export * from "./tankDefinitions";
|
||||||
export * from "./tankBoundingBox";
|
|
||||||
export * from "./tankIcon";
|
export * from "./tankIcon";
|
||||||
export * from "./tankNames";
|
export * from "./tankNames";
|
||||||
export * from "./unionBoundingBox";
|
export * from "./unionBoundingBox";
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { ModelDefinition, TurretModelDefinition } from "../protos";
|
|
||||||
import { unionBoundingBox } from "./unionBoundingBox";
|
|
||||||
|
|
||||||
export function tankBoundingBox(
|
|
||||||
tankModelDefinition: ModelDefinition,
|
|
||||||
turretModelDefinition: TurretModelDefinition,
|
|
||||||
) {
|
|
||||||
const hull = tankModelDefinition.bounding_box!;
|
|
||||||
const turret = turretModelDefinition.bounding_box!;
|
|
||||||
const origin = tankModelDefinition.turret_origin!;
|
|
||||||
|
|
||||||
if (
|
|
||||||
turret.min!.x === turret.max!.x &&
|
|
||||||
turret.min!.y === turret.max!.y &&
|
|
||||||
turret.min!.z === turret.max!.z
|
|
||||||
) {
|
|
||||||
return unionBoundingBox(hull, hull);
|
|
||||||
}
|
|
||||||
|
|
||||||
return unionBoundingBox(hull, {
|
|
||||||
min: {
|
|
||||||
x: turret.min!.x + origin.x,
|
|
||||||
y: turret.min!.y + origin.z,
|
|
||||||
z: turret.min!.z + origin.y,
|
|
||||||
},
|
|
||||||
max: {
|
|
||||||
x: turret.max!.x + origin.x,
|
|
||||||
y: turret.max!.y + origin.z,
|
|
||||||
z: turret.max!.z + origin.y,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { times } from "lodash-es";
|
import { times } from "lodash-es";
|
||||||
import { CrewType, ShellType } from "../../protos";
|
import { CrewType, ShellType, TankClass, TankType } from "../../protos";
|
||||||
|
|
||||||
export const SHELL_NAMES: Record<ShellType, string> = {
|
export const SHELL_NAMES: Record<ShellType, string> = {
|
||||||
[ShellType.SHELL_TYPE_AP]: "AP",
|
[ShellType.SHELL_TYPE_AP]: "AP",
|
||||||
@@ -15,6 +15,33 @@ export const CREW_MEMBER_NAMES = {
|
|||||||
[CrewType.CREW_TYPE_RADIOMAN]: "radioman",
|
[CrewType.CREW_TYPE_RADIOMAN]: "radioman",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use svg
|
||||||
|
*/
|
||||||
|
export const TANK_ICONS: Record<TankClass, string> = {
|
||||||
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/BIHSEH0.png",
|
||||||
|
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/CSNha5V.png",
|
||||||
|
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/wvf3ltm.png",
|
||||||
|
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/ECeqlZa.png",
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @deprecated use svg
|
||||||
|
*/
|
||||||
|
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
|
||||||
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/TCu3EdR.png",
|
||||||
|
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/zdkpTRb.png",
|
||||||
|
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/3z7eHX6.png",
|
||||||
|
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/P3vbmyA.png",
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @deprecated use svg
|
||||||
|
*/
|
||||||
|
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
|
||||||
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/WTjeirB.png",
|
||||||
|
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/EwhtKkU.png",
|
||||||
|
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/u8YDMBh.png",
|
||||||
|
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png",
|
||||||
|
};
|
||||||
export const TIERS = times(10, (index) => index + 1);
|
export const TIERS = times(10, (index) => index + 1);
|
||||||
export const TIER_ROMAN_NUMERALS: Record<number, string> = {
|
export const TIER_ROMAN_NUMERALS: Record<number, string> = {
|
||||||
1: "I",
|
1: "I",
|
||||||
@@ -41,3 +68,8 @@ export const flags: Record<string, string> = {
|
|||||||
european: "🇪🇺",
|
european: "🇪🇺",
|
||||||
other: "<:other:1218421572243558482>",
|
other: "<:other:1218421572243558482>",
|
||||||
};
|
};
|
||||||
|
export const TREE_TYPE_ICONS: Record<TankType, Record<TankClass, string>> = {
|
||||||
|
[TankType.TANK_TYPE_RESEARCHABLE]: TANK_ICONS,
|
||||||
|
[TankType.TANK_TYPE_PREMIUM]: TANK_ICONS_PREMIUM,
|
||||||
|
[TankType.TANK_TYPE_COLLECTOR]: TANK_ICONS_COLLECTOR,
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
export enum ImgurSize {
|
||||||
|
/**
|
||||||
|
* The default size
|
||||||
|
*/
|
||||||
|
Default = '',
|
||||||
|
/**
|
||||||
|
* Unknown size but really really small
|
||||||
|
*/
|
||||||
|
UltraSmall = 'ss',
|
||||||
|
/**
|
||||||
|
* 90x90
|
||||||
|
*/
|
||||||
|
SmallSquare = 's',
|
||||||
|
/**
|
||||||
|
* 160x160
|
||||||
|
*/
|
||||||
|
BigSquare = 'b',
|
||||||
|
/**
|
||||||
|
* 160x160
|
||||||
|
*/
|
||||||
|
SmallThumbnail = 't',
|
||||||
|
/**
|
||||||
|
* 320x320
|
||||||
|
*/
|
||||||
|
MediumThumbnail = 'm',
|
||||||
|
/**
|
||||||
|
* 640x640
|
||||||
|
*/
|
||||||
|
LargeThumbnail = 'l',
|
||||||
|
/**
|
||||||
|
* 1024x1024
|
||||||
|
*/
|
||||||
|
HugeThumbnail = 'h',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImgurOptions {
|
||||||
|
format: 'jpeg' | 'png';
|
||||||
|
size: ImgurSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultImgurOptions: ImgurOptions = {
|
||||||
|
format: 'png',
|
||||||
|
size: ImgurSize.Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function imgur(id: string, imgurOptions?: Partial<ImgurOptions>) {
|
||||||
|
const options = { ...defaultImgurOptions, ...imgurOptions };
|
||||||
|
|
||||||
|
return `https://i.imgur.com/${id}${options.size}.${options.format}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './imgur';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from "./blitz";
|
export * from "./blitz";
|
||||||
export * from "./blitzkit";
|
export * from "./blitzkit";
|
||||||
|
export * from "./imgur";
|
||||||
export * from "./math";
|
export * from "./math";
|
||||||
export * from "./patreon";
|
export * from "./patreon";
|
||||||
export * from "./protobuf";
|
export * from "./protobuf";
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ message TrackDefinition {
|
|||||||
required float resistance_medium = 10;
|
required float resistance_medium = 10;
|
||||||
required float resistance_soft = 11;
|
required float resistance_soft = 11;
|
||||||
repeated Unlock unlocks = 12;
|
repeated Unlock unlocks = 12;
|
||||||
required float repair_time = 13;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message EngineDefinition {
|
message EngineDefinition {
|
||||||
|
|||||||
@@ -338,7 +338,6 @@ export interface TrackDefinition {
|
|||||||
resistance_medium: number;
|
resistance_medium: number;
|
||||||
resistance_soft: number;
|
resistance_soft: number;
|
||||||
unlocks: Unlock[];
|
unlocks: Unlock[];
|
||||||
repair_time: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EngineDefinition {
|
export interface EngineDefinition {
|
||||||
@@ -1760,7 +1759,6 @@ function createBaseTrackDefinition(): TrackDefinition {
|
|||||||
resistance_medium: 0,
|
resistance_medium: 0,
|
||||||
resistance_soft: 0,
|
resistance_soft: 0,
|
||||||
unlocks: [],
|
unlocks: [],
|
||||||
repair_time: 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1802,9 +1800,6 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
|
|||||||
for (const v of message.unlocks) {
|
for (const v of message.unlocks) {
|
||||||
Unlock.encode(v!, writer.uint32(98).fork()).join();
|
Unlock.encode(v!, writer.uint32(98).fork()).join();
|
||||||
}
|
}
|
||||||
if (message.repair_time !== 0) {
|
|
||||||
writer.uint32(109).float(message.repair_time);
|
|
||||||
}
|
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1911,14 +1906,6 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
|
|||||||
message.unlocks.push(Unlock.decode(reader, reader.uint32()));
|
message.unlocks.push(Unlock.decode(reader, reader.uint32()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case 13: {
|
|
||||||
if (tag !== 109) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.repair_time = reader.float();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ((tag & 7) === 4 || tag === 0) {
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
break;
|
break;
|
||||||
@@ -1942,7 +1929,6 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
|
|||||||
resistance_medium: isSet(object.resistance_medium) ? globalThis.Number(object.resistance_medium) : 0,
|
resistance_medium: isSet(object.resistance_medium) ? globalThis.Number(object.resistance_medium) : 0,
|
||||||
resistance_soft: isSet(object.resistance_soft) ? globalThis.Number(object.resistance_soft) : 0,
|
resistance_soft: isSet(object.resistance_soft) ? globalThis.Number(object.resistance_soft) : 0,
|
||||||
unlocks: globalThis.Array.isArray(object?.unlocks) ? object.unlocks.map((e: any) => Unlock.fromJSON(e)) : [],
|
unlocks: globalThis.Array.isArray(object?.unlocks) ? object.unlocks.map((e: any) => Unlock.fromJSON(e)) : [],
|
||||||
repair_time: isSet(object.repair_time) ? globalThis.Number(object.repair_time) : 0,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1984,9 +1970,6 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
|
|||||||
if (message.unlocks?.length) {
|
if (message.unlocks?.length) {
|
||||||
obj.unlocks = message.unlocks.map((e) => Unlock.toJSON(e));
|
obj.unlocks = message.unlocks.map((e) => Unlock.toJSON(e));
|
||||||
}
|
}
|
||||||
if (message.repair_time !== 0) {
|
|
||||||
obj.repair_time = message.repair_time;
|
|
||||||
}
|
|
||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -2011,7 +1994,6 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
|
|||||||
message.resistance_medium = object.resistance_medium ?? 0;
|
message.resistance_medium = object.resistance_medium ?? 0;
|
||||||
message.resistance_soft = object.resistance_soft ?? 0;
|
message.resistance_soft = object.resistance_soft ?? 0;
|
||||||
message.unlocks = object.unlocks?.map((e) => Unlock.fromPartial(e)) || [];
|
message.unlocks = object.unlocks?.map((e) => Unlock.fromPartial(e)) || [];
|
||||||
message.repair_time = object.repair_time ?? 0;
|
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,28 +90,6 @@ export interface Hierarchy {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseHierarchies<Parent>(
|
|
||||||
hierarchies: Hierarchy[],
|
|
||||||
parent: Parent,
|
|
||||||
parseHierarchy: (
|
|
||||||
hierarchy: Hierarchy,
|
|
||||||
components: Component[],
|
|
||||||
parent: Parent,
|
|
||||||
) => Parent | undefined,
|
|
||||||
) {
|
|
||||||
for (const hierarchy of hierarchies) {
|
|
||||||
const components = Array.from(
|
|
||||||
{ length: hierarchy.components.count },
|
|
||||||
(_, index) => hierarchy.components[index.toString().padStart(4, "0")],
|
|
||||||
);
|
|
||||||
const child = parseHierarchy(hierarchy, components, parent);
|
|
||||||
|
|
||||||
if (child !== undefined && hierarchy["#hierarchy"]) {
|
|
||||||
parseHierarchies(hierarchy["#hierarchy"], child, parseHierarchy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0001, 0002, 0003...: Component
|
// 0001, 0002, 0003...: Component
|
||||||
type Components = {
|
type Components = {
|
||||||
count: number;
|
count: number;
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ export interface VehicleDefinitions {
|
|||||||
};
|
};
|
||||||
level: number;
|
level: number;
|
||||||
hullPosition: string;
|
hullPosition: string;
|
||||||
hysteresisHealth: number;
|
|
||||||
healthRegenPerSec: number;
|
|
||||||
armor: {
|
armor: {
|
||||||
leftTrack:
|
leftTrack:
|
||||||
| number
|
| number
|
||||||
|
|||||||
@@ -683,7 +683,6 @@
|
|||||||
"hullTraverseSoftTerrain": "Soft",
|
"hullTraverseSoftTerrain": "Soft",
|
||||||
"health": "Health",
|
"health": "Health",
|
||||||
"fireChance": "Fire chance",
|
"fireChance": "Fire chance",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "View range",
|
"viewRange": "View range",
|
||||||
"camouflageStill": "Camouflage still",
|
"camouflageStill": "Camouflage still",
|
||||||
"camouflageMoving": "Moving",
|
"camouflageMoving": "Moving",
|
||||||
|
|||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "Suave",
|
"hullTraverseSoftTerrain": "Suave",
|
||||||
"health": "Salud",
|
"health": "Salud",
|
||||||
"fireChance": "Probabilidad de incendio",
|
"fireChance": "Probabilidad de incendio",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "Rango de visión",
|
"viewRange": "Rango de visión",
|
||||||
"camouflageStill": "Quieto",
|
"camouflageStill": "Quieto",
|
||||||
"camouflageMoving": "En movimiento",
|
"camouflageMoving": "En movimiento",
|
||||||
|
|||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "Soft",
|
"hullTraverseSoftTerrain": "Soft",
|
||||||
"health": "Health",
|
"health": "Health",
|
||||||
"fireChance": "Fire chance",
|
"fireChance": "Fire chance",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "View range",
|
"viewRange": "View range",
|
||||||
"camouflageStill": "Still",
|
"camouflageStill": "Still",
|
||||||
"camouflageMoving": "Moving",
|
"camouflageMoving": "Moving",
|
||||||
|
|||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "軟弱地",
|
"hullTraverseSoftTerrain": "軟弱地",
|
||||||
"health": "耐久値",
|
"health": "耐久値",
|
||||||
"fireChance": "火災発生率",
|
"fireChance": "火災発生率",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "視認範囲",
|
"viewRange": "視認範囲",
|
||||||
"camouflageStill": "静止時",
|
"camouflageStill": "静止時",
|
||||||
"camouflageMoving": "移動時",
|
"camouflageMoving": "移動時",
|
||||||
|
|||||||
+200
-201
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"test": {
|
"test": {
|
||||||
"default": "test",
|
"default": "test",
|
||||||
"long": "To jest długi ciąg testowy. NIGDY NIE ZOBACZYSZ tego w interfejsie użytkownika. Mój ulubiony owoc to {fruit}, ponieważ ma kolor {color}. Tak bardzo go lubię, że powiem jego nazwę 3 razy! {fruit}! {fruit}! {fruit}! Po raz kolejny to {color}, a nie {not_color}.",
|
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
|
||||||
"fox": "Szybki brązowy lis przeskoczył nad leniwym psem."
|
"fox": "Szybki brązowy lis przeskoczył nad leniwym psem."
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"gun_types": {
|
"gun_types": {
|
||||||
"auto_loader": "Samo ładowacz",
|
"auto_loader": "Samo ładowacz",
|
||||||
"auto_reloader": "Automatyczne przeładowanie",
|
"auto_reloader": "Automatyczne przeładowanie",
|
||||||
"regular": "Regularny"
|
"regular": "Regular"
|
||||||
},
|
},
|
||||||
"shells": {
|
"shells": {
|
||||||
"ap": "AP",
|
"ap": "AP",
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
},
|
},
|
||||||
"branches": {
|
"branches": {
|
||||||
"beta": "Beta",
|
"beta": "Beta",
|
||||||
"development": "Rozwój",
|
"development": "Development",
|
||||||
"opentest": "OtwartyTest",
|
"opentest": "OpenTest",
|
||||||
"preview": "Przegląd",
|
"preview": "Preview",
|
||||||
"production": "Produkcja"
|
"production": "Production"
|
||||||
},
|
},
|
||||||
"locales": {
|
"locales": {
|
||||||
"en": "Angielski",
|
"en": "Angielski",
|
||||||
@@ -102,28 +102,28 @@
|
|||||||
"other": "Hybryda"
|
"other": "Hybryda"
|
||||||
},
|
},
|
||||||
"tank_class_short": {
|
"tank_class_short": {
|
||||||
"-1": "nierozpoznany",
|
"-1": "unrecognized",
|
||||||
"0": "Lekki",
|
"0": "Lekki",
|
||||||
"1": "Średni",
|
"1": "Średni",
|
||||||
"2": "Ciężki",
|
"2": "Ciężki",
|
||||||
"3": "NC"
|
"3": "TD"
|
||||||
},
|
},
|
||||||
"tank_class_short_plural_lowercase": {
|
"tank_class_short_plural_lowercase": {
|
||||||
"0": "lekkie",
|
"0": "lekkie",
|
||||||
"1": "średnie",
|
"1": "średnie",
|
||||||
"2": "ciężkie",
|
"2": "ciężkie",
|
||||||
"3": "NC-ów"
|
"3": "TDs"
|
||||||
},
|
},
|
||||||
"tank_class_medium": {
|
"tank_class_medium": {
|
||||||
"-1": "nierozpoznany",
|
"-1": "unrecognized",
|
||||||
"0": "Lekki",
|
"0": "Lekki",
|
||||||
"1": "Średni",
|
"1": "Średni",
|
||||||
"2": "Ciężki",
|
"2": "Ciężki",
|
||||||
"3": "Niszczyciel czołgów"
|
"3": "Niszczyciel czołgów"
|
||||||
},
|
},
|
||||||
"tree_type": {
|
"tree_type": {
|
||||||
"-1": "nierozpoznany",
|
"-1": "unrecognized",
|
||||||
"0": "Drzewko technologiczne",
|
"0": "Tech tree",
|
||||||
"1": "Premium",
|
"1": "Premium",
|
||||||
"2": "Kolekcjonerski"
|
"2": "Kolekcjonerski"
|
||||||
},
|
},
|
||||||
@@ -187,223 +187,223 @@
|
|||||||
},
|
},
|
||||||
"website": {
|
"website": {
|
||||||
"redirect": {
|
"redirect": {
|
||||||
"title": "301: przeniesiony na stałe",
|
"title": "301: moved permanently",
|
||||||
"body": "Ta strona została przeniesiona. Przekierowywanie..."
|
"body": "This page has moved. Redirecting..."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Ustawienia",
|
"title": "Settings",
|
||||||
"description": "Zmień ustawienia BlitzKit",
|
"description": "Change BlitzKit settings",
|
||||||
"dev_mode": "Tryb deweloperski",
|
"dev_mode": "Development mode",
|
||||||
"auto_load_models": "Automatycznie wczytaj modele",
|
"auto_load_models": "Auto-load models",
|
||||||
"language": "Język"
|
"language": "Language"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"player_search": {
|
"player_search": {
|
||||||
"hint": "Szukaj graczy...",
|
"hint": "Search players...",
|
||||||
"no_results": "Brak wyników",
|
"no_results": "No results",
|
||||||
"searching": "Wyszukiwanie..."
|
"searching": "Searching..."
|
||||||
},
|
},
|
||||||
"copy_button": {
|
"copy_button": {
|
||||||
"copied": "Skopiowano!"
|
"copied": "Copied!"
|
||||||
},
|
},
|
||||||
"tank_search": {
|
"tank_search": {
|
||||||
"select_dialog_title": "Szukaj czołgów",
|
"select_dialog_title": "Search for tanks",
|
||||||
"swap_dialog_title": "Zamień czołg",
|
"swap_dialog_title": "Swap tank",
|
||||||
"clear_filters": "Wyczyść filtry",
|
"clear_filters": "Clear filters",
|
||||||
"login": "Zaloguj się, aby tego użyć",
|
"login": "Login to use this",
|
||||||
"search_bar_hint": "Szukaj czołgów...",
|
"search_bar_hint": "Search tanks...",
|
||||||
"recent": "Ostatnio oglądane",
|
"recent": "Recently viewed",
|
||||||
"select_all": "Zaznacz wszystko",
|
"select_all": "Select all",
|
||||||
"count_singular": "1 czołg",
|
"count_singular": "1 tank",
|
||||||
"count_plural": "{count} czołgi",
|
"count_plural": "{count} tanks",
|
||||||
"sorting_by": "Sortowanie według {name}",
|
"sorting_by": "Sorting by {name}",
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"tier": "Poziom czołgu {tier}",
|
"tier": "Tier {tier}",
|
||||||
"passive": "{name} (Pasywny)",
|
"passive": "{name} (Passive)",
|
||||||
"active": "{name} (Aktywne)",
|
"active": "{name} (Active)",
|
||||||
"no_tanks_found": {
|
"no_tanks_found": {
|
||||||
"body": "Nie znaleziono czołgu.",
|
"body": "No tanks found.",
|
||||||
"search_again": "",
|
"search_again": "",
|
||||||
"clear_filters": "Spróbuj wyczyścić filtry."
|
"clear_filters": "Try clearing filters."
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"meta.none": "brak",
|
"meta.none": "none",
|
||||||
"survivability.health": "punkty.życia",
|
"survivability.health": "health",
|
||||||
"survivability.viewRange": "zasięg widzenia",
|
"survivability.viewRange": "view range",
|
||||||
"survivability.camouflageStill": "kamuflaż w bezruchu",
|
"survivability.camouflageStill": "camouflage still",
|
||||||
"survivability.camouflageMoving": "kamuflaż w trakcie ruchu",
|
"survivability.camouflageMoving": "camouflage moving",
|
||||||
"survivability.camouflageShooting": "kamuflaż podczas strzału",
|
"survivability.camouflageShooting": "camouflage shooting",
|
||||||
"survivability.volume": "wartość",
|
"survivability.volume": "volume",
|
||||||
"survivability.length": "długość",
|
"survivability.length": "length",
|
||||||
"fire.dpm": "DPM pocisków podstawowych",
|
"fire.dpm": "standard DPM",
|
||||||
"fire.dpmPremium": "DPM pocisków premium",
|
"fire.dpmPremium": "premium DPM",
|
||||||
"fire.reload": "przeładowanie",
|
"fire.reload": "reload",
|
||||||
"fire.caliber": "kaliber",
|
"fire.caliber": "caliber",
|
||||||
"fire.standardPenetration": "Penetracja pocisków standardowych",
|
"fire.standardPenetration": "standard penetration",
|
||||||
"fire.premiumPenetration": "Penetracja pocisków premium",
|
"fire.premiumPenetration": "premium penetration",
|
||||||
"fire.tertiaryPenetration": "tertiary penetration",
|
"fire.tertiaryPenetration": "tertiary penetration",
|
||||||
"fire.damage": "uszkodzenia",
|
"fire.damage": "damage",
|
||||||
"fire.moduleDamage": "Uszkodzenia modułów",
|
"fire.moduleDamage": "module damage",
|
||||||
"fire.shellVelocity": "Prędkość pocisku",
|
"fire.shellVelocity": "shell velocity",
|
||||||
"fire.shellRange": "Zasięg pocisku",
|
"fire.shellRange": "shell range",
|
||||||
"fire.shellCapacity": "Maksymalna ilość pocisków",
|
"fire.shellCapacity": "shell capacity",
|
||||||
"fire.aimTime": "Czas celowania",
|
"fire.aimTime": "aim time",
|
||||||
"fire.dispersionStill": "dispersion still",
|
"fire.dispersionStill": "dispersion still",
|
||||||
"fire.dispersionMoving": "dispersion moving",
|
"fire.dispersionMoving": "dispersion moving",
|
||||||
"fire.gunDepression": "Depresja działa",
|
"fire.gunDepression": "gun depression",
|
||||||
"fire.gunElevation": "Elewacja działa",
|
"fire.gunElevation": "gun elevation",
|
||||||
"maneuverability.forwardsSpeed": "Maksymalna prędkość do przodu",
|
"maneuverability.forwardsSpeed": "forwards speed",
|
||||||
"maneuverability.backwardsSpeed": "Maksymalna prędkość do tyłu",
|
"maneuverability.backwardsSpeed": "backwards speed",
|
||||||
"maneuverability.power": "Moc silnika",
|
"maneuverability.power": "power",
|
||||||
"maneuverability.powerToWeight": "Proporcja mocy do masy",
|
"maneuverability.powerToWeight": "power to weight",
|
||||||
"maneuverability.weight": "Masa czołgu",
|
"maneuverability.weight": "weight",
|
||||||
"maneuverability.traverseSpeed": "traverse speed"
|
"maneuverability.traverseSpeed": "traverse speed"
|
||||||
},
|
},
|
||||||
"sort_dropdown": {
|
"sort_dropdown": {
|
||||||
"label": "Filtruj",
|
"label": "Sort",
|
||||||
"groups": {
|
"groups": {
|
||||||
"sort": {
|
"sort": {
|
||||||
"no_sort": "Brak filtrów",
|
"no_sort": "No sort",
|
||||||
"label": "Filtruj według",
|
"label": "Sort by",
|
||||||
"fire": "Siła ognia",
|
"fire": "Fire",
|
||||||
"maneuverability": "Mobilność ",
|
"maneuverability": "Maneuverability",
|
||||||
"survivability": "Przetrwanie"
|
"survivability": "Survivability"
|
||||||
},
|
},
|
||||||
"direction": {
|
"direction": {
|
||||||
"label": "Direction",
|
"label": "Direction",
|
||||||
"ascending": "Rosnąco",
|
"ascending": "Ascending",
|
||||||
"descending": "Malejąco"
|
"descending": "Descending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warnings": {
|
"warnings": {
|
||||||
"test": "Wartości statystyk czołgów w fazie testowej mogą ulec zmianie."
|
"test": "Tanks in testing are subject to change and may not represent the final product."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"discord": {
|
"discord": {
|
||||||
"join_short": "Dołącz do elity!",
|
"join_short": "Join cool people!",
|
||||||
"join_long": "Dołącz do topowej elity!",
|
"join_long": "Join super duper cool people!",
|
||||||
"action": "Discord"
|
"action": "Discord"
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"credit": "Według {name} ♥️"
|
"credit": "By {name} ❤️"
|
||||||
},
|
},
|
||||||
"hot_tanks": {
|
"hot_tanks": {
|
||||||
"title": "Popularne czołgi",
|
"title": "Popular tanks",
|
||||||
"subtitle": "Ostatnie {days} dni"
|
"subtitle": "Last {days} days"
|
||||||
},
|
},
|
||||||
"other_projects": {
|
"other_projects": {
|
||||||
"title": "Inne epickie projekty"
|
"title": "Other cool projects"
|
||||||
},
|
},
|
||||||
"help_translate": "Proszę pomóż przetłumaczyć {name} na {language}!",
|
"help_translate": "Please help translate {name} to {language}!",
|
||||||
"seo_description": "Narzędzia do wszystkiego World of Tanks Blitz"
|
"seo_description": "Tools for everything World of Tanks Blitz"
|
||||||
},
|
},
|
||||||
"docs": {
|
"docs": {
|
||||||
"patches": {
|
"patches": {
|
||||||
"name": "Zapisy zmian BlitzKit",
|
"name": "BlitzKit Changelogs",
|
||||||
"seo_description": "Zapisy zmian BlitzKit: Discord bot i strona internetowa"
|
"seo_description": "Changelogs for BlitzKit Discord bot and website"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"playlist": {
|
"playlist": {
|
||||||
"reset": "Resetuj",
|
"reset": "Reset",
|
||||||
"shuffle": "Wymieszaj",
|
"shuffle": "Shuffle",
|
||||||
"remove": "Usuń czołgi, którymi grałem",
|
"remove": "Remove played",
|
||||||
"tracking": "BlitzKit zapisuje twoje bitwy",
|
"tracking": "BlitzKit is tracking your battles",
|
||||||
"sort": {
|
"sort": {
|
||||||
"button": "Filtruj według",
|
"button": "Sort by",
|
||||||
"last_played": "Ostatnio używane czołgi",
|
"last_played": "Last played",
|
||||||
"least_played": "Najmniej używane czołgi"
|
"least_played": "Least played"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"index": "#",
|
"index": "#",
|
||||||
"never": "Nigdy",
|
"never": "Never",
|
||||||
"tank": "Czołg",
|
"tank": "Tank",
|
||||||
"last_played": "Ostatnio używane czołgi",
|
"last_played": "Last played",
|
||||||
"battles": "Wszystkie bitwy na koncie",
|
"battles": "Career battles",
|
||||||
"unrecorded": "--",
|
"unrecorded": "--",
|
||||||
"wins": "Zwycięstwa"
|
"wins": "Zwycięstwa"
|
||||||
},
|
},
|
||||||
"large_warning": {
|
"large_warning": {
|
||||||
"title": "To jest mnóstwo czołgów!",
|
"title": "That's a lot of tanks!",
|
||||||
"description": "Czy na pewno chcesz stworzyć playlistę złożoną z {count} czołgów? To {time} godzin gry przy 50% zwycięstw!",
|
"description": "Are you sure you want to create a playlist of {count} tanks? That's {time} hours of gameplay at 50% winrate!",
|
||||||
"cancel": "Anuluj",
|
"cancel": "Cancel",
|
||||||
"proceed": "Zatwierdź"
|
"proceed": "Proceed"
|
||||||
},
|
},
|
||||||
"generate": "Wygeneruj playlistę",
|
"generate": "Generate playlist",
|
||||||
"name": "Playlista",
|
"name": "Playlist",
|
||||||
"description": "Generator playlisty czołgów",
|
"description": "Tank playlist generator",
|
||||||
"seo_description": "Nie wiesz, którym czołgiem grać w World of Tanks Blitz? Użyj funkcji playlisty BlitzKit, i ułóż własną listę czołgów do gry!",
|
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!",
|
||||||
"button": "Wygeneruj",
|
"button": "Generate",
|
||||||
"pool_size": "{count} możliwe czołgi"
|
"pool_size": "{count} possible tanks"
|
||||||
},
|
},
|
||||||
"mixer": {
|
"mixer": {
|
||||||
"name": "Mikser",
|
"name": "Mixer",
|
||||||
"button": "Mix and match",
|
"button": "Mix and match",
|
||||||
"description": "Mix tank modules",
|
"description": "Mix tank modules",
|
||||||
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
|
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
|
||||||
"screenshot": "Zrzut ekranu",
|
"screenshot": "Screenshot",
|
||||||
"randomize": "Wymieszaj"
|
"randomize": "Randomize"
|
||||||
},
|
},
|
||||||
"guess": {
|
"guess": {
|
||||||
"name": "Zgadnij",
|
"name": "Guess",
|
||||||
"button": "What could it be?",
|
"button": "What could it be?",
|
||||||
"description": "Silhouette mini-game",
|
"description": "Silhouette mini-game",
|
||||||
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
|
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Wpisz odpowiedź",
|
"placeholder": "Type to guess...",
|
||||||
"skip": "Pomiń",
|
"skip": "Skip",
|
||||||
"guess": "Zgaduję",
|
"guess": "Guess",
|
||||||
"next": "Następny",
|
"next": "Next",
|
||||||
"no_results": "Brak wyników"
|
"no_results": "Brak wyników"
|
||||||
},
|
},
|
||||||
"stats": "{correct} / {total} correct, {streak} streak",
|
"stats": "{correct} / {total} correct, {streak} streak",
|
||||||
"help": {
|
"help": {
|
||||||
"title": "Odsłonić część czołgu?",
|
"title": "Partially reveal tank?",
|
||||||
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
|
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
|
||||||
"cancel": "Anuluj",
|
"cancel": "Cancel",
|
||||||
"reveal": "Pokaż"
|
"reveal": "Reveal"
|
||||||
},
|
},
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"select_all": "Zaznacz wszystko ",
|
"select_all": "Select all",
|
||||||
"tier": "Poziom czołgu {tier}"
|
"tier": "Tier {tier}"
|
||||||
},
|
},
|
||||||
"tankopedia": {
|
"tankopedia": {
|
||||||
"search": "Wyszukaj statystykę...",
|
"search": "Search a stat...",
|
||||||
"name": "Tankopedia",
|
"name": "Tankopedia",
|
||||||
"button": "Znajdź czołg",
|
"button": "Find tanks",
|
||||||
"description": "Encyklopedia czołgów w WoTb",
|
"description": "Blitz tank encyclopedia",
|
||||||
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz",
|
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz",
|
||||||
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
|
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
|
||||||
"tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.",
|
"tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.",
|
||||||
"visualizers": {
|
"visualizers": {
|
||||||
"reload": {
|
"reload": {
|
||||||
"skip": "Pomiń"
|
"skip": "Skip"
|
||||||
},
|
},
|
||||||
"assault": {
|
"assault": {
|
||||||
"title": "Assault shells"
|
"title": "Assault shells"
|
||||||
},
|
},
|
||||||
"rotator": {
|
"rotator": {
|
||||||
"hull": "Kadłub",
|
"hull": "Hull",
|
||||||
"turret": "Wieża",
|
"turret": "Turret",
|
||||||
"hard": "Utwardzony",
|
"hard": "Hard",
|
||||||
"medium": "Średni",
|
"medium": "Średni",
|
||||||
"soft": "Miękki",
|
"soft": "Soft",
|
||||||
"effective": "Rzeczywisty"
|
"effective": "Effective"
|
||||||
},
|
},
|
||||||
"ricochet": {
|
"ricochet": {
|
||||||
"angle": "Kąt",
|
"angle": "Angle",
|
||||||
"normalization": "Normalization",
|
"normalization": "Normalization",
|
||||||
"effective_angle": "Rzeczywisty kąt nachylenia pancerza",
|
"effective_angle": "Effective angle",
|
||||||
"armor": "Pancerz",
|
"armor": "Armor",
|
||||||
"no_ricochet": "Pociski odłamkowe nie rykoszetują."
|
"no_ricochet": "Explosive shells don't ricochet"
|
||||||
},
|
},
|
||||||
"view_range": {
|
"view_range": {
|
||||||
"camo": "Kamuflaż",
|
"camo": "Camo",
|
||||||
"ghost_factory": "Fabryka duchów",
|
"ghost_factory": "Ghost Factory",
|
||||||
"dead_rail": "Martwy tor"
|
"dead_rail": "Dead Rail"
|
||||||
},
|
},
|
||||||
"flexibility": {
|
"flexibility": {
|
||||||
"yaw": "Yaw",
|
"yaw": "Yaw",
|
||||||
@@ -443,71 +443,71 @@
|
|||||||
"ricochet": "Ricochet",
|
"ricochet": "Ricochet",
|
||||||
"explosion_radius": "Explosion radius",
|
"explosion_radius": "Explosion radius",
|
||||||
"exit": "Exit",
|
"exit": "Exit",
|
||||||
"reset": "Zresetuj"
|
"reset": "Reset"
|
||||||
},
|
},
|
||||||
"advanced": "Zaawansowane podświetlanie",
|
"advanced": "Advanced highlighting",
|
||||||
"shot_card": {
|
"shot_card": {
|
||||||
"element": {
|
"element": {
|
||||||
"primary": "Główny",
|
"primary": "Primary",
|
||||||
"spaced": "Spaced",
|
"spaced": "Spaced",
|
||||||
"external": "Zewnętrzny",
|
"external": "External",
|
||||||
"gap": "Odstęp",
|
"gap": "Gap",
|
||||||
"gun": "Działo",
|
"gun": "Gun",
|
||||||
"track": "Gąsienice"
|
"track": "Tracks"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"penetration": "{damage} penetracja punktów wytrzymałości",
|
"penetration": "{damage}hp penetration",
|
||||||
"blocked": "Zablokowany",
|
"blocked": "Blocked",
|
||||||
"ricochet": "Rykoszet",
|
"ricochet": "Ricochet",
|
||||||
"splash": "{damage} rozprysk punktów wytrzymałości"
|
"splash": "{damage}hp splash"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"thickness_and_angle": "{thickness}mm {angle}",
|
"thickness_and_angle": "{thickness}mm {angle}°",
|
||||||
"nominal": "{thickness}mm nominalny",
|
"nominal": "{thickness}mm nominal",
|
||||||
"ricochet_loss": "{loss}% penetracji"
|
"ricochet_loss": "{loss}% penetration"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"static": {
|
"static": {
|
||||||
"name": "Pancerz statyczny",
|
"name": "Static armor",
|
||||||
"primary": "Główny Pancerz",
|
"primary": "Primary",
|
||||||
"spaced": "Ekran przeciwkumulacyjny",
|
"spaced": "Spaced",
|
||||||
"external": "Zewnętrzny",
|
"external": "External",
|
||||||
"edit": {
|
"edit": {
|
||||||
"label": "Edytuj",
|
"label": "Edit",
|
||||||
"reset": "Zresetuj"
|
"reset": "Reset"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"back": "Powrót",
|
"back": "Back",
|
||||||
"compare": "Porównaj",
|
"compare": "Compare",
|
||||||
"swap": {
|
"swap": {
|
||||||
"button": "Zamień",
|
"button": "Swap",
|
||||||
"title": "Zamień czołgi"
|
"title": "Swap tanks"
|
||||||
},
|
},
|
||||||
"vanilla": "Vanilla",
|
"vanilla": "Vanilla",
|
||||||
"opentest": "OpenTest",
|
"opentest": "OpenTest",
|
||||||
"full_name": "Pełna nazwa",
|
"full_name": "Full name",
|
||||||
"nation": "Naród",
|
"nation": "Naród",
|
||||||
"class": "Klasa",
|
"class": "Klasa",
|
||||||
"tier": "Poziom",
|
"tier": "Poziom",
|
||||||
"type": "Rodzaj",
|
"type": "Rodzaj",
|
||||||
"dev_id": "DEV: ID",
|
"dev_id": "DEV: ID",
|
||||||
"dev_name": "DEV: NAZWA",
|
"dev_name": "DEV: NAME",
|
||||||
"purchase": "Cena zakupu",
|
"purchase": "Purchase price",
|
||||||
"restoration": "Cena odzyskania",
|
"restoration": "Restoration price",
|
||||||
"sale": "Sale price",
|
"sale": "Sale price",
|
||||||
"research": "Research XP"
|
"research": "Research XP"
|
||||||
},
|
},
|
||||||
"tech_tree": {
|
"tech_tree": {
|
||||||
"title": "Drzewko technologiczne",
|
"title": "Tech tree",
|
||||||
"battle": "1 bitwa",
|
"battle": "1 battle",
|
||||||
"battles": "{count} bitew",
|
"battles": "{count} battles",
|
||||||
"route": "Route {index} of {count}"
|
"route": "Route {index} of {count}"
|
||||||
},
|
},
|
||||||
"game_modes": {
|
"game_modes": {
|
||||||
"title": "Umiejętności trybu gry"
|
"title": "Game mode abilities"
|
||||||
},
|
},
|
||||||
"review": {
|
"review": {
|
||||||
"title": "Review videos",
|
"title": "Review videos",
|
||||||
@@ -520,26 +520,26 @@
|
|||||||
"equalize": "Equalize"
|
"equalize": "Equalize"
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules": {
|
||||||
"title": "Moduły",
|
"title": "Modules",
|
||||||
"stock": "Standardowy",
|
"stock": "Stock",
|
||||||
"upgrade": "Ulepsz"
|
"upgrade": "Upgrade"
|
||||||
},
|
},
|
||||||
"equipment": {
|
"equipment": {
|
||||||
"title": "Wyposażenie",
|
"title": "Equipment",
|
||||||
"clear": "Wyczyść"
|
"clear": "Clear"
|
||||||
},
|
},
|
||||||
"provisions": {
|
"provisions": {
|
||||||
"title": "Materiały eksploatacyjne",
|
"title": "Provisions",
|
||||||
"clear": "Wyczyść"
|
"clear": "Clear"
|
||||||
},
|
},
|
||||||
"consumables": {
|
"consumables": {
|
||||||
"title": "Consumables",
|
"title": "Consumables",
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"info": "Yellow consumables do not affect the statistics."
|
"info": "Yellow consumables do not affect the statistics."
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
"title": "Crew skills",
|
"title": "Crew skills",
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"maximize": "Maximize",
|
"maximize": "Maximize",
|
||||||
"info": "Yellow skills only apply under special circumstances. Hold Space to quickly toggle between level 0 and 7."
|
"info": "Yellow skills only apply under special circumstances. Hold Space to quickly toggle between level 0 and 7."
|
||||||
},
|
},
|
||||||
@@ -586,19 +586,19 @@
|
|||||||
"damageWithoutAssault": "Damage without assault shells",
|
"damageWithoutAssault": "Damage without assault shells",
|
||||||
"clipDamage": "Clipping potential",
|
"clipDamage": "Clipping potential",
|
||||||
"moduleDamage": "Module damage",
|
"moduleDamage": "Module damage",
|
||||||
"explosionRadius": "Promień eksplozji",
|
"explosionRadius": "Explosion radius",
|
||||||
"shellVelocity": "Prędkość pocisku",
|
"shellVelocity": "Shell velocity",
|
||||||
"aimTime": "Czas celowania",
|
"aimTime": "Aim time",
|
||||||
"dispersion": "Still",
|
"dispersion": "Still",
|
||||||
"dispersionMoving": "Moving",
|
"dispersionMoving": "Moving",
|
||||||
"dispersionHullTraversing": "Hull traversing",
|
"dispersionHullTraversing": "Hull traversing",
|
||||||
"dispersionTurretTraversing": "Turret traversing",
|
"dispersionTurretTraversing": "Turret traversing",
|
||||||
"dispersionShooting": "Strzelanie",
|
"dispersionShooting": "Shooting",
|
||||||
"dispersionGunDamaged": "Uszkodzone Działo",
|
"dispersionGunDamaged": "Gun damaged",
|
||||||
"gunDepression": "Limit obrotu działa w dół",
|
"gunDepression": "Depression",
|
||||||
"gunElevation": "Limit obrotu działa w górę",
|
"gunElevation": "Elevation",
|
||||||
"gunFrontalDepression": "Przedni limit obrotu działa w dół",
|
"gunFrontalDepression": "Frontal depression",
|
||||||
"gunFrontalElevation": "Przedni limit obrotu działa w górę",
|
"gunFrontalElevation": "Frontal elevation",
|
||||||
"gunRearDepression": "Rear depression",
|
"gunRearDepression": "Rear depression",
|
||||||
"gunRearElevation": "Rear elevation",
|
"gunRearElevation": "Rear elevation",
|
||||||
"azimuthLeft": "Azimuth left",
|
"azimuthLeft": "Azimuth left",
|
||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "Soft",
|
"hullTraverseSoftTerrain": "Soft",
|
||||||
"health": "Health",
|
"health": "Health",
|
||||||
"fireChance": "Fire chance",
|
"fireChance": "Fire chance",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "View range",
|
"viewRange": "View range",
|
||||||
"camouflageStill": "Still",
|
"camouflageStill": "Still",
|
||||||
"camouflageMoving": "Moving",
|
"camouflageMoving": "Moving",
|
||||||
@@ -656,14 +655,14 @@
|
|||||||
"loader": "Loader"
|
"loader": "Loader"
|
||||||
},
|
},
|
||||||
"maneuverability": {
|
"maneuverability": {
|
||||||
"title": "Mobilność "
|
"title": "Maneuverability"
|
||||||
},
|
},
|
||||||
"survivability": {
|
"survivability": {
|
||||||
"title": "Przetrwanie"
|
"title": "Survivability"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compare": {
|
"compare": {
|
||||||
"name": "Porównaj",
|
"name": "Compare",
|
||||||
"button": "Compare tanks",
|
"button": "Compare tanks",
|
||||||
"description": "Compare tank statistics",
|
"description": "Compare tank statistics",
|
||||||
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz",
|
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz",
|
||||||
@@ -673,7 +672,7 @@
|
|||||||
"title": "Add tanks",
|
"title": "Add tanks",
|
||||||
"description": "Select tanks to add to comparison"
|
"description": "Select tanks to add to comparison"
|
||||||
},
|
},
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"deltas": {
|
"deltas": {
|
||||||
"none": "No deltas",
|
"none": "No deltas",
|
||||||
"percentage": "Percentage",
|
"percentage": "Percentage",
|
||||||
@@ -686,17 +685,17 @@
|
|||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"equipment": {
|
"equipment": {
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"apply_to_all": "Apply to all"
|
"apply_to_all": "Apply to all"
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
"clear": "Wyczyść",
|
"clear": "Clear",
|
||||||
"maximize": "Maximize",
|
"maximize": "Maximize",
|
||||||
"equalize": "Equalize"
|
"equalize": "Equalize"
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules": {
|
||||||
"stock": "Standardowy",
|
"stock": "Stock",
|
||||||
"upgrade": "Ulepsz"
|
"upgrade": "Upgrade"
|
||||||
},
|
},
|
||||||
"provisions": {
|
"provisions": {
|
||||||
"clear": "Clear this tank",
|
"clear": "Clear this tank",
|
||||||
@@ -761,14 +760,14 @@
|
|||||||
"seo_description": "Track your stats in real time as you play World of Tanks Blitz",
|
"seo_description": "Track your stats in real time as you play World of Tanks Blitz",
|
||||||
"search": {
|
"search": {
|
||||||
"lookup": "Look up a player to get started",
|
"lookup": "Look up a player to get started",
|
||||||
"hint": "Szukaj graczy...",
|
"hint": "Search players...",
|
||||||
"searching": "Wyszukiwanie...",
|
"searching": "Searching...",
|
||||||
"no_results": "No players found"
|
"no_results": "No players found"
|
||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
"title": "Tracking {name}",
|
"title": "Tracking {name}",
|
||||||
"subtitle": "Since {time}",
|
"subtitle": "Since {time}",
|
||||||
"reset": "Zresetuj",
|
"reset": "Reset",
|
||||||
"change": "Change"
|
"change": "Change"
|
||||||
},
|
},
|
||||||
"no_cc": {
|
"no_cc": {
|
||||||
@@ -792,7 +791,7 @@
|
|||||||
"description": "Rank Blitz tanks",
|
"description": "Rank Blitz tanks",
|
||||||
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz",
|
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"reset": "Zresetuj",
|
"reset": "Reset",
|
||||||
"share": "Share"
|
"share": "Share"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -828,7 +827,7 @@
|
|||||||
"success": {
|
"success": {
|
||||||
"title": "Copy URL",
|
"title": "Copy URL",
|
||||||
"info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
|
"info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
|
||||||
"back": "Powrót",
|
"back": "Back",
|
||||||
"copy": "Copy URL"
|
"copy": "Copy URL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -839,7 +838,7 @@
|
|||||||
},
|
},
|
||||||
"customize": {
|
"customize": {
|
||||||
"title": "Customize",
|
"title": "Customize",
|
||||||
"reset": "Zresetuj",
|
"reset": "Reset",
|
||||||
"description": "Bells and whistles of your embed"
|
"description": "Bells and whistles of your embed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"development": "Development",
|
"development": "Development",
|
||||||
"opentest": "TesteAberto",
|
"opentest": "TesteAberto",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"production": "Produção"
|
"production": "Production"
|
||||||
},
|
},
|
||||||
"locales": {
|
"locales": {
|
||||||
"en": "Inglês",
|
"en": "Inglês",
|
||||||
@@ -187,11 +187,11 @@
|
|||||||
},
|
},
|
||||||
"website": {
|
"website": {
|
||||||
"redirect": {
|
"redirect": {
|
||||||
"title": "301: movido permanentemente",
|
"title": "301: moved permanently",
|
||||||
"body": "This page has moved. Redirecting..."
|
"body": "This page has moved. Redirecting..."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Configurações",
|
"title": "Settings",
|
||||||
"description": "Change BlitzKit settings",
|
"description": "Change BlitzKit settings",
|
||||||
"dev_mode": "Modo de desenvolvimento",
|
"dev_mode": "Modo de desenvolvimento",
|
||||||
"auto_load_models": "Auto-load models",
|
"auto_load_models": "Auto-load models",
|
||||||
@@ -207,9 +207,9 @@
|
|||||||
"copied": "Copiado!"
|
"copied": "Copiado!"
|
||||||
},
|
},
|
||||||
"tank_search": {
|
"tank_search": {
|
||||||
"select_dialog_title": "Procura por tanque",
|
"select_dialog_title": "Search for tanks",
|
||||||
"swap_dialog_title": "Swap tank",
|
"swap_dialog_title": "Swap tank",
|
||||||
"clear_filters": "Limpar filtros",
|
"clear_filters": "Clear filters",
|
||||||
"login": "Login to use this",
|
"login": "Login to use this",
|
||||||
"search_bar_hint": "Procurar tanques...",
|
"search_bar_hint": "Procurar tanques...",
|
||||||
"recent": "Visto recentemente",
|
"recent": "Visto recentemente",
|
||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "Macio",
|
"hullTraverseSoftTerrain": "Macio",
|
||||||
"health": "Vida",
|
"health": "Vida",
|
||||||
"fireChance": "Chance de incêndio",
|
"fireChance": "Chance de incêndio",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "Alcance de visão",
|
"viewRange": "Alcance de visão",
|
||||||
"camouflageStill": "Parado",
|
"camouflageStill": "Parado",
|
||||||
"camouflageMoving": "Em movimento",
|
"camouflageMoving": "Em movimento",
|
||||||
|
|||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "На мягком грунте",
|
"hullTraverseSoftTerrain": "На мягком грунте",
|
||||||
"health": "Прочность",
|
"health": "Прочность",
|
||||||
"fireChance": "Вероятность возгорания",
|
"fireChance": "Вероятность возгорания",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "Радиус обзора",
|
"viewRange": "Радиус обзора",
|
||||||
"camouflageStill": "Когда неподвижен",
|
"camouflageStill": "Когда неподвижен",
|
||||||
"camouflageMoving": "В движении",
|
"camouflageMoving": "В движении",
|
||||||
|
|||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "Soft",
|
"hullTraverseSoftTerrain": "Soft",
|
||||||
"health": "Здоров'я",
|
"health": "Здоров'я",
|
||||||
"fireChance": "Fire chance",
|
"fireChance": "Fire chance",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "View range",
|
"viewRange": "View range",
|
||||||
"camouflageStill": "Нерухомо",
|
"camouflageStill": "Нерухомо",
|
||||||
"camouflageMoving": "У русі",
|
"camouflageMoving": "У русі",
|
||||||
|
|||||||
@@ -623,7 +623,6 @@
|
|||||||
"hullTraverseSoftTerrain": "水面上车转速率",
|
"hullTraverseSoftTerrain": "水面上车转速率",
|
||||||
"health": "血量",
|
"health": "血量",
|
||||||
"fireChance": "发动机受击时着火概率",
|
"fireChance": "发动机受击时着火概率",
|
||||||
"trackRepairTime": "Track repair time",
|
|
||||||
"viewRange": "视野范围",
|
"viewRange": "视野范围",
|
||||||
"camouflageStill": "静止隐蔽",
|
"camouflageStill": "静止隐蔽",
|
||||||
"camouflageMoving": "移动时",
|
"camouflageMoving": "移动时",
|
||||||
|
|||||||
@@ -128,30 +128,19 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
|||||||
? shotStatusColors[shot.out.status]
|
? shotStatusColors[shot.out.status]
|
||||||
: undefined;
|
: undefined;
|
||||||
const inTitleColor = shotStatusColors[shot.in.status];
|
const inTitleColor = shotStatusColors[shot.in.status];
|
||||||
const hasOutLayers = shot.out !== undefined && shot.out.layers.length > 0;
|
|
||||||
const penetrationChance = shot.penetrationChance !== undefined && (
|
|
||||||
<Text color="gray" weight="bold">
|
|
||||||
{literals(strings.common.units.percentage, {
|
|
||||||
value: (shot.penetrationChance * 100).toFixed(0),
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant="classic" {...props}>
|
<Card variant="classic" {...props}>
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Flex direction="column" gap="1">
|
<Flex direction="column" gap="1">
|
||||||
<Flex justify="between" align="center" gap="4">
|
<Text color={inTitleColor} weight="bold">
|
||||||
<Text color={inTitleColor} weight="bold">
|
{literals(
|
||||||
{literals(
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
shot.in.status
|
||||||
.status[shot.in.status],
|
],
|
||||||
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{!hasOutLayers && penetrationChance}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<table
|
<table
|
||||||
style={{
|
style={{
|
||||||
@@ -215,17 +204,13 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Inset>
|
</Inset>
|
||||||
|
|
||||||
<Flex justify="between" align="center" gap="4">
|
<Text color={outTitleColor} weight="bold">
|
||||||
<Text color={outTitleColor} weight="bold">
|
{literals(
|
||||||
{literals(
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
.status[shot.out.status],
|
||||||
.status[shot.out.status],
|
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||||
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
)}
|
||||||
)}
|
</Text>
|
||||||
</Text>
|
|
||||||
|
|
||||||
{penetrationChance}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex direction="column">
|
<Flex direction="column">
|
||||||
{shot.out.layers.map((layer, index) => {
|
{shot.out.layers.map((layer, index) => {
|
||||||
|
|||||||
@@ -63,36 +63,6 @@ export type ArmorUserData = {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const PENETRATION_RANDOMIZATION = 0.05;
|
|
||||||
|
|
||||||
function resolvePenetrationChance(shoot: (scale: number) => Shot | null) {
|
|
||||||
function penetrates(scale: number) {
|
|
||||||
const shot = shoot(scale);
|
|
||||||
|
|
||||||
return (shot?.out ?? shot?.in)?.status === "penetration";
|
|
||||||
}
|
|
||||||
|
|
||||||
let low = 1 - PENETRATION_RANDOMIZATION;
|
|
||||||
let high = 1 + PENETRATION_RANDOMIZATION;
|
|
||||||
|
|
||||||
if (penetrates(low)) return 1;
|
|
||||||
if (!penetrates(high)) return 0;
|
|
||||||
|
|
||||||
for (let iteration = 0; iteration < 16; iteration++) {
|
|
||||||
const middle = (low + high) / 2;
|
|
||||||
|
|
||||||
if (penetrates(middle)) {
|
|
||||||
high = middle;
|
|
||||||
} else {
|
|
||||||
low = middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
(1 + PENETRATION_RANDOMIZATION - high) / (2 * PENETRATION_RANDOMIZATION)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const omitMaterial = new MeshBasicMaterial({
|
const omitMaterial = new MeshBasicMaterial({
|
||||||
colorWrite: false,
|
colorWrite: false,
|
||||||
depthWrite: true,
|
depthWrite: true,
|
||||||
@@ -119,7 +89,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
intersections: Intersection[],
|
intersections: Intersection[],
|
||||||
allowRicochet: boolean,
|
allowRicochet: boolean,
|
||||||
remainingPenetrationInput?: number,
|
remainingPenetrationInput?: number,
|
||||||
penetrationScale = 1,
|
|
||||||
) => {
|
) => {
|
||||||
const { customShell } = Tankopedia.state;
|
const { customShell } = Tankopedia.state;
|
||||||
const shell = customShell ?? Duel.state.antagonist.shell;
|
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||||
@@ -156,7 +125,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
Duel.state.protagonist.equipmentMatrix,
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const penetration =
|
const penetration =
|
||||||
penetrationScale *
|
|
||||||
shell.penetration!.near *
|
shell.penetration!.near *
|
||||||
resolvePenetrationCoefficient(
|
resolvePenetrationCoefficient(
|
||||||
hasCalibratedShells,
|
hasCalibratedShells,
|
||||||
@@ -399,7 +367,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
ricochetIntersections,
|
ricochetIntersections,
|
||||||
false,
|
false,
|
||||||
remainingPenetration,
|
remainingPenetration,
|
||||||
penetrationScale,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
shot.in.status = "ricochet";
|
shot.in.status = "ricochet";
|
||||||
@@ -455,16 +422,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
const shot = shoot(event.point, event.intersections, true)!;
|
const shot = shoot(event.point, event.intersections, true)!;
|
||||||
|
|
||||||
shot.penetrationChance = resolvePenetrationChance((scale) =>
|
|
||||||
shoot(
|
|
||||||
event.point,
|
|
||||||
event.intersections,
|
|
||||||
true,
|
|
||||||
undefined,
|
|
||||||
scale,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
draft.shot = shot;
|
draft.shot = shot;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
import { tankIcon } from "@blitzkit/core";
|
||||||
|
import { Flex, Heading, Link, Strong, Text } from "@radix-ui/themes";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Flex justify="center" px="4">
|
||||||
|
<Link
|
||||||
|
color="gray"
|
||||||
|
highContrast
|
||||||
|
style={{ display: "content" }}
|
||||||
|
href="/docs/guides/christmas-2025"
|
||||||
|
underline="none"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
mt="3"
|
||||||
|
px="5"
|
||||||
|
py="4"
|
||||||
|
style={{
|
||||||
|
borderRadius: "var(--radius-3)",
|
||||||
|
backgroundImage: "url(https://i.imgur.com/LRYmSkg.png)",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
align="center"
|
||||||
|
gap="5"
|
||||||
|
>
|
||||||
|
<img src={tankIcon(25169)} style={{ height: "min(15vw, 5rem)" }} />
|
||||||
|
|
||||||
|
<Flex direction="column">
|
||||||
|
<Heading size={{ initial: "4", sm: "6" }}>Christmas 2025</Heading>
|
||||||
|
<Text size={{ initial: "2", sm: "3" }}>
|
||||||
|
Win the <Strong>GSOR the TANK</Strong> and more!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Code, Flex, Heading, Link, Text } from "@radix-ui/themes";
|
||||||
|
import { useEffect, useState, type ReactNode } from "react";
|
||||||
|
import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps";
|
||||||
|
import { InlineSkeleton } from "./InlineSkeleton";
|
||||||
|
|
||||||
|
const TIME = 1764898200 * 1000;
|
||||||
|
|
||||||
|
export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
|
const [timeLeft, setTimeLeft] = useState<number>(TIME - Date.now());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setTimeLeft(TIME - Date.now());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let string: ReactNode;
|
||||||
|
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((timeLeft / (1000 * 60)) % 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
const seconds = Math.floor((timeLeft / 1000) % 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
|
||||||
|
string = (
|
||||||
|
<>
|
||||||
|
in{" "}
|
||||||
|
<Code highContrast>
|
||||||
|
{hours}h {minutes}m {seconds}s
|
||||||
|
</Code>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
string = "has ended. Come again later!";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex justify="center" px="4">
|
||||||
|
<Link
|
||||||
|
color="gray"
|
||||||
|
highContrast
|
||||||
|
style={{ display: "content" }}
|
||||||
|
href="https://discord.gg/njQdBhNS?event=1446208951136813117"
|
||||||
|
underline="none"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
mt="3"
|
||||||
|
px="5"
|
||||||
|
py="4"
|
||||||
|
style={{
|
||||||
|
borderRadius: "var(--radius-3)",
|
||||||
|
backgroundImage: "url(https://i.imgur.com/LRYmSkg.png)",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
boxShadow: "0 0 var(--space-7) var(--blue-a5)",
|
||||||
|
}}
|
||||||
|
align="center"
|
||||||
|
gap="5"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://i.imgur.com/mvmJQgB.png"
|
||||||
|
style={{
|
||||||
|
height: "min(15vw, 5rem)",
|
||||||
|
filter: "drop-shadow(0.5rem 0.5rem 1rem #00000080)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Flex direction="column">
|
||||||
|
<Heading size={{ initial: "4", sm: "6" }}>
|
||||||
|
Music Box giveaway {!skeleton && string}{" "}
|
||||||
|
{skeleton && <InlineSkeleton width="10rem" />}
|
||||||
|
</Heading>
|
||||||
|
<Text size={{ initial: "2", sm: "3" }}>
|
||||||
|
Join the BlitzKit Discord server event
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
import { Link, Table } from '@radix-ui/themes';
|
import { Link, Table } from '@radix-ui/themes';
|
||||||
import MarkdownToJSX from 'markdown-to-jsx';
|
import MarkdownToJSX from 'markdown-to-jsx';
|
||||||
import type { ComponentProps } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
import { docImageUrl, isExternalImage } from '../core/blitzkit/docImages';
|
|
||||||
|
|
||||||
interface MarkdownProps {
|
interface MarkdownProps {
|
||||||
children: string;
|
children: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Image({ src, ...props }: ComponentProps<'img'>) {
|
function Image(props: ComponentProps<'img'>) {
|
||||||
return (
|
return <img style={{ maxWidth: '100%' }} {...props} />;
|
||||||
<img
|
|
||||||
style={{ maxWidth: '100%' }}
|
|
||||||
src={src && isExternalImage(src) ? docImageUrl(src) : src}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Markdown({ children }: MarkdownProps) {
|
export function Markdown({ children }: MarkdownProps) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { FilterControl } from "./TankSearch/components/FilterControl";
|
|||||||
const tankDefinitions = await api.tankDefinitions();
|
const tankDefinitions = await api.tankDefinitions();
|
||||||
|
|
||||||
const POOL_HEIGHT = "16rem";
|
const POOL_HEIGHT = "16rem";
|
||||||
const FILTERS_WIDTH = "32rem";
|
|
||||||
|
|
||||||
export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) {
|
export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const [largeWarningOpen, setLargeWarningOpen] = useState(false);
|
const [largeWarningOpen, setLargeWarningOpen] = useState(false);
|
||||||
@@ -81,19 +80,13 @@ export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
<Flex justify="center" width="100%" align="center" px="4">
|
<Flex justify="center" width="100%" align="center" px="4">
|
||||||
<Flex
|
<Flex
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
gap="6"
|
gap="9"
|
||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
maxWidth="80rem"
|
maxWidth="80rem"
|
||||||
direction={{ initial: "column-reverse", md: "row" }}
|
direction={{ initial: "column-reverse", md: "row" }}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex direction="column" gap="4" align="center">
|
||||||
direction="column"
|
|
||||||
gap="4"
|
|
||||||
align="center"
|
|
||||||
flexShrink="0"
|
|
||||||
width={{ initial: "100%", md: FILTERS_WIDTH }}
|
|
||||||
>
|
|
||||||
<FilterControl />
|
<FilterControl />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function StupidCat() {
|
||||||
|
return <img src="https://i.imgur.com/6FBBzb9.png" />;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
normalizeBoundingBox,
|
normalizeBoundingBox,
|
||||||
resolveDpm,
|
resolveDpm,
|
||||||
tankBoundingBox,
|
unionBoundingBox,
|
||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
} from "@blitzkit/core";
|
} from "@blitzkit/core";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
@@ -112,7 +112,10 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
|||||||
return (tank.camouflage_still * gun.camouflage_loss * 100).toFixed(0);
|
return (tank.camouflage_still * gun.camouflage_loss * 100).toFixed(0);
|
||||||
case "survivability.volume": {
|
case "survivability.volume": {
|
||||||
const dimensions = normalizeBoundingBox(
|
const dimensions = normalizeBoundingBox(
|
||||||
tankBoundingBox(tankModelDefinition, turretModelDefinition),
|
unionBoundingBox(
|
||||||
|
tankModelDefinition.bounding_box!,
|
||||||
|
turretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (dimensions.x * dimensions.y * dimensions.z).toFixed(0);
|
return (dimensions.x * dimensions.y * dimensions.z).toFixed(0);
|
||||||
@@ -120,7 +123,10 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
|||||||
|
|
||||||
case "survivability.length": {
|
case "survivability.length": {
|
||||||
const bounds = normalizeBoundingBox(
|
const bounds = normalizeBoundingBox(
|
||||||
tankBoundingBox(tankModelDefinition, turretModelDefinition),
|
unionBoundingBox(
|
||||||
|
tankModelDefinition.bounding_box!,
|
||||||
|
turretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Math.max(bounds.x, bounds.y, bounds.z).toFixed(0);
|
return Math.max(bounds.x, bounds.y, bounds.z).toFixed(0);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
normalizeBoundingBox,
|
normalizeBoundingBox,
|
||||||
resolveDpm,
|
resolveDpm,
|
||||||
SEARCH_KEYS,
|
SEARCH_KEYS,
|
||||||
tankBoundingBox,
|
unionBoundingBox,
|
||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
} from "@blitzkit/core";
|
} from "@blitzkit/core";
|
||||||
import { literals } from "@blitzkit/i18n/src/literals";
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
@@ -137,10 +137,16 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
const bTurretModelDefinition =
|
const bTurretModelDefinition =
|
||||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||||
const aSize = normalizeBoundingBox(
|
const aSize = normalizeBoundingBox(
|
||||||
tankBoundingBox(aTankModelDefinition, aTurretModelDefinition),
|
unionBoundingBox(
|
||||||
|
aTankModelDefinition.bounding_box!,
|
||||||
|
aTurretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const bSize = normalizeBoundingBox(
|
const bSize = normalizeBoundingBox(
|
||||||
tankBoundingBox(bTankModelDefinition, bTurretModelDefinition),
|
unionBoundingBox(
|
||||||
|
bTankModelDefinition.bounding_box!,
|
||||||
|
bTurretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const aVolume = aSize.x * aSize.y * aSize.z;
|
const aVolume = aSize.x * aSize.y * aSize.z;
|
||||||
const bVolume = bSize.x * bSize.y * bSize.z;
|
const bVolume = bSize.x * bSize.y * bSize.z;
|
||||||
@@ -158,10 +164,16 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
const bTurretModelDefinition =
|
const bTurretModelDefinition =
|
||||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||||
const aSize = normalizeBoundingBox(
|
const aSize = normalizeBoundingBox(
|
||||||
tankBoundingBox(aTankModelDefinition, aTurretModelDefinition),
|
unionBoundingBox(
|
||||||
|
aTankModelDefinition.bounding_box!,
|
||||||
|
aTurretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const bSize = normalizeBoundingBox(
|
const bSize = normalizeBoundingBox(
|
||||||
tankBoundingBox(bTankModelDefinition, bTurretModelDefinition),
|
unionBoundingBox(
|
||||||
|
bTankModelDefinition.bounding_box!,
|
||||||
|
bTurretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const aLength = Math.max(aSize.x, aSize.y, aSize.z);
|
const aLength = Math.max(aSize.x, aSize.y, aSize.z);
|
||||||
const bLength = Math.max(bSize.x, bSize.y, bSize.z);
|
const bLength = Math.max(bSize.x, bSize.y, bSize.z);
|
||||||
|
|||||||
-8
@@ -24,14 +24,6 @@ export function Survivability({ stats }: StatsAcceptorProps) {
|
|||||||
decimals={0}
|
decimals={0}
|
||||||
value={(stats) => stats.fireChance * 100}
|
value={(stats) => stats.fireChance * 100}
|
||||||
/>
|
/>
|
||||||
{stats.trackRepairTime !== undefined && (
|
|
||||||
<InfoWithDelta
|
|
||||||
value="trackRepairTime"
|
|
||||||
stats={stats}
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
decimals={2}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<InfoWithDelta value="viewRange" stats={stats} decimals={0} />
|
<InfoWithDelta value="viewRange" stats={stats} decimals={0} />
|
||||||
<ViewRangeVisualizer stats={stats} />
|
<ViewRangeVisualizer stats={stats} />
|
||||||
<Info
|
<Info
|
||||||
|
|||||||
+2
-2
@@ -31,11 +31,11 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
position="absolute"
|
position="absolute"
|
||||||
right="0"
|
right={display === TankopediaDisplay.StaticArmor ? "0" : "-6rem"}
|
||||||
top="50%"
|
top="50%"
|
||||||
mr="4"
|
mr="4"
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${display === TankopediaDisplay.StaticArmor ? "0" : "calc(100% + var(--space-4))"}, -50%)`,
|
transform: "translateY(-50%)",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
transitionDuration: "200ms",
|
transitionDuration: "200ms",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { alias } from "@blitzkit/core";
|
|
||||||
|
|
||||||
const EXTERNAL_IMAGE = /^https?:\/\//;
|
|
||||||
const MARKDOWN_IMAGES = [
|
|
||||||
/!\[[^\]]*\]\((\S+?)(?:\s+"[^"]*")?\)/g,
|
|
||||||
/<img[^>]*\ssrc=["']([^"']+)["']/g,
|
|
||||||
];
|
|
||||||
|
|
||||||
export function isExternalImage(src: string) {
|
|
||||||
return EXTERNAL_IMAGE.test(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function docImageId(url: string) {
|
|
||||||
let hash = 0x811c9dc5;
|
|
||||||
|
|
||||||
for (let index = 0; index < url.length; index++) {
|
|
||||||
hash ^= url.charCodeAt(index);
|
|
||||||
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash.toString(16).padStart(8, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function docImageUrl(url: string) {
|
|
||||||
return alias("api", `/docs/images/${docImageId(url)}.webp`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function externalDocImages(markdown: string) {
|
|
||||||
return MARKDOWN_IMAGES.flatMap((pattern) =>
|
|
||||||
Array.from(markdown.matchAll(pattern), (match) => match[1]),
|
|
||||||
).filter(isExternalImage);
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
resolvePenetrationCoefficient,
|
resolvePenetrationCoefficient,
|
||||||
ShellType,
|
ShellType,
|
||||||
sum,
|
sum,
|
||||||
tankBoundingBox,
|
|
||||||
TankClass,
|
TankClass,
|
||||||
|
unionBoundingBox,
|
||||||
type EngineDefinition,
|
type EngineDefinition,
|
||||||
type EquipmentDefinitions,
|
type EquipmentDefinitions,
|
||||||
type GunDefinition,
|
type GunDefinition,
|
||||||
@@ -119,7 +119,6 @@ export function tankCharacteristics(
|
|||||||
const hasImprovedVerticalStabilizer = equipment(122);
|
const hasImprovedVerticalStabilizer = equipment(122);
|
||||||
const hasDownImprovedVerticalStabilizer = equipment(124);
|
const hasDownImprovedVerticalStabilizer = equipment(124);
|
||||||
const hasImprovedSuspension = equipment(123);
|
const hasImprovedSuspension = equipment(123);
|
||||||
const hasToolbox = equipment(113);
|
|
||||||
|
|
||||||
const hasTungsten = consumable(45);
|
const hasTungsten = consumable(45);
|
||||||
const hasAdrenaline = consumable(18);
|
const hasAdrenaline = consumable(18);
|
||||||
@@ -336,7 +335,10 @@ export function tankCharacteristics(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const size = normalizeBoundingBox(
|
const size = normalizeBoundingBox(
|
||||||
tankBoundingBox(tankModelDefinition, turretModelDefinition),
|
unionBoundingBox(
|
||||||
|
tankModelDefinition.bounding_box!,
|
||||||
|
turretModelDefinition.bounding_box!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const weightKg =
|
const weightKg =
|
||||||
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
|
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
|
||||||
@@ -516,12 +518,6 @@ export function tankCharacteristics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fireChance = engine.fire_chance * fireChanceCoefficient;
|
const fireChance = engine.fire_chance * fireChanceCoefficient;
|
||||||
const trackRepairTime = track.repair_time
|
|
||||||
? track.repair_time /
|
|
||||||
((hasToolbox ? 1.2 : 1) *
|
|
||||||
(1 + (crewSkills.repair ?? 0) * 0.04) *
|
|
||||||
(hasProtectiveKit ? 1.1 : 1))
|
|
||||||
: undefined;
|
|
||||||
const viewRange = turret.view_range * viewRangeCoefficient;
|
const viewRange = turret.view_range * viewRangeCoefficient;
|
||||||
const camouflageStill = tank.camouflage_still * camouflageCoefficientStill;
|
const camouflageStill = tank.camouflage_still * camouflageCoefficientStill;
|
||||||
const camouflageMoving =
|
const camouflageMoving =
|
||||||
@@ -606,7 +602,6 @@ export function tankCharacteristics(
|
|||||||
hullTraverseSoftTerrain,
|
hullTraverseSoftTerrain,
|
||||||
health,
|
health,
|
||||||
fireChance,
|
fireChance,
|
||||||
trackRepairTime,
|
|
||||||
viewRange,
|
viewRange,
|
||||||
camouflageStill,
|
camouflageStill,
|
||||||
camouflageMoving,
|
camouflageMoving,
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import type { APIContext, GetStaticPathsItem } from "astro";
|
|
||||||
import sharp from "sharp";
|
|
||||||
import {
|
|
||||||
docImageId,
|
|
||||||
externalDocImages,
|
|
||||||
} from "../../../../core/blitzkit/docImages";
|
|
||||||
import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths";
|
|
||||||
import { getStaticPaths as _getStaticPaths } from "../../_index";
|
|
||||||
|
|
||||||
export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
|
|
||||||
const documents = import.meta.glob<string>(
|
|
||||||
"../../../../../../../docs/**/*.md",
|
|
||||||
{ eager: true, query: "?raw", import: "default" },
|
|
||||||
);
|
|
||||||
const urls = new Set(Object.values(documents).flatMap(externalDocImages));
|
|
||||||
|
|
||||||
return Array.from(
|
|
||||||
urls,
|
|
||||||
(url) =>
|
|
||||||
({
|
|
||||||
params: { id: docImageId(url) },
|
|
||||||
props: { url },
|
|
||||||
}) satisfies GetStaticPathsItem,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function GET({ props }: APIContext<{ url: string }>) {
|
|
||||||
const response = await fetch(props.url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch ${props.url}: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = await sharp(Buffer.from(await response.arrayBuffer()))
|
|
||||||
.webp()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
return new Response(new Uint8Array(image));
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import sharp from "sharp";
|
|
||||||
import type { APIContext, GetStaticPathsItem } from "astro";
|
import type { APIContext, GetStaticPathsItem } from "astro";
|
||||||
import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths";
|
import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths";
|
||||||
import { vfs } from "../../../../core/blitzkit/vfs";
|
import { vfs } from "../../../../core/blitzkit/vfs";
|
||||||
@@ -30,10 +29,7 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function GET({ props }: APIContext<{ path: string }>) {
|
export async function GET({ props }: APIContext<{ path: string }>) {
|
||||||
const bytes = await sharp(new Uint8Array(await vfs.file(props.path)))
|
const bytes = new Uint8Array(await vfs.file(props.path));
|
||||||
.trim({ background: { r: 0, g: 0, b: 0, alpha: 0 }, threshold: 1 })
|
|
||||||
.webp()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
return new Response(new Uint8Array(bytes));
|
return new Response(bytes);
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
parseHierarchies,
|
|
||||||
Sc2ReadStream,
|
Sc2ReadStream,
|
||||||
ScgReadStream,
|
ScgReadStream,
|
||||||
toUniqueId,
|
toUniqueId,
|
||||||
VertexAttribute,
|
VertexAttribute,
|
||||||
vertexAttributeVectorSizes,
|
vertexAttributeVectorSizes,
|
||||||
|
type Hierarchy,
|
||||||
type VehicleDefinitionList,
|
type VehicleDefinitionList,
|
||||||
} from "@blitzkit/core";
|
} from "@blitzkit/core";
|
||||||
import type { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
|
import type { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
|
||||||
import { Accessor, Document, Node, NodeIO, Scene } from "@gltf-transform/core";
|
import { Accessor, Document, Node, NodeIO, Scene } from "@gltf-transform/core";
|
||||||
import type { APIContext } from "astro";
|
import type { APIContext } from "astro";
|
||||||
|
import { times } from "lodash-es";
|
||||||
import { vfs } from "../../../../core/blitzkit/vfs";
|
import { vfs } from "../../../../core/blitzkit/vfs";
|
||||||
|
|
||||||
export { getStaticPaths } from "./_index";
|
export { getStaticPaths } from "./_index";
|
||||||
@@ -55,10 +56,13 @@ async function extractArmor(vfs: AbstractVFS, fileName: string) {
|
|||||||
const scene = document.createScene();
|
const scene = document.createScene();
|
||||||
const buffer = document.createBuffer();
|
const buffer = document.createBuffer();
|
||||||
|
|
||||||
parseHierarchies<Scene | Node>(
|
function parseHierarchies(hierarchies: Hierarchy[], parent: Scene | Node) {
|
||||||
sc2["#hierarchy"],
|
hierarchies.forEach((hierarchy) => {
|
||||||
scene,
|
const components = times(
|
||||||
(hierarchy, components, parent) => {
|
hierarchy.components.count,
|
||||||
|
(index) => hierarchy.components[index.toString().padStart(4, "0")],
|
||||||
|
);
|
||||||
|
|
||||||
components.forEach((component) => {
|
components.forEach((component) => {
|
||||||
switch (component["comp.typename"]) {
|
switch (component["comp.typename"]) {
|
||||||
case "TransformComponent":
|
case "TransformComponent":
|
||||||
@@ -159,9 +163,13 @@ async function extractArmor(vfs: AbstractVFS, fileName: string) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return parent;
|
if (hierarchy["#hierarchy"]) {
|
||||||
},
|
parseHierarchies(hierarchy["#hierarchy"], parent);
|
||||||
);
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseHierarchies(sc2["#hierarchy"], scene);
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
bufferToBigInt,
|
bufferToBigInt,
|
||||||
DdsReadStream,
|
DdsReadStream,
|
||||||
parseHierarchies,
|
|
||||||
PvrReadStream,
|
PvrReadStream,
|
||||||
Sc2ReadStream,
|
Sc2ReadStream,
|
||||||
ScgReadStream,
|
ScgReadStream,
|
||||||
toUniqueId,
|
toUniqueId,
|
||||||
VertexAttribute,
|
VertexAttribute,
|
||||||
type ConfigArchive,
|
type ConfigArchive,
|
||||||
|
type Hierarchy,
|
||||||
type TankParameters,
|
type TankParameters,
|
||||||
type Textures,
|
type Textures,
|
||||||
type VehicleDefinitionList,
|
type VehicleDefinitionList,
|
||||||
@@ -240,10 +240,8 @@ async function extractModel(vfs: AbstractVFS, path: string) {
|
|||||||
materials.set(id, resolvedMaterial);
|
materials.set(id, resolvedMaterial);
|
||||||
});
|
});
|
||||||
|
|
||||||
parseHierarchies<Scene | Node>(
|
function parseHierarchies(hierarchies: Hierarchy[], parent: Scene | Node) {
|
||||||
sc2["#hierarchy"],
|
hierarchies.forEach((hierarchy) => {
|
||||||
scene,
|
|
||||||
(hierarchy, components, parent) => {
|
|
||||||
if (
|
if (
|
||||||
omitMeshNames.start.some((omit) => hierarchy.name.startsWith(omit)) ||
|
omitMeshNames.start.some((omit) => hierarchy.name.startsWith(omit)) ||
|
||||||
omitMeshNames.end.some((omit) => hierarchy.name.endsWith(omit))
|
omitMeshNames.end.some((omit) => hierarchy.name.endsWith(omit))
|
||||||
@@ -251,6 +249,10 @@ async function extractModel(vfs: AbstractVFS, path: string) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const node = document.createNode(hierarchy.name);
|
const node = document.createNode(hierarchy.name);
|
||||||
|
const components = times(
|
||||||
|
hierarchy.components.count,
|
||||||
|
(index) => hierarchy.components[index.toString().padStart(4, "0")],
|
||||||
|
);
|
||||||
|
|
||||||
components.forEach((component) => {
|
components.forEach((component) => {
|
||||||
switch (component["comp.typename"]) {
|
switch (component["comp.typename"]) {
|
||||||
@@ -359,11 +361,15 @@ async function extractModel(vfs: AbstractVFS, path: string) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
parent.addChild(node);
|
if (hierarchy["#hierarchy"]) {
|
||||||
|
parseHierarchies(hierarchy["#hierarchy"], node);
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
parent.addChild(node);
|
||||||
},
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
|
parseHierarchies(sc2["#hierarchy"], scene);
|
||||||
|
|
||||||
scene.addChild(document.createNode("test"));
|
scene.addChild(document.createNode("test"));
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ function ModuleChooser({
|
|||||||
<Dialog.Trigger>
|
<Dialog.Trigger>
|
||||||
<Button
|
<Button
|
||||||
variant="surface"
|
variant="surface"
|
||||||
size={{ initial: "2", xs: "3", md: "4" }}
|
size={{ initial: "3", md: "4" }}
|
||||||
style={{
|
style={{
|
||||||
overflow: "clip",
|
overflow: "clip",
|
||||||
borderTopRightRadius: subSelector ? 0 : undefined,
|
borderTopRightRadius: subSelector ? 0 : undefined,
|
||||||
@@ -137,7 +137,7 @@ function ModuleChooser({
|
|||||||
<Popover.Trigger>
|
<Popover.Trigger>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="surface"
|
variant="surface"
|
||||||
size={{ initial: "2", xs: "3", md: "4" }}
|
size={{ initial: "3", md: "4" }}
|
||||||
style={{
|
style={{
|
||||||
overflow: "clip",
|
overflow: "clip",
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
@@ -204,7 +204,7 @@ function ModuleChooser({
|
|||||||
|
|
||||||
function ModuleSeparator() {
|
function ModuleSeparator() {
|
||||||
return (
|
return (
|
||||||
<Text size={{ initial: "3", xs: "5" }} color="gray" trim="end">
|
<Text size="5" color="gray" trim="end">
|
||||||
<CaretRightIcon width="1em" height="1em" />
|
<CaretRightIcon width="1em" height="1em" />
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
@@ -260,7 +260,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex gap={{ initial: "1", xs: "2" }} align="center">
|
<Flex gap="2" align="center">
|
||||||
<ModuleChooser
|
<ModuleChooser
|
||||||
skeleton={skeleton}
|
skeleton={skeleton}
|
||||||
tank={hullTankId.id}
|
tank={hullTankId.id}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export type Shot = {
|
|||||||
containsGaps: boolean;
|
containsGaps: boolean;
|
||||||
damage: number;
|
damage: number;
|
||||||
splashRadius?: number;
|
splashRadius?: number;
|
||||||
penetrationChance?: number;
|
|
||||||
|
|
||||||
in: {
|
in: {
|
||||||
surfaceNormal: Vector3;
|
surfaceNormal: Vector3;
|
||||||
|
|||||||
Reference in new issue
Block a user