diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index aec440c67..cc424ff18 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -12,6 +12,7 @@ jobs: permissions: actions: read contents: read + pull-requests: write security-events: write steps: @@ -33,8 +34,30 @@ jobs: run: ./gradlew build - name: Lint + id: lint + continue-on-error: true + run: ./gradlew lint + + - name: Count lint violations + id: count_violations + if: steps.lint.outcome == 'failure' run: | - ./gradlew lint || echo "::warning::Checkstyle found violations. See the checkstyle-report artifact for details." + count=$(grep -r '> $GITHUB_OUTPUT + + - name: Comment on PR if lint failed + if: steps.lint.outcome == 'failure' && github.event_name == 'pull_request' + uses: actions/github-script@v6 + with: + script: | + const count = ${{ steps.count_violations.outputs.count }}; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `:warning: **Checkstyle found ${count} style violation${count === 1 ? '' : 's'}.** Download the **checkstyle-report** artifact from the [Actions run](${runUrl}) for details.` + }) - name: Determine artifact name id: find_artifact diff --git a/.gitignore b/.gitignore index 784e34ce4..7a9c56279 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ steam_app_id.txt /Tests /src/main/resources/hash.txt libs.tar.gz +chars +.claude +.antigravitycli \ No newline at end of file diff --git a/slicr.py b/slicr.py new file mode 100644 index 000000000..c752e6363 --- /dev/null +++ b/slicr.py @@ -0,0 +1,69 @@ +from PIL import Image +import svgwrite +import fontforge + +SCALER = 24 +OFF = 10 + +chars = (" !\"#$%&'()*+,-./" + + "0123456789:;<=>?" + + "@ABCDEFGHIJKLMNO" + + "PQRSTUVWXYZ[\\]^_" + + "'abcdefghijklmno" + + "pqrstuvwxyz{|}~`" + + "âăîşţàçæèéêëïôœù" + + "úûüÿáíóñ¡¿äöå") + +def save_char(char_id, cell_width, cell_height, char_img): + dwg = svgwrite.Drawing(f"chars/{chars[char_id]}.svg", size=(f"{cell_width*SCALER}px", f"{(cell_height-(2*OFF))*SCALER}px"), profile='tiny') + for y in range(cell_height): + for x in range(cell_width): + r, g, b, a = char_img.getpixel((x, y)) + if a > 0: + hex_color = f'#{r:02x}{g:02x}{b:02x}' + dwg.add(dwg.rect((x*SCALER, (y-OFF)*SCALER), (SCALER, SCALER), fill=hex_color)) + + dwg.save() + +def slice_image(image_path, cell_width, cell_height): + """ + Slices a grid image into individual character PNGs. + """ + img = Image.open(image_path) + width, height = img.size + + char_id = 0 + for y in range(0, height, cell_height): + for x in range(0, width, cell_width): + box = (x, y, x + cell_width, y + cell_height) + char_img = img.crop(box) + if (char_id < len(chars)): + save_char(char_id, cell_width, cell_height, char_img) + char_id += 1 + +def fontify(): + slice_image("src/main/resources/fonts/default/font.png", 32, 64) + font = fontforge.font() + font.fontname = "Bullet" + font.fullname = "Bullet Sans" + font.familyname = "Bullet" + font.encoding = "UnicodeBMP" + font.copyright = "Copyright (c) 2025 Matei Budiu, Parth Iyer" + for char in chars: + glyph = font.createChar(ord(char)) + try: + if char == ' ': + glyph.width = 15 * SCALER + else: + glyph.importOutlines(f"chars/{char}.svg") + xmin, ymin, xmax, ymax = glyph.boundingBox() + glyph.width = int(xmax - xmin + 5*SCALER/2) + glyph.left_side_bearing = int(5*SCALER/2) + + except Exception as e: + print(f"Error processing {char}: {e}") + + font.autoWidth(0) + font.generate("src/main/resources/fonts/default/Bullet.ttf") + +fontify() diff --git a/src/main/java/lwjglwindow/LWJGLWindow.java b/src/main/java/lwjglwindow/LWJGLWindow.java index 1664f000f..ea3656029 100644 --- a/src/main/java/lwjglwindow/LWJGLWindow.java +++ b/src/main/java/lwjglwindow/LWJGLWindow.java @@ -3,7 +3,6 @@ import basewindow.*; import basewindow.transformation.Matrix4; import basewindow.transformation.Transformation; -import tanks.Game; import de.matthiasmann.twl.utils.PNGDecoder; import de.matthiasmann.twl.utils.PNGDecoder.Format; @@ -129,42 +128,10 @@ public void run() protected void init() { - this.fontRenderer = new FontRenderer(this, "/fonts/default/font.png"); - - // Load zh cn font - try - { - int count = 1; - while (true) - { - try (InputStream zhCnFontInputStream = LWJGLWindow.class.getClassLoader().getResourceAsStream("fonts/zh_cn/font_zh_cn_" + count + ".png"); - InputStream zhCnTxtInputStream = LWJGLWindow.class.getClassLoader().getResourceAsStream("fonts/zh_cn/font_zh_cn_" + count + ".txt")) - { - if (zhCnFontInputStream == null) break; - if (zhCnTxtInputStream == null) - { - System.err.println("Failed to load zh cn font " + count); - continue; - } - Scanner scanner = new Scanner(Objects.requireNonNull(zhCnTxtInputStream), StandardCharsets.UTF_8.name()); - StringBuilder sb = new StringBuilder(); - while (scanner.hasNextLine()) - { - sb.append(scanner.nextLine()); - } - String chinese_chars = sb.toString(); - int[] chinese_chars_sizes = new int[chinese_chars.length()]; - Arrays.fill(chinese_chars_sizes, 8); - this.fontRenderer.addFont("/fonts/zh_cn/font_zh_cn_" + count + ".png", chinese_chars, chinese_chars_sizes); - count++; - } - } - } - catch (IOException e) - { - e.printStackTrace(Game.logger); - e.printStackTrace(); - } + TruetypeFontRenderer ttf = new TruetypeFontRenderer(this, "/fonts/default/Bullet.ttf", 128, true, 1.4, 0.3); + ttf.addFontsFromDirectory(System.getProperty("user.home") + "/.tanks/fonts", 128, false, 1.4, 0.3); + ttf.addSystemFonts(128, false, 1.4, 0.3); + this.fontRenderer = ttf; GLFWErrorCallback.createPrint(System.err).set(); @@ -1410,4 +1377,4 @@ public void setForceModelGlow(boolean glow) { this.forceModelGlow = glow; } -} +} \ No newline at end of file diff --git a/src/main/java/lwjglwindow/TruetypeFontRenderer.java b/src/main/java/lwjglwindow/TruetypeFontRenderer.java new file mode 100644 index 000000000..08b4f5310 --- /dev/null +++ b/src/main/java/lwjglwindow/TruetypeFontRenderer.java @@ -0,0 +1,1138 @@ +package lwjglwindow; + +import basewindow.BaseFontRenderer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.stb.STBTTFontinfo; +import org.lwjgl.system.MemoryStack; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.stb.STBTruetype.*; + +public class TruetypeFontRenderer extends BaseFontRenderer +{ + public static class TtfFontInfo + { + public final STBTTFontinfo stbInfo; + public final ByteBuffer ttfBuffer; + public final float fontScale; + public final int ascent; + public final int bakeHeight; + public final boolean pixelPerfect; + public final double sizeScale; + public final double yOffset; + public final java.awt.Font awtFont; + public final double awtToStbScaleRatio; + + private final Map glyphTextures = new HashMap<>(); + private final Map glyphMetrics = new HashMap<>(); + + public TtfFontInfo(ByteBuffer buffer, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + this(buffer, 0, bakeHeight, pixelPerfect, sizeScale, yOffset); + } + + /** + * @param fontOffset Byte offset of this font's table directory within {@code buffer}: 0 for a + * standalone .ttf, or the {@code stbtt_GetFontOffsetForIndex} value for a + * member of a .ttc collection (where many fonts share one buffer). + */ + public TtfFontInfo(ByteBuffer buffer, int fontOffset, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + this.ttfBuffer = buffer; + this.stbInfo = STBTTFontinfo.create(); + this.bakeHeight = bakeHeight; + this.pixelPerfect = pixelPerfect; + this.sizeScale = sizeScale; + this.yOffset = yOffset; + + if (!stbtt_InitFont(stbInfo, buffer, fontOffset)) + throw new RuntimeException("Failed to initialize STB truetype font"); + + this.fontScale = stbtt_ScaleForPixelHeight(stbInfo, bakeHeight); + + try (MemoryStack stack = MemoryStack.stackPush()) + { + IntBuffer a = stack.mallocInt(1); + IntBuffer d = stack.mallocInt(1); + IntBuffer lg = stack.mallocInt(1); + stbtt_GetFontVMetrics(stbInfo, a, d, lg); + this.ascent = a.get(0); + } + + java.awt.Font rawFont = null; + try + { + byte[] bytes = new byte[buffer.remaining()]; + int originalPos = buffer.position(); + buffer.get(bytes); + buffer.position(originalPos); + + rawFont = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, new java.io.ByteArrayInputStream(bytes)); + } + catch (Exception e) + { + System.err.println("TruetypeFontRenderer: failed to load AWT Font: " + e.getMessage()); + } + this.awtFont = (rawFont != null) ? rawFont.deriveFont((float) bakeHeight) : null; + + double ratio = 1.0; + if (this.awtFont != null) + { + char calChar = 'a'; + if (!supportsCodepoint(calChar)) + { + for (int c = 32; c < 65536; c++) + { + if (supportsCodepoint(c)) + { + calChar = (char) c; + break; + } + } + } + + try + { + java.awt.font.FontRenderContext frc = new java.awt.font.FontRenderContext(null, true, true); + java.awt.font.GlyphVector gv = this.awtFont.layoutGlyphVector(frc, new char[]{calChar}, 0, 1, java.awt.Font.LAYOUT_LEFT_TO_RIGHT); + double awtAdvance = gv.getGlyphPosition(1).getX(); + int[] stbMetrics = getGlyphMetrics(stbtt_FindGlyphIndex(stbInfo, calChar)); + double stbAdvance = stbMetrics[0] * this.fontScale; + if (awtAdvance > 0 && stbAdvance > 0) + { + ratio = stbAdvance / awtAdvance; + } + } + catch (Exception e) + { + // Keep ratio = 1.0 + } + } + this.awtToStbScaleRatio = ratio; + } + + public boolean supportsCodepoint(int codepoint) + { + return stbtt_FindGlyphIndex(stbInfo, codepoint) != 0; + } + + public int[] getMetrics(int codepoint) + { + return getGlyphMetrics(stbtt_FindGlyphIndex(stbInfo, codepoint)); + } + + public int[] getGlyphMetrics(int glyphId) + { + if (glyphMetrics.containsKey(glyphId)) + return glyphMetrics.get(glyphId); + + try (MemoryStack stack = MemoryStack.stackPush()) + { + IntBuffer adv = stack.mallocInt(1); + IntBuffer lsb = stack.mallocInt(1); + stbtt_GetGlyphHMetrics(stbInfo, glyphId, adv, lsb); + + IntBuffer x0 = stack.mallocInt(1); + IntBuffer y0 = stack.mallocInt(1); + IntBuffer x1 = stack.mallocInt(1); + IntBuffer y1 = stack.mallocInt(1); + stbtt_GetGlyphBitmapBox(stbInfo, glyphId, fontScale, fontScale, x0, y0, x1, y1); + + int[] m = new int[]{adv.get(0), x1.get(0) - x0.get(0), y1.get(0) - y0.get(0), x0.get(0), y0.get(0)}; + glyphMetrics.put(glyphId, m); + return m; + } + } + + public int getOrCreateTexture(int codepoint) + { + return getOrCreateGlyphTexture(stbtt_FindGlyphIndex(stbInfo, codepoint)); + } + + public int getOrCreateGlyphTexture(int glyphId) + { + if (glyphTextures.containsKey(glyphId)) + return glyphTextures.get(glyphId); + + try (MemoryStack stack = MemoryStack.stackPush()) + { + IntBuffer w = stack.mallocInt(1); + IntBuffer h = stack.mallocInt(1); + IntBuffer xoff = stack.mallocInt(1); + IntBuffer yoff = stack.mallocInt(1); + + ByteBuffer bitmap = stbtt_GetGlyphBitmap(stbInfo, fontScale, fontScale, glyphId, w, h, xoff, yoff); + + if (bitmap == null || w.get(0) == 0 || h.get(0) == 0) + { + glyphTextures.put(glyphId, 0); + return 0; + } + + // The project's fragment shader does `color * vertexColor`, where `color` is the + // sampled texel. GL_ALPHA textures return RGB=0 in GLSL, which would zero out + // the glyph color. Upload as RGBA with white RGB and STB's alpha so the shader + // multiplies the per-vertex color through unchanged. + int bw = w.get(0); + int bh = h.get(0); + ByteBuffer rgba = BufferUtils.createByteBuffer(bw * bh * 4); + for (int i = 0; i < bw * bh; i++) + { + int a = bitmap.get(i) & 0xFF; + if (pixelPerfect) + a = a >= 128 ? 0xFF : 0x00; + rgba.put((byte) 0xFF); + rgba.put((byte) 0xFF); + rgba.put((byte) 0xFF); + rgba.put((byte) a); + } + rgba.flip(); + + int filter = pixelPerfect ? GL_NEAREST : GL_LINEAR; + int texId = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, texId); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bw, bh, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + + stbtt_FreeBitmap(bitmap); + + int[] m = new int[]{getGlyphMetrics(glyphId)[0], w.get(0), h.get(0), xoff.get(0), yoff.get(0)}; + glyphMetrics.put(glyphId, m); + glyphTextures.put(glyphId, texId); + return texId; + } + } + } + + private final List fonts = new ArrayList<>(); + private final TtfFontInfo defaultFont; + private final LWJGLWindow lwjglWindow; + + public TruetypeFontRenderer(LWJGLWindow h, String ttfResourcePath) + { + this(h, ttfResourcePath, 64, false, 1.0, 0.0); + } + + /** + * @param bakeHeight Glyph rasterization resolution in pixels. Smaller = chunkier; larger = smoother. + * @param pixelPerfect If true, glyph alpha is thresholded to 0/255 and texture filtering uses NEAREST. + * @param sizeScale Glyph size multiplier. 1.0 = STB's natural size; >1 enlarges to better fill the cell. + * @param yOffset Vertical shift in cell-height units. +0.1 moves text down by sY * 3.2 screen pixels. + */ + public TruetypeFontRenderer(LWJGLWindow h, String ttfResourcePath, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + super(h); + this.lwjglWindow = h; + this.defaultFont = loadFont(ttfResourcePath, bakeHeight, pixelPerfect, sizeScale, yOffset); + this.fonts.add(defaultFont); + } + + private ByteBuffer readResource(String path) throws IOException + { + try (InputStream in = lwjglWindow.getResource(path)) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] chunk = new byte[8192]; + int n; + while ((n = in.read(chunk)) > 0) + out.write(chunk, 0, n); + byte[] bytes = out.toByteArray(); + ByteBuffer buffer = BufferUtils.createByteBuffer(bytes.length); + buffer.put(bytes).flip(); + return buffer; + } + } + + private TtfFontInfo loadFont(String path, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + try + { + return new TtfFontInfo(readResource(path), bakeHeight, pixelPerfect, sizeScale, yOffset); + } + catch (Exception e) + { + throw new RuntimeException("Failed to load truetype font: " + path, e); + } + } + + /** + * Adds a fallback font with custom tuning. Used when the primary font lacks a glyph. + * If the font fails to load (missing file, unsupported format, etc.) the failure is logged + * and the call is a no-op — a broken fallback should not kill the renderer. + */ + public void addFont(String ttfPath, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + try + { + fonts.add(loadFont(ttfPath, bakeHeight, pixelPerfect, sizeScale, yOffset)); + } + catch (Exception e) + { + System.err.println("TruetypeFontRenderer: failed to add fallback font '" + ttfPath + "': " + e.getMessage()); + if (e.getCause() != null) + System.err.println(" caused by: " + e.getCause()); + } + } + + /** {@code BaseFontRenderer} contract — for TrueType only the first arg matters; other args ignored. */ + @Override + public void addFont(String ttfPath, String chars, int[] charSizes) + { + addFont(ttfPath, 64, false, 1.0, 0.0); + } + + /** Reads an entire file on disk into a native {@link ByteBuffer} suitable for STB. */ + private static ByteBuffer readFile(String filePath) throws IOException + { + byte[] bytes = Files.readAllBytes(Paths.get(filePath)); + ByteBuffer buffer = BufferUtils.createByteBuffer(bytes.length); + buffer.put(bytes).flip(); + return buffer; + } + + /** + * Registers every font contained in {@code buffer} as a fallback, in order, all sharing the one + * buffer. A plain .ttf reports a single font; a .ttc collection reports several. Order matters: + * {@link #findFontForChar} returns the first font that has the glyph, so earlier-added fonts win. + * + * @return the number of fonts registered + */ + private int addFontsFromBuffer(ByteBuffer buffer, String label, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + int count = stbtt_GetNumberOfFonts(buffer); + if (count <= 0) + throw new RuntimeException("not a valid font file (stbtt_GetNumberOfFonts returned " + count + ")"); + + int loaded = 0; + int failed = 0; + for (int i = 0; i < count; i++) + { + int offset = stbtt_GetFontOffsetForIndex(buffer, i); + if (offset < 0) + continue; + + try + { + fonts.add(new TtfFontInfo(buffer, offset, bakeHeight, pixelPerfect, sizeScale, yOffset)); + loaded++; + } + catch (Exception e) + { + failed++; + } + } + + // STB rasterizes TrueType (glyf) and bare CFF outlines, but not CFF2 — the format used by + // variable OpenType/CFF fonts such as the system Noto Sans CJK packages. Those fail to init; + // report the likely cause once per file instead of once per sub-font. + if (failed > 0) + { + String reason = isOpenTypeCFF(buffer) ? "OpenType/CFF outlines (CFF2 variable fonts aren't supported by STB)" : "STB could not initialize them"; + System.err.println("TruetypeFontRenderer: skipped " + failed + " of " + count + " font(s) in '" + label + "' — " + reason); + } + + return loaded; + } + + /** True if the first font in {@code buffer} is OpenType/CFF-flavoured ('OTTO'), not TrueType. */ + private static boolean isOpenTypeCFF(ByteBuffer buffer) + { + int off = stbtt_GetFontOffsetForIndex(buffer, 0); + if (off < 0 || off + 4 > buffer.capacity()) + return false; + // 'OTTO' = 0x4F 0x54 0x54 0x4F + return buffer.get(off) == 0x4F && buffer.get(off + 1) == 0x54 && buffer.get(off + 2) == 0x54 && buffer.get(off + 3) == 0x4F; + } + + /** + * Adds every font in a file on disk (a .ttf, .otf, or multi-font .ttc) as a fallback. Intended + * for fonts outside the classpath — an OS system font, or a user-supplied file under + * {@code ~/.tanks/fonts}. A missing, unreadable, or invalid file is logged and skipped: a bad + * fallback must never kill the renderer. + * + * @return the number of fonts registered (0 on failure) + */ + public int addFontFile(String filePath, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + try + { + int loaded = addFontsFromBuffer(readFile(filePath), filePath, bakeHeight, pixelPerfect, sizeScale, yOffset); + if (loaded > 0) + System.out.println("TruetypeFontRenderer: loaded " + loaded + " font(s) from " + filePath); + return loaded; + } + catch (Exception e) + { + System.err.println("TruetypeFontRenderer: failed to load font file '" + filePath + "': " + e.getMessage()); + return 0; + } + } + + /** + * Adds every font file sitting directly inside {@code dirPath} (non-recursive) as a fallback: + * each .ttc, .ttf, and .otf, processed in case-insensitive filename order so load priority is + * stable across runs. This is where the downloaded Noto Sans collection in {@code ~/.tanks/fonts} + * is picked up, alongside any other font the user drops there. The directory is created if it + * doesn't exist (so there's a place to drop fonts and for the downloader to write to); an empty + * or uncreatable directory is a no-op. + */ + public void addFontsFromDirectory(String dirPath, int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + File dir = new File(dirPath); + if (!dir.isDirectory() && !dir.mkdirs()) + { + System.err.println("TruetypeFontRenderer: could not create font directory " + dirPath); + return; + } + + File[] files = dir.listFiles((d, name) -> + { + String n = name.toLowerCase(Locale.ROOT); + return n.endsWith(".ttc") || n.endsWith(".ttf") || n.endsWith(".otf"); + }); + + if (files == null) + return; + + Arrays.sort(files, Comparator.comparing(f -> f.getName().toLowerCase(Locale.ROOT))); + for (File f: files) + addFontFile(f.getAbsolutePath(), bakeHeight, pixelPerfect, sizeScale, yOffset); + } + + /** + * Adds the platform's system fonts as fallbacks — the tier between the bundled Bullet font and + * any downloaded fonts. Bullet already covers Latin, so the value here is breadth: CJK, Indic, + * Arabic, Hebrew, Thai and other scripts that no single UI font carries. We therefore register + * several broad-coverage system fonts and let {@link #findFontForChar} choose per glyph. + * + *

macOS and Windows keep their fonts in stable, well-known directories, so those are + * hardcoded. On Linux font locations vary by distro, so we ask fontconfig ({@code fc-match}) + * which file the system actually uses for each of a set of representative scripts; if fontconfig + * is unavailable we fall back to scanning the standard font directories. + */ + public void addSystemFonts(int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); + + // LinkedHashSet: keep discovery order but drop duplicates — one font often serves several + // scripts (e.g. a Noto CJK file covers zh/ja/ko), and it must not be loaded more than once. + Set paths = new LinkedHashSet<>(); + + if (os.contains("win")) + { + String windir = System.getenv("WINDIR"); + String root = (windir != null ? windir : "C:\\Windows") + "\\Fonts\\"; + addExisting(paths, + root + "segoeui.ttf", // Latin, Cyrillic, Greek, Arabic, Hebrew, ... + root + "msyh.ttc", // Microsoft YaHei — Simplified Chinese + root + "msjh.ttc", // Microsoft JhengHei — Traditional Chinese + root + "yugothm.ttc", // Yu Gothic — Japanese + root + "msgothic.ttc", // MS Gothic — Japanese (older systems) + root + "malgun.ttf", // Malgun Gothic — Korean + root + "Nirmala.ttf", // Nirmala UI — Devanagari and other Indic scripts + root + "tahoma.ttf"); // extra Arabic/Hebrew coverage + } + else if (os.contains("mac") || os.contains("darwin")) + { + addExisting(paths, + "/System/Library/Fonts/SFNS.ttf", // San Francisco — Latin et al. + "/System/Library/Fonts/SFNSText.ttf", + "/Library/Fonts/Arial Unicode.ttf", // very broad multi-script + "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "/System/Library/Fonts/PingFang.ttc", // CJK + "/System/Library/Fonts/Hiragino Sans GB.ttc", // CJK + "/System/Library/Fonts/AppleSDGothicNeo.ttc", // Korean + "/System/Library/Fonts/Kohinoor.ttc", // Devanagari + "/System/Library/Fonts/Supplemental/Devanagari Sangam MN.ttc", + "/System/Library/Fonts/Supplemental/Thonburi.ttc"); // Thai + } + else // Linux / other unix + { + // Linux resolves and loads its own fonts (fontconfig-driven, with STB-loadability checks). + addLinuxSystemFonts(bakeHeight, pixelPerfect, sizeScale, yOffset); + return; + } + + if (paths.isEmpty()) + System.err.println("TruetypeFontRenderer: no system fonts found for OS '" + os + "'"); + + for (String path: paths) + addFontFile(path, bakeHeight, pixelPerfect, sizeScale, yOffset); + } + + /** Adds each path that exists as a regular file to {@code out}, skipping the rest. */ + private static void addExisting(Set out, String... paths) + { + for (String p: paths) + if (new File(p).isFile()) + out.add(p); + } + + /** + * Resolves and loads broad-coverage Linux system fonts via fontconfig. For each representative + * language it takes fontconfig's best match ({@code fc-match}); if STB can't rasterize that file + * (e.g. a CFF2 font like the system Noto Sans CJK), it walks the fonts that actually cover the + * script ({@code fc-list}) and loads the first STB can use — typically a glyf alternative such as + * Droid Sans Fallback. If fontconfig is unavailable, or nothing usable is found, it falls back to + * scanning the standard font directories. + */ + private void addLinuxSystemFonts(int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + // (fontconfig language, representative BMP codepoint) for the major writing systems. We only + // pull in a font for a script when nothing loaded so far renders its sample character, and we + // verify a candidate actually contains that glyph: fontconfig's best match is often a CFF2 + // font STB can't use, and its coverage lists include near-misses. Picking by real coverage is + // what steps over the system's CFF2 Noto CJK to a glyf fallback (e.g. Droid Sans Fallback). + String[] langs = { "ru", "el", "zh", "ja", "ko", "hi", "bn", "ta", "te", "kn", "ml", "gu", "pa", "or", "si", "ar", "he", "th", "my", "km", "lo", "ka", "hy", "am" }; + int[] samples = + { + 0x0410, + 0x0391, + 0x4E00, + 0x3042, + 0xAC00, + 0x0905, + 0x0985, + 0x0B85, + 0x0C05, + 0x0C85, + 0x0D05, + 0x0A85, + 0x0A05, + 0x0B05, + 0x0D85, + 0x0627, + 0x05D0, + 0x0E01, + 0x1000, + 0x1780, + 0x0E81, + 0x10D0, + 0x0531, + 0x1200 + }; + + Set added = new HashSet<>(); // files contributing glyphs to the chain + Set rejected = new HashSet<>(); // files STB can't use at all (e.g. CFF2) + + for (int s = 0; s < langs.length; s++) + { + int codepoint = samples[s]; + if (anyFontSupports(codepoint)) + continue; // already covered by Bullet or a font loaded for an earlier script + + List candidates; + try + { + // fontconfig's best match first (highest quality), then every font claiming to cover + // the script. + List rawCandidates = new ArrayList<>(runFontconfig("fc-match", "-f", "%{file}\n", ":lang=" + langs[s])); + rawCandidates.addAll(runFontconfig("fc-list", ":lang=" + langs[s], "--format", "%{file}\n")); + + // Prioritize static fonts over variable fonts because Java 8's AWT shaping + // does not work with modern OpenType variable fonts (.vf / [wght]). + List staticFonts = new ArrayList<>(); + List variableFonts = new ArrayList<>(); + for (String c : rawCandidates) + { + String lower = c.toLowerCase(); + if (lower.contains("[wght") || lower.contains("-vf") || lower.contains("google-noto-vf") || lower.contains("variable")) + variableFonts.add(c); + else + staticFonts.add(c); + } + candidates = new ArrayList<>(); + candidates.addAll(staticFonts); + candidates.addAll(variableFonts); + } + catch (IOException notInstalled) + { + addFontsFromStandardDirs(bakeHeight, pixelPerfect, sizeScale, yOffset); + return; + } + + for (String file: candidates) + { + if (added.contains(file) || rejected.contains(file) || !new File(file).isFile()) + continue; + if (addFontFileCovering(file, codepoint, rejected, bakeHeight, pixelPerfect, sizeScale, yOffset)) + { + added.add(file); + break; + } + } + } + } + + /** True if any already-loaded font has a glyph for {@code codepoint}. */ + private boolean anyFontSupports(int codepoint) + { + for (TtfFontInfo font: fonts) + if (font.supportsCodepoint(codepoint)) + return true; + return false; + } + + /** + * Reads {@code filePath} and registers only the sub-fonts that STB can initialize and + * that contain a glyph for {@code requiredCodepoint}; returns true if at least one was added. + * Files STB can't use at all (e.g. CFF2) are recorded in {@code rejected} so other scripts skip + * them. This is what lets the resolver pass over the system's CFF2 Noto CJK to a glyf fallback. + */ + private boolean addFontFileCovering(String filePath, int requiredCodepoint, Set rejected, + int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + ByteBuffer buffer; + try + { + buffer = readFile(filePath); + } + catch (IOException e) + { + rejected.add(filePath); + return false; + } + + int count = stbtt_GetNumberOfFonts(buffer); + int initialized = 0; + int added = 0; + for (int i = 0; i < count; i++) + { + int offset = stbtt_GetFontOffsetForIndex(buffer, i); + if (offset < 0) + continue; + + TtfFontInfo info; + try + { + info = new TtfFontInfo(buffer, offset, bakeHeight, pixelPerfect, sizeScale, yOffset); + } + catch (Exception e) + { + continue; // STB couldn't initialize this sub-font (e.g. CFF2) + } + initialized++; + + if (info.supportsCodepoint(requiredCodepoint)) + { + fonts.add(info); + added++; + } + } + + if (initialized == 0) + { + rejected.add(filePath); + System.err.println("TruetypeFontRenderer: cannot use '" + filePath + "' — " + + (isOpenTypeCFF(buffer) ? "OpenType/CFF2 outlines unsupported by STB" : "no STB-loadable fonts")); + } + if (added > 0) + System.out.println("TruetypeFontRenderer: loaded system font " + filePath); + + return added > 0; + } + + /** Last-resort fallback when fontconfig is absent: load every font under the standard font dirs. */ + private void addFontsFromStandardDirs(int bakeHeight, boolean pixelPerfect, double sizeScale, double yOffset) + { + Set scanned = new LinkedHashSet<>(); + for (String dir: new String[] + { + System.getProperty("user.home") + "/.local/share/fonts", + System.getProperty("user.home") + "/.fonts", + "/usr/share/fonts", + "/usr/local/share/fonts" + } + ) + collectFontFiles(new File(dir), scanned, 0); + + for (String f: scanned) + addFontFile(f, bakeHeight, pixelPerfect, sizeScale, yOffset); + + if (scanned.isEmpty()) + System.err.println("TruetypeFontRenderer: no Linux system fonts found"); + } + + /** + * Runs a fontconfig command and returns its stdout as a list of non-blank, trimmed lines. + * Throws {@link IOException} if the binary isn't installed. + */ + private static List runFontconfig(String... command) throws IOException + { + Process p = new ProcessBuilder(command).start(); + + List lines = new ArrayList<>(); + try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) + { + String line; + while ((line = r.readLine()) != null) + { + line = line.trim(); + if (!line.isEmpty()) + lines.add(line); + } + } + + try + { + p.waitFor(3, java.util.concurrent.TimeUnit.SECONDS); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + p.destroy(); + + return lines; + } + + /** Recursively collects .ttf/.ttc/.otf files under {@code dir} (depth-bounded) into {@code out}. */ + private static void collectFontFiles(File dir, Set out, int depth) + { + if (depth > 6 || !dir.isDirectory()) + return; + + File[] entries = dir.listFiles(); + if (entries == null) + return; + + for (File f: entries) + { + if (f.isDirectory()) + collectFontFiles(f, out, depth + 1); + else + { + String n = f.getName().toLowerCase(Locale.ROOT); + if (n.endsWith(".ttf") || n.endsWith(".ttc") || n.endsWith(".otf")) + out.add(f.getAbsolutePath()); + } + } + } + + @Override + public boolean supportsChar(char c) + { + for (TtfFontInfo font: fonts) + { + if (font.supportsCodepoint(c)) + return true; + } + return false; + } + + private TtfFontInfo findFontForChar(char c) + { + for (TtfFontInfo font: fonts) + { + if (font.supportsCodepoint(c)) + return font; + } + return defaultFont; + } + + protected double drawChar(double x, double y, double z, double sX, double sY, char c, boolean depthtest) + { + if (lwjglWindow.drawingShadow) + return 0; + + int codepoint = c; + TtfFontInfo font = findFontForChar(c); + + int texId = font.getOrCreateTexture(codepoint); + int[] m = font.getMetrics(codepoint); + int advance = m[0]; + int bitmapW = m[1]; + int bitmapH = m[2]; + int xoff = m[3]; + int yoff = m[4]; + + double scaleX = sX * 32.0 * font.sizeScale / font.bakeHeight; + double scaleY = sY * 32.0 * font.sizeScale / font.bakeHeight; + + double baselineY = (y - sY * 16) + font.ascent * font.fontScale * scaleY + sY * 32 * font.yOffset; + double gx = x + xoff * scaleX; + double gy = baselineY + yoff * scaleY; + double gw = bitmapW * scaleX; + double gh = bitmapH * scaleY; + + if (texId != 0) + { + if (depthtest) + glEnable(GL_DEPTH_TEST); + + lwjglWindow.enableTexture(); + glEnable(GL_BLEND); + lwjglWindow.setTransparentBlendFunc(); + glDepthMask(false); + + glBindTexture(GL_TEXTURE_2D, texId); + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2d(0, 0); + glVertex3d(gx, gy, z); + glTexCoord2d(0, 1); + glVertex3d(gx, gy + gh, z); + glTexCoord2d(1, 1); + glVertex3d(gx + gw, gy + gh, z); + glTexCoord2d(1, 0); + glVertex3d(gx + gw, gy, z); + glEnd(); + + glDepthMask(true); + lwjglWindow.disableTexture(); + + if (depthtest) + glDisable(GL_DEPTH_TEST); + } + + return advance * font.fontScale * scaleX; + } + + @Override + public void drawString(double x, double y, double z, double sX, double sY, String s) + { + drawString(x, y, z, sX, sY, s, true); + } + + @Override + public void drawString(double x, double y, double z, double sX, double sY, String s, boolean depth) + { + if (depth) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + + double opacity = this.window.colorA; + double curX = x; + + double r0 = this.window.colorR; + double g0 = this.window.colorG; + double b0 = this.window.colorB; + double a0 = this.window.colorA; + + List runs = parseRuns(s); + for (ShapedRun run : runs) + { + if (run.isReset) + { + this.window.setColor(r0 * 255, g0 * 255, b0 * 255, a0 * 255); + } + else if (run.color != null) + { + this.window.setColor(run.color[0], run.color[1], run.color[2], run.color[3] * opacity); + } + else if (!run.text.isEmpty()) + { + List fontRuns = partitionByFont(run.text); + for (FontRun fontRun : fontRuns) + { + curX += drawShapedString(curX, y, z, sX, sY, fontRun.text, fontRun.font, depth); + } + } + } + + glDisable(GL_DEPTH_TEST); + } + + @Override + public void drawString(double x, double y, double sX, double sY, String s) + { + double opacity = this.window.colorA; + double curX = x; + + double r0 = this.window.colorR; + double g0 = this.window.colorG; + double b0 = this.window.colorB; + double a0 = this.window.colorA; + + List runs = parseRuns(s); + for (ShapedRun run : runs) + { + if (run.isReset) + { + this.window.setColor(r0 * 255, g0 * 255, b0 * 255, a0 * 255); + } + else if (run.color != null) + { + this.window.setColor(run.color[0], run.color[1], run.color[2], run.color[3] * opacity); + } + else if (!run.text.isEmpty()) + { + List fontRuns = partitionByFont(run.text); + for (FontRun fontRun : fontRuns) + { + curX += drawShapedString(curX, y, 0, sX, sY, fontRun.text, fontRun.font, false); + } + } + } + } + + @Override + public double getStringSizeX(double sX, String s) + { + double w = 0; + List runs = parseRuns(s); + for (ShapedRun run : runs) + { + if (!run.text.isEmpty()) + { + List fontRuns = partitionByFont(run.text); + for (FontRun fontRun : fontRuns) + { + w += getShapedStringSizeX(sX, fontRun.text, fontRun.font); + } + } + } + return w; + } + + private double getShapedStringSizeX(double sX, String text, TtfFontInfo font) + { + if (text.isEmpty()) + return 0; + + if (font.awtFont == null) + { + double w = 0; + for (int i = 0; i < text.length(); i++) + { + int[] m = font.getGlyphMetrics(stbtt_FindGlyphIndex(font.stbInfo, text.charAt(i))); + w += m[0] * font.fontScale * sX * 32.0 * font.sizeScale / font.bakeHeight; + } + return w; + } + + java.awt.font.FontRenderContext frc = new java.awt.font.FontRenderContext(null, true, true); + java.awt.font.GlyphVector gv = font.awtFont.layoutGlyphVector(frc, text.toCharArray(), 0, text.length(), java.awt.Font.LAYOUT_LEFT_TO_RIGHT); + double scaleX = sX * 32.0 * font.sizeScale / font.bakeHeight; + return gv.getGlyphPosition(gv.getNumGlyphs()).getX() * font.awtToStbScaleRatio * scaleX; + } + + @Override + public double getStringSizeY(double sY, String s) + { + return sY * 32; + } + + private static class ShapedRun + { + public final String text; + public final double[] color; // Null if no change, size 4 array if specified + public final boolean isReset; + + public ShapedRun(String text, double[] color, boolean isReset) + { + this.text = text; + this.color = color; + this.isReset = isReset; + } + } + + private static class FontRun + { + public final String text; + public final TtfFontInfo font; + + public FontRun(String text, TtfFontInfo font) + { + this.text = text; + this.font = font; + } + } + + private List parseRuns(String s) + { + List runs = new ArrayList<>(); + StringBuilder currentText = new StringBuilder(); + char[] c = s.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (c[i] == 'Â') + { + continue; + } + else if (c[i] == '§') + { + if (s.length() <= i + 1) + { + currentText.append(c[i]); + continue; + } + + if (c[i + 1] == 'r') + { + if (currentText.length() > 0) + { + runs.add(new ShapedRun(currentText.toString(), null, false)); + currentText.setLength(0); + } + runs.add(new ShapedRun("", null, true)); + i++; + continue; + } + + if (s.length() <= i + 12) + { + currentText.append(c[i]); + continue; + } + + try + { + int r = Integer.parseInt(c[i + 1] + "" + c[i + 2] + "" + c[i + 3]); + int g = Integer.parseInt(c[i + 4] + "" + c[i + 5] + "" + c[i + 6]); + int b = Integer.parseInt(c[i + 7] + "" + c[i + 8] + "" + c[i + 9]); + int a = Integer.parseInt(c[i + 10] + "" + c[i + 11] + "" + c[i + 12]); + + if (currentText.length() > 0) + { + runs.add(new ShapedRun(currentText.toString(), null, false)); + currentText.setLength(0); + } + runs.add(new ShapedRun("", new double[]{r, g, b, a}, false)); + i += 12; + } + catch (Exception e) + { + currentText.append(c[i]); + } + } + else + { + currentText.append(c[i]); + } + } + if (currentText.length() > 0) + { + runs.add(new ShapedRun(currentText.toString(), null, false)); + } + return runs; + } + + private List partitionByFont(String text) + { + List runs = new ArrayList<>(); + if (text.isEmpty()) + return runs; + + StringBuilder currentText = new StringBuilder(); + TtfFontInfo currentFont = findFontForChar(text.charAt(0)); + currentText.append(text.charAt(0)); + + for (int i = 1; i < text.length(); i++) + { + char c = text.charAt(i); + TtfFontInfo font = findFontForChar(c); + if (font == currentFont) + { + currentText.append(c); + } + else + { + runs.add(new FontRun(currentText.toString(), currentFont)); + currentText.setLength(0); + currentFont = font; + currentText.append(c); + } + } + if (currentText.length() > 0) + { + runs.add(new FontRun(currentText.toString(), currentFont)); + } + return runs; + } + + private double drawShapedString(double x, double y, double z, double sX, double sY, String text, TtfFontInfo font, boolean depthtest) + { + if (text.isEmpty()) + return 0; + + if (font.awtFont == null) + { + double curX = x; + for (int i = 0; i < text.length(); i++) + { + curX += drawChar(curX, y, z, sX, sY, text.charAt(i), depthtest); + } + return curX - x; + } + + java.awt.font.FontRenderContext frc = new java.awt.font.FontRenderContext(null, true, true); + java.awt.font.GlyphVector gv = font.awtFont.layoutGlyphVector(frc, text.toCharArray(), 0, text.length(), java.awt.Font.LAYOUT_LEFT_TO_RIGHT); + + int numGlyphs = gv.getNumGlyphs(); + double scaleX = sX * 32.0 * font.sizeScale / font.bakeHeight; + double scaleY = sY * 32.0 * font.sizeScale / font.bakeHeight; + double baselineY = (y - sY * 16) + font.ascent * font.fontScale * scaleY + sY * 32 * font.yOffset; + + if (lwjglWindow.drawingShadow) + { + return gv.getGlyphPosition(numGlyphs).getX() * font.awtToStbScaleRatio * scaleX; + } + + for (int i = 0; i < numGlyphs; i++) + { + int glyphId = gv.getGlyphCode(i); + if (glyphId < 0) + continue; + + java.awt.geom.Point2D pos = gv.getGlyphPosition(i); + + int texId = font.getOrCreateGlyphTexture(glyphId); + int[] m = font.getGlyphMetrics(glyphId); + int bitmapW = m[1]; + int bitmapH = m[2]; + int xoff = m[3]; + int yoff = m[4]; + + double gx = x + (pos.getX() * font.awtToStbScaleRatio + xoff) * scaleX; + double gy = baselineY + (pos.getY() * font.awtToStbScaleRatio + yoff) * scaleY; + double gw = bitmapW * scaleX; + double gh = bitmapH * scaleY; + + if (texId != 0) + { + if (depthtest) + glEnable(GL_DEPTH_TEST); + + lwjglWindow.enableTexture(); + glEnable(GL_BLEND); + lwjglWindow.setTransparentBlendFunc(); + glDepthMask(false); + + glBindTexture(GL_TEXTURE_2D, texId); + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2d(0, 0); + glVertex3d(gx, gy, z); + glTexCoord2d(0, 1); + glVertex3d(gx, gy + gh, z); + glTexCoord2d(1, 1); + glVertex3d(gx + gw, gy + gh, z); + glTexCoord2d(1, 0); + glVertex3d(gx + gw, gy, z); + glEnd(); + + glDepthMask(true); + lwjglWindow.disableTexture(); + + if (depthtest) + glDisable(GL_DEPTH_TEST); + } + } + + return gv.getGlyphPosition(numGlyphs).getX() * font.awtToStbScaleRatio * scaleX; + } +} diff --git a/src/main/java/tanks/gui/screen/ScreenLanguage.java b/src/main/java/tanks/gui/screen/ScreenLanguage.java index 9ed64757c..ac2e644a2 100644 --- a/src/main/java/tanks/gui/screen/ScreenLanguage.java +++ b/src/main/java/tanks/gui/screen/ScreenLanguage.java @@ -74,6 +74,10 @@ public ScreenLanguage() changeLanguage(new Translation("zhcn.lang")) )); + languages.buttons.add(4, new Button(0, 0, 350, 40, "हिन्दी", () -> + changeLanguage(new Translation("hi.lang")) + )); + languages.sortButtons(); } diff --git a/src/main/resources/fonts/default/Bullet.ttf b/src/main/resources/fonts/default/Bullet.ttf new file mode 100644 index 000000000..cee7bf964 Binary files /dev/null and b/src/main/resources/fonts/default/Bullet.ttf differ diff --git a/src/main/resources/fonts/zh_cn/OFL.txt b/src/main/resources/fonts/zh_cn/OFL.txt deleted file mode 100644 index 3ff0ccaba..000000000 --- a/src/main/resources/fonts/zh_cn/OFL.txt +++ /dev/null @@ -1,96 +0,0 @@ -Copyright 2014-2025 Adobe (http://www.adobe.com/), with Reserved Font -Name 'Source'. Source is a trademark of Adobe in the United States -and/or other countries. - -This Font Software is licensed under the SIL Open Font License, -Version 1.1. - -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font -creation efforts of academic and linguistic communities, and to -provide a free and open framework in which fonts may be shared and -improved in partnership with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply to -any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software -components as distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, -deleting, or substituting -- in part or in whole -- any of the -components of the Original Version, by changing formats or by porting -the Font Software to a new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, -modify, redistribute, and sell modified and unmodified copies of the -Font Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, in -Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the -corresponding Copyright Holder. This restriction only applies to the -primary font name as presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created using -the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_1.png b/src/main/resources/fonts/zh_cn/font_zh_cn_1.png deleted file mode 100644 index 056753656..000000000 Binary files a/src/main/resources/fonts/zh_cn/font_zh_cn_1.png and /dev/null differ diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_1.txt b/src/main/resources/fonts/zh_cn/font_zh_cn_1.txt deleted file mode 100644 index d3deda358..000000000 --- a/src/main/resources/fonts/zh_cn/font_zh_cn_1.txt +++ /dev/null @@ -1 +0,0 @@ -,。?!@#¥%……&*()-+~·《》【】、|‘”;:—、简体中文结构基于并添加了更多翻译游戏版本语言坦克十字军东征开始选项关退出帧率内存使用量单人择模式返回随机卡小我的教程时间确定性继续生成新重此到主菜编辑删除名称大背景颜色照明团队物品限制分钟秒将和数 \ No newline at end of file diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_2.png b/src/main/resources/fonts/zh_cn/font_zh_cn_2.png deleted file mode 100644 index 8ac4b2dd3..000000000 Binary files a/src/main/resources/fonts/zh_cn/font_zh_cn_2.png and /dev/null differ diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_2.txt b/src/main/resources/fonts/zh_cn/font_zh_cn_2.txt deleted file mode 100644 index 0c39018ed..000000000 --- a/src/main/resources/fonts/zh_cn/font_zh_cn_2.txt +++ /dev/null @@ -1 +0,0 @@ -设置为以禁直射光阴影红绿蓝宽度高盟友敌初硬币商店排列子弹地雷护盾从板普通火焰激反超级闪电冰冻喷器炮治疗束爆炸迷你黑暗保图标最堆叠尺寸价格引线长触发半径冷却活动伤害上一页下破坏方块是否第共后坐力型类效果速次命值提升额外个进行按顺序战斗看能坚持久创建玩自己学习 \ No newline at end of file diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_3.png b/src/main/resources/fonts/zh_cn/font_zh_cn_3.png deleted file mode 100644 index dcd90ade6..000000000 Binary files a/src/main/resources/fonts/zh_cn/font_zh_cn_3.png and /dev/null differ diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_3.txt b/src/main/resources/fonts/zh_cn/font_zh_cn_3.txt deleted file mode 100644 index c455fa265..000000000 --- a/src/main/resources/fonts/zh_cn/font_zh_cn_3.txt +++ /dev/null @@ -1 +0,0 @@ -如何可键盘控向移或左右这里鼠击摧毁所有获胜些住钮来瞄准空在屏幕同拥墙车放附近棕壁旁边它另靠者被会再试题心之前避包括因们不得过查及剩余利信息栏底部显示网络延迟组闭启户其他家化脱颖而允许义备就绪等待位默认作弊当无纠正较弱适合稳连接强聊天滤潜词汇没输入暂停切换缩 \ No newline at end of file diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_4.png b/src/main/resources/fonts/zh_cn/font_zh_cn_4.png deleted file mode 100644 index a57ef3588..000000000 Binary files a/src/main/resources/fonts/zh_cn/font_zh_cn_4.png and /dev/null differ diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_4.txt b/src/main/resources/fonts/zh_cn/font_zh_cn_4.txt deleted file mode 100644 index 3cc341d8c..000000000 --- a/src/main/resources/fonts/zh_cn/font_zh_cn_4.txt +++ /dev/null @@ -1 +0,0 @@ -隐藏全追踪热见槽取消对象播撤销做工具快操伍障碍造擦调整摄像头居属清形解绑目指针替华丽改变面响轨迹路某粒著百比垂步刷减少池耗也决致问三维视场角俯倾斜抗锯齿修复薄缘烁但代需要才注意声音乐载点统计详细据尝花费种固锁与助公平供太流畅验日志证私政策依赖库容功界频衡欢 \ No newline at end of file diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_5.png b/src/main/resources/fonts/zh_cn/font_zh_cn_5.png deleted file mode 100644 index 1bb64db5f..000000000 Binary files a/src/main/resources/fonts/zh_cn/font_zh_cn_5.png and /dev/null differ diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_5.txt b/src/main/resources/fonts/zh_cn/font_zh_cn_5.txt deleted file mode 100644 index 8f5209288..000000000 --- a/src/main/resources/fonts/zh_cn/font_zh_cn_5.txt +++ /dev/null @@ -1 +0,0 @@ -迎完吗练假原箭布智两够呈弧眩晕低支援短巨且法阻挡越增仿吹风导范围愤怒打冒险经典失去条途只丢草雪隧道育坑洼堵塞突灌木丛落裹闯箱紫圆顶蛇堡垒幽灵侵冬湖陷阱烤泥潭森林杠狂兄弟婴儿攻暴起享派每都相端口应该非转您址托管朋传想什么送已断好找蜂窝离毫把踢请访码社区现浏览 \ No newline at end of file diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_6.png b/src/main/resources/fonts/zh_cn/font_zh_cn_6.png deleted file mode 100644 index 0f6aa5c19..000000000 Binary files a/src/main/resources/fonts/zh_cn/font_zh_cn_6.png and /dev/null differ diff --git a/src/main/resources/fonts/zh_cn/font_zh_cn_6.txt b/src/main/resources/fonts/zh_cn/font_zh_cn_6.txt deleted file mode 100644 index 8510797fb..000000000 --- a/src/main/resources/fonts/zh_cn/font_zh_cn_6.txt +++ /dev/null @@ -1 +0,0 @@ -账期链纵杆跳那拖橙圈手拉双灰恭喜走测框沉浸杀总死亡鸟瞰哨环填写副坊邀败员错误未检常脑任搜索件理夹辆窗杂资料层询约辨画预倒剪粘贴旋魔棒截材质透佳纪录域处烈幅降撕裂摘滚配药覆盖绕根街逐若占警告止至差很运免顿立即城础虚协议旨年龄段台均宜尊犯感谢欣赏 \ No newline at end of file diff --git a/src/main/resources/translations/hi.lang b/src/main/resources/translations/hi.lang new file mode 100644 index 000000000..6fad50f19 --- /dev/null +++ b/src/main/resources/translations/hi.lang @@ -0,0 +1,794 @@ +हिन्दी +Languages=भाषाएँ +Tanks=टैंक्स +The Crusades=क्रूसेड्स +Play!=खेलें! +Options=सेटिंग्स +About=गेम के बारे में +Exit the game=गेम बंद करें +FPS=एफ पी एस +Memory used=उपयोग की गई मेमोरी +Singleplayer=सिंगल प्लेयर +Multiplayer=मल्टीप्लेयर +Select a game mode=खेल मोड चुनें +Version=संस्करण +Back=वापस +Select a singleplayer game mode=एकल खिलाड़ी मोड चुनें +Random level=रैंडम लेवल +Crusades=क्रूसेड्स +My levels=मेरे लेवल्स +Tutorial=ट्यूटोरियल +Level time:=लेवल का समय: +Deterministic mode=नियतात्मक मोड +Continue playing=खेलना जारी रखें +Generate a new level=नया लेवल तैयार करें +Restart this level=यह लेवल दोबारा शुरू करें +Quit to title=होम स्क्रीन पर जाएँ +Play=खेलें +Edit=संपादित करें +Delete Level=लेवल हटाएँ +Level name=लेवल का नाम +Level size=लेवल का आकार +Background colors=पृष्ठभूमि के रंग +Lighting=प्रकाश +Teams=टीमें +Items=आइटम्स +Time limit=समय सीमा +Minutes=मिनट +Seconds=सेकंड +Set minutes and seconds to 0 to disable the time limit=समय सीमा निष्क्रिय करने के लिए मिनट और सेकंड को 0 पर सेट करें +Direct light=सीधा प्रकाश +Shadow light=छाया प्रकाश +Red=लाल +Green=हरा +Blue=नीला +Red randomness=लाल रैंडमनेस +Green randomness=हरी रैंडमनेस +Blue randomness=नीली रैंडमनेस +Width=चौड़ाई +Height=ऊँचाई +New team=नई टीम +ally=साथी +enemy=दुश्मन +Starting coins=शुरुआती सिक्के +Shop=दुकान +Starting items=शुरुआती आइटम्स +Rearrange items=वस्तुएँ पुनः व्यवस्थित करें +Add item=आइटम जोड़ें +Bullet=गोली +Mine=माइन +Shield=ढाल +From template=टेम्पलेट से +Basic bullet=साधारण गोली +Basic mine=साधारण माइन +Fire bullet=आग की गोली +Laser=लेज़र +Bouncy fire bullet=उछलती हुई आग की गोली +Mega mine=मेगा माइन +Zap=ज़ैप +Shield=ढाल +Freezing bullet=जमाने वाली गोली +Flamethrower=ज्वाला फेंकने वाला +Mega bullet=मेगा गोली +Artillery shell=तोपख़ाने का गोला +Healing ray=चिकित्सा किरण +Explosive bullet=विस्फोटक गोली +Mini bullet=छोटी गोली +Dark fire bullet=काली आग की गोली +%d coins=%d सिक्के +Delete item=आइटम हटाएँ +Save to template=टेम्पलेट में सहेजें +Name=नाम +Icon=आइकन +Select icon=आइकन चुनें +Amount=मात्रा +Max stack size=अधिकतम स्टैक आकार +Price=मूल्य +Fuse length=फ्यूज़ की लंबाई +Triggered fuse=ट्रिगर किया गया फ्यूज़ +Explosion radius=विस्फोट त्रिज्या +Size=आकार +Cooldown=कूलडाउन +Max live mines=अधिकतम सक्रिय माइनें +Damage=क्षति +Previous page=पिछला पेज +Next page=अगला पेज +Destroys blocks=ब्लॉक नष्ट करता है +Yes=हाँ +No=नहीं +Ok=ठीक है +Page %d of %d=पेज %d में से %d +Recoil=रिकॉइल +Heavy=भारी +Type=प्रकार +Effect=प्रभाव +Speed=गति +Max live bullets=अधिकतम सक्रिय गोलियाँ +Bounces=उछाल +Health boost=स्वास्थ्य वृद्धि +Max extra health=अधिकतम अतिरिक्त स्वास्थ्य +Level options=लेवल ऑप्शंस +Generate a random level to play=खेलने के लिए एक रैंडम लेवल तैयार करें +Fight battles in an order, and see how long you can survive!=क्रम से लड़ाइयाँ लड़ें, और देखें कि आप कब तक जीवित रह सकते हैं! +Create and play your own levels!=अपने खुद के लेवल्स बनाएँ और खेलें! +Learn how to play Tanks!=टैंक्स खेलना सीखें! +You can control the blue tank using the keyboard.=आप कीबोर्ड का उपयोग करके नीले टैंक को नियंत्रित कर सकते हैं। +Move up: %d=ऊपर जाएँ: %d +Move up: %d or %d=ऊपर जाएँ: %d या %d +Move left: %d=बाएँ जाएँ: %d +Move left: %d or %d=बाएँ जाएँ: %d या %d +Move right: %d=दाएँ जाएँ: %d +Move right: %d or %d=दाएँ जाएँ: %d या %d +Move down: %d=नीचे जाएँ: %d +Move down: %d or %d=नीचे जाएँ: %d या %d +Move over here to continue=जारी रखने के लिए यहाँ आएँ +Press %d or %d to shoot toward your mouse!=माउस की दिशा में फायर करने के लिए %d या %d दबाएँ! +Press %d to shoot toward your mouse!=माउस की दिशा में फायर करने के लिए %d दबाएँ! +Destroy all enemy tanks to win!=जीतने के लिए सभी दुश्मन टैंकों को नष्ट करें! +Shoot these enemy tanks!=इन दुश्मन टैंकों पर फायर करें! +Press and hold . or Mouse button 3 to aim.=निशाना लगाने के लिए . या माउस बटन 3 दबाकर रखें। +Press Space or Mouse button 1 to shoot=फायर करने के लिए स्पेस या माउस बटन 1 दबाएँ +You can have up to 5 bullets on screen at one time.=स्क्रीन पर एक समय में अधिकतम 5 गोलियाँ हो सकती हैं। +Aim and bounce your bullets on a wall to destroy this tank!=इस टैंक को नष्ट करने के लिए निशाना लगाएँ और दीवार से अपनी गोलियों को उछालें! +Press Enter or Mouse button 2 to lay a mine!=माइन लगाने के लिए एंटर या माउस बटन 2 दबाएँ! +Mines can destroy nearby tanks and brown blocks.=माइनें पास के टैंकों और भूरे ब्लॉकों को नष्ट कर सकती हैं। +Place a mine next to this brown wall to destroy it!=इस भूरी दीवार को नष्ट करने के लिए उसके पास माइन रखें! +A mine will explode after 10 seconds, if another tank gets near, or if shot. =माइन 10 सेकंड बाद, या कोई अन्य टैंक पास आने पर, या उस पर गोली लगने पर फट जाती है। +You can have up to 2 mines on screen at a time.=स्क्रीन पर एक समय में अधिकतम 2 माइनें हो सकती हैं। +Stand back!=पीछे हटें! +Your mines can destroy you!=आपकी माइनें आपको नष्ट कर सकती हैं! +You were destroyed!=आप नष्ट हो गए! +Try again=फिर से प्रयास करें +Back to title=होम स्क्रीन पर जाएँ +Watch out! This brown tank will shoot at you!=सावधान! यह भूरा टैंक आप पर गोली चलाएगा! +Shoot it before it destroys you!=इससे पहले कि वह आपको नष्ट करे, उस पर फायर करें! +Avoid all bullets and mines, including your own, as they can destroy you.=सभी गोलियों और माइनों से बचें, अपनी सहित, क्योंकि वे आपको नष्ट कर सकती हैं। +If your tank is destroyed, you will have to start the level over!=यदि आपका टैंक नष्ट हो गया, तो आपको स्तर फिर से शुरू करना होगा! +You can see your health, available bullets and mines, and remaining tanks by pressing Shift!=शिफ्ट दबाकर आप अपना स्वास्थ्य, उपलब्ध गोलियाँ और माइनें, और शेष टैंक देख सकते हैं! +Victory!=जीत! +Info bar:=सूचना पट्टी: +Shows the following information at the bottom of the screen=स्क्रीन के निचले हिस्से में निम्नलिखित जानकारी दिखाता है +Game version=खेल संस्करण +Framerate=फ्रेमरेट +Network latency (if in a party)=नेटवर्क विलंबता (पार्टी में होने पर) +Memory usage=मेमोरी उपयोग +off=बंद +on=चालू +Multiplayer options=मल्टीप्लेयर विकल्प +Username=उपयोगकर्ता नाम +Pick a username that players will see in multiplayer=ऐसा उपयोगकर्ता नाम चुनें जो खिलाड़ी मल्टीप्लेयर में देखेंगे +Tank color=टैंक का रंग +Personalize your tank and stand out in multiplayer!=अपने टैंक को व्यक्तिगत बनाएँ और मल्टीप्लेयर में अलग दिखें! +Multiplayer tank color=मल्टीप्लेयर टैंक का रंग +Primary red=प्राथमिक लाल +Primary green=प्राथमिक हरा +Primary blue=प्राथमिक नीला +Secondary red=द्वितीयक लाल +Secondary green=द्वितीयक हरा +Secondary blue=द्वितीयक नीला +Second color:=दूसरा रंग: +Allows you to pick a custom secondary color=आपको एक कस्टम द्वितीयक रंग चुनने की अनुमति देता है +Party host options=पार्टी मेज़बान विकल्प +Options for parties you host=आपकी होस्ट की गई पार्टियों के विकल्प +Cooldown time=कूलडाउन समय +The wait time in seconds after all players are ready before the battle begins.=सभी खिलाड़ी तैयार होने के बाद लड़ाई शुरू होने से पहले प्रतीक्षा का समय (सेकंड में)। +Friendly fire:=मित्रवत फायर: +default=डिफ़ॉल्ट +Anticheat=धोखा-रोधी +When this option is enabled while hosting a party, other players' positions and velocities will be corrected if invalid.=पार्टी होस्ट करते समय यह विकल्प सक्षम होने पर, अमान्य पाए जाने पर अन्य खिलाड़ियों की स्थिति और गति को सही किया जाएगा। +Weaker settings work better with less-stable connections.=कमज़ोर सेटिंग्स कम-स्थिर कनेक्शन के साथ बेहतर काम करती हैं। +strong=मज़बूत +weak=कमज़ोर +Chat filter:=चैट फ़िल्टर: +Filters chat of potentially-inappropriate words=संभावित रूप से अनुचित शब्दों को चैट से फ़िल्टर करता है +Auto ready:=स्वतः तैयार: +When enabled, automatically presses the ready button if there is no shop=सक्षम होने पर, यदि कोई दुकान नहीं है तो स्वचालित रूप से तैयार बटन दबाता है +Input options=इनपुट विकल्प +Input options and controls=इनपुट विकल्प और नियंत्रण +Controls=नियंत्रण +None=कोई नहीं +Game controls=खेल नियंत्रण +Game=खेल +Pause=रोकें +Toggle zoom=ज़ूम चालू/बंद करें +Chat=चैट +Hide / show pause menu=पॉज़ मेनू छिपाएँ / दिखाएँ +Toggle fullscreen=फुलस्क्रीन चालू/बंद करें +Tank=टैंक +Tank controls=टैंक नियंत्रण +Move up=ऊपर जाएँ +Move down =नीचे जाएँ +Move left=बाएँ जाएँ +Move right=दाएँ जाएँ +Shoot bullet=गोली चलाएँ +Lay mine=माइन रखें +Trace aim=निशाना ट्रेस करें +Hotbar=हॉटबार +Toggle hotbar visibility=हॉटबार दृश्यता चालू/बंद करें +Item slot %d=आइटम स्लॉट %d +Deselect item slot=आइटम स्लॉट चयन रद्द करें +editor=संपादक +Editor controls=संपादक नियंत्रण +Editor menu=संपादक मेनू +Object menu=ऑब्जेक्ट मेनू +Play level=स्तर खेलें +Toggle on-screen buttons=ऑन-स्क्रीन बटन चालू/बंद करें +Undo=पूर्ववत करें +Redo=फिर से करें +Use tool=उपकरण उपयोग करें +Tool quick action=उपकरण त्वरित क्रिया +Tank team=टैंक टीम +Tank orientation=टैंक की दिशा +Obstacle height=बाधा की ऊँचाई +Obstacle group ID=बाधा समूह आईडी +Build=बनाएँ +Erase=मिटाएँ +Adjust camera=कैमरा समायोजित करें +Zoom in=ज़ूम इन करें +Zoom out=ज़ूम आउट करें +Re-center camera=कैमरा पुनः केंद्रित करें +Next object=अगला ऑब्जेक्ट +Previous object=पिछला ऑब्जेक्ट +Next object type=अगला ऑब्जेक्ट प्रकार +Previous object type=पिछला ऑब्जेक्ट प्रकार +Next object property=अगला ऑब्जेक्ट गुण +Previous object property=पिछला ऑब्जेक्ट गुण +Select=चुनें +Clear selection=चयन साफ़ करें +Toggle square selection=वर्गाकार चयन चालू/बंद करें +Toggle remove from selection=चयन से हटाना चालू/बंद करें +Reset=रीसेट +Unbind=अनबाइंड +Mouse target: =माउस लक्ष्य: +When enabled, your mouse pointer will be replaced by a target=सक्षम होने पर, आपके माउस पॉइंटर को एक लक्ष्य से बदल दिया जाएगा +Graphics options=ग्राफ़िक्स विकल्प +Terrain: =भूभाग: +fast=तेज़ +fancy=शानदार +Fancy terrain varies block and ground colors=शानदार भूभाग ब्लॉक और ज़मीन के रंगों में विविधता लाता है +May impact performance on larger levels=बड़े स्तरों पर प्रदर्शन को प्रभावित कर सकता है +Bullet trails: =गोली के निशान: +Bullet trails show the paths of bullets=गोली के निशान गोलियों का मार्ग दिखाते हैं +Fancy bullet trails enable extra particle effects for certain bullet types=शानदार गोली के निशान कुछ गोली प्रकारों के लिए अतिरिक्त पार्टिकल प्रभाव सक्षम करते हैं +Glow effects: =चमक प्रभाव: +Glow effects may significantly impact performance=चमक प्रभाव प्रदर्शन को महत्वपूर्ण रूप से प्रभावित कर सकते हैं +Particle effects: =कण प्रभाव: +Particle effects may significantly impact performance=कण प्रभाव प्रदर्शन को महत्वपूर्ण रूप से प्रभावित कर सकते हैं +Particle percentage=कण प्रतिशत +V-Sync:=V-Sync: +Limit framerate to your screen's refresh rate=फ्रेमरेट को आपकी स्क्रीन की रिफ्रेश दर तक सीमित करें +May decrease battery consumption=बैटरी की खपत कम कर सकता है +Also, might fix issues with inconsistent game speed=साथ ही, असंगत खेल गति की समस्याओं को ठीक कर सकता है +3D graphics: =3D ग्राफ़िक्स: +3D graphics may impact performance=3D ग्राफ़िक्स प्रदर्शन को प्रभावित कर सकते हैं +3D ground: =3D ज़मीन: +3D ground may impact performance in larger levels=3D ज़मीन बड़े स्तरों पर प्रदर्शन को प्रभावित कर सकती है +View:=दृश्य: +Changes the angle at which you view the game field=उस कोण को बदलता है जिस पर आप खेल मैदान देखते हैं +Bird's-eye view=पक्षी की दृष्टि से +Angled=कोणीय +Antialiasing:=एंटीएलियासिंग: +May fix flickering in thin edges at the cost of performance=प्रदर्शन की क़ीमत पर पतले किनारों की झिलमिलाहट को ठीक कर सकता है +Requires restarting the game to take effect=प्रभावी होने के लिए खेल को पुनः आरंभ करना आवश्यक है +Notice!=सूचना! +Antialiasing will be enabled the next time you start the game=अगली बार जब आप खेल शुरू करेंगे तो एंटीएलियासिंग सक्षम हो जाएगी +Antialiasing will be disabled the next time you start the game=अगली बार जब आप खेल शुरू करेंगे तो एंटीएलियासिंग अक्षम हो जाएगी +Fullscreen:=फुलस्क्रीन: +Can also be toggled at any time by pressing %d or %d=%d या %d दबाकर किसी भी समय भी चालू/बंद किया जा सकता है +Sound options=ध्वनि विकल्प +Sound volume=ध्वनि वॉल्यूम +Music volume=संगीत वॉल्यूम +Game options=खेल विकल्प +Autostart:=स्वतः आरंभ: +When enabled, level will start automatically 4 seconds after they are loaded (if the play button isn't clicked earlier)=सक्षम होने पर, स्तर लोड होने के 4 सेकंड बाद स्वचालित रूप से शुरू हो जाएगा (यदि खेलें बटन पहले नहीं दबाया गया) +Advanced stats:=उन्नत आँकड़े: +Shows more detailed statistics after a crusade ends.=क्रूसेड समाप्त होने के बाद अधिक विस्तृत आँकड़े दिखाता है। +Speedrunning options=स्पीडरनिंग विकल्प +Timer:=टाइमर: +When enabled, time spent in the current level attempt and crusade will be displayed=सक्षम होने पर, वर्तमान स्तर प्रयास और क्रूसेड में बिताया गया समय प्रदर्शित होगा +Deterministic:=नियतात्मक: +Deterministic mode changed the random number generation to be fixed based on a seed, and the game speed to be locked and independent of framerate.=नियतात्मक मोड यादृच्छिक संख्या जनरेशन को बीज पर आधारित निश्चित कर देता है, और खेल की गति को लॉक और फ्रेमरेट से स्वतंत्र कर देता है। +This is useful for fair speedruns but may provide for a less smooth experience=यह निष्पक्ष स्पीडरन के लिए उपयोगी है लेकिन कम सहज अनुभव दे सकता है +About=के बारे में +Changelogs=परिवर्तन लॉग +License=लाइसेंस +Privacy policy=गोपनीयता नीति +Library licenses=लाइब्रेरी लाइसेंस +What's new in %d:=%d में क्या नया है: +New features:=नई सुविधाएँ: +Levels:=स्तर: +Items:=आइटम्स: +User interfaces:=उपयोगकर्ता इंटरफ़ेस: +Graphics:=ग्राफ़िक्स: +Audio:=ऑडियो: +Options:=विकल्प: +More:=अधिक: +Balancing:=संतुलन: +Improvements:=सुधार: +Welcome to %d!=%d में आपका स्वागत है! +Time: %d=समय: %d +New level=नया स्तर +Level menu=स्तर मेनू +Up=ऊपर +Down=नीचे +Left=बाएँ +Right=दाएँ +Done=पूर्ण +none=कोई नहीं +Are you sure you want to delete the level?=क्या आप वाकई स्तर हटाना चाहते हैं? +Player=खिलाड़ी +Move the player=खिलाड़ी को हिलाएँ +Add multiple player spawn points=कई खिलाड़ी स्पॉन बिंदु जोड़ें +A dummy tank used to practice your aim=निशाने का अभ्यास करने के लिए डमी टैंक +A primitive stationary tank=एक आदिम स्थिर टैंक +A primitive mobile tank=एक आदिम चलने वाला टैंक +A tank which shoots fast rocket bullets=तेज़ रॉकेट गोलियाँ चलाने वाला टैंक +A tanks which lays mines=माइनें बिछाने वाला टैंक +A medium-speed smart tank=मध्यम गति का स्मार्ट टैंक +A stationary tank which shoots deadly lasers=घातक लेज़र चलाने वाला स्थिर टैंक +A deadly stationary tank which shoots rockets that bounce twice=दो बार उछलने वाले रॉकेट चलाने वाला घातक स्थिर टैंक +A smart, fast tank which can lay mines=माइनें बिछा सकने वाला स्मार्ट, तेज़ टैंक +A stationary tank which shoots stunning electricity which arcs between targets=लक्ष्यों के बीच चाप बनाने वाली चकित करने वाली बिजली चलाने वाला स्थिर टैंक +An invisible smart tank=एक अदृश्य स्मार्ट टैंक +A support tank which shoots freezing bullets that deal low damage=कम क्षति देने वाली जमाने वाली गोलियाँ चलाने वाला सहायक टैंक +A short-range tank which shoots fire=आग चलाने वाला लघु-दूरी टैंक +A tank which shoots huge bullets which bounce 3 times and can't be stopped=विशाल गोलियाँ चलाने वाला टैंक जो 3 बार उछलती हैं और रोकी नहीं जा सकतीं +A stationary tank which lobs bullets over walls=दीवारों के ऊपर गोलियाँ फेंकने वाला स्थिर टैंक +A tank which adds extra health to its allies and becomes explosive as a last stand=अपने मित्रों को अतिरिक्त स्वास्थ्य देने वाला टैंक जो अंतिम क्षण में विस्फोटक बन जाता है +A tank which shoots explosive bullets=विस्फोटक गोलियाँ चलाने वाला टैंक +A tank which speeds up its allies and speeds up as a last stand=अपने मित्रों को तेज़ करने वाला टैंक जो अंतिम क्षण में स्वयं भी तेज़ हो जाता है +A fast tank which rapidly fires many small, low-damage bullets=तेज़ी से कई छोटी, कम-क्षति वाली गोलियाँ चलाने वाला तेज़ टैंक +A tank which mimics the closest tank it sees=सबसे नज़दीकी दिखाई देने वाले टैंक की नक़ल करने वाला टैंक +A tank which blows strong gusts of air=हवा के ज़ोरदार झोंके मारने वाला टैंक +A tank which spawns mini-tanks and shoots 2-bounce rockets=मिनी-टैंक उत्पन्न करने वाला और 2-उछाल वाले रॉकेट चलाने वाला टैंक +A small, primitive tank which shoots tiny, low damage bullets=छोटी, कम क्षति वाली गोलियाँ चलाने वाला छोटा, आदिम टैंक +A tank which shoots homing rockets=होमिंग रॉकेट चलाने वाला टैंक +A tank which gets angry at line of sight=दृष्टि रेखा पर ग़ुस्साने वाला टैंक +A big boss tank which spawns other tanks and takes 5 regular bullets to destroy=अन्य टैंक उत्पन्न करने वाला बड़ा बॉस टैंक जिसे नष्ट करने के लिए 5 सामान्य गोलियाँ चाहिए +Last opened %d =अंतिम बार खोला %d +Adventure crusade=साहसिक क्रूसेड +Classic crusade=क्लासिक क्रूसेड +Levels: %d=स्तर: %d +Game paused=खेल रोक दिया गया +Note! You will lose a life for restarting!=ध्यान दें! पुनः आरंभ करने पर आप एक जीवन खो देंगे! +Note! You will lose a life for quitting in the middle of a level=ध्यान दें! स्तर के बीच में छोड़ने पर आप एक जीवन खो देंगे +You can't restart the level because you only have one life left!=आप स्तर पुनः आरंभ नहीं कर सकते क्योंकि आपके पास केवल एक जीवन शेष है! +Since you do not have any other lives left, your progress will be lost=चूँकि आपके पास कोई अन्य जीवन शेष नहीं है, आपकी प्रगति खो जाएगी +Battle %d=लड़ाई %d +Grassy welcome=घास भरा स्वागत +Snowy tunnel=बर्फ़ीली सुरंग +Stadium=स्टेडियम +Potholes=गड्ढे +Shooting range=शूटिंग रेंज +Blockage=अवरोध +Breakout=ब्रेकआउट +Bomb squad=बम दस्ता +Shrubland=झाड़ीदार ज़मीन +The corner=कोना +Package=पैकेज +Break in=घुसपैठ +The box=डिब्बा +The classic=क्लासिक +Purple dome=बैंगनी गुंबद +Snake=साँप +Zap=ज़ैप +Grassy fort=घास भरा क़िला +Snow piles=बर्फ़ के ढेर +Ghost invasion=भूतों का आक्रमण +Winter lake=शीतकालीन झील +Freeze trap=जमाने वाला जाल +Toast=टोस्ट +Mud pits=कीचड़ के गड्ढे +Dodge forest=बचाव जंगल +Bumper mania=बंपर पागलपन +Squad=दस्ता +Corners=कोने +The base=ठिकाना +Face off=आमने-सामने +Black bros=काले भाई +Shades of green=हरे के रंग +Babies attack=शिशुओं का हमला +Blizzard=बर्फ़ानी तूफ़ान +Big boss=बड़ा बॉस +Play random levels, crusades, the tutorial, or make your own levels!=यादृच्छिक स्तर, क्रूसेड, ट्यूटोरियल खेलें या अपने स्तर बनाएँ! +Play, chat, and share levels and crusades with other players!=अन्य खिलाड़ियों के साथ स्तर और क्रूसेड खेलें, चैट करें और साझा करें! +Create or join a party=पार्टी बनाएँ या जॉइन करें +Make sure that everyone is using the same port!=सुनिश्चित करें कि सभी एक ही पोर्ट का उपयोग कर रहे हैं! +All players should be connected to the same network, unless the host is port forwarding.=जब तक मेज़बान पोर्ट फ़ॉरवर्डिंग नहीं कर रहा हो, सभी खिलाड़ियों को एक ही नेटवर्क से जुड़ा होना चाहिए। +Joining via Steam peer-to-peer Doesn't require port forwarding.=स्टीम peer-to-peer के माध्यम से जॉइन करने के लिए पोर्ट फ़ॉरवर्डिंग आवश्यक नहीं है। +Create a party=पार्टी बनाएँ +Join a party=पार्टी जॉइन करें +Port=पोर्ट +Sets port for multiplayer=मल्टीप्लेयर के लिए पोर्ट सेट करता है +Make sure all players are using the same port=सुनिश्चित करें कि सभी खिलाड़ी एक ही पोर्ट का उपयोग कर रहे हैं +Your local IP Address: %d %d Port: %d%d=आपका स्थानीय IP पता: %d %d पोर्ट: %d%d +Also hosting on Steam peer-to-peer (Steam friends can join)=स्टीम peer-to-peer पर भी होस्ट किया जा रहा है (स्टीम मित्र जॉइन कर सकते हैं) +Players in this party:=इस पार्टी में खिलाड़ी: +Play:=खेलें: +Versus=बनाम +Fight other players in this party in a randomly generated level=इस पार्टी के अन्य खिलाड़ियों के साथ यादृच्छिक रूप से बनाए गए स्तर में लड़ें +Play levels you have created=आपके द्वारा बनाए गए स्तर खेलें +Level and crusade sharing:=स्तर और क्रूसेड साझाकरण: +Upload=अपलोड +Download=डाउनलोड +Party options=पार्टी विकल्प +What would you like to share?=आप क्या साझा करना चाहेंगे? +Share a level=एक स्तर साझा करें +Share a crusade=एक क्रूसेड साझा करें +Click here or press %d to send a chat message=चैट संदेश भेजने के लिए यहाँ क्लिक करें या %d दबाएँ +Share=साझा करें +Shared levels=साझा किए गए स्तर +Shared crusades=साझा की गई क्रूसेड +Level downloaded!=स्तर डाउनलोड हो गया! +Pick a different name...=एक अलग नाम चुनें... +Ready %d=तैयार %d +End party=पार्टी समाप्त करें +Are you sure you want to end the party?=क्या आप वाकई पार्टी समाप्त करना चाहते हैं? +All players will be disconnected!=सभी खिलाड़ी डिस्कनेक्ट हो जाएँगे! +Join Steam friends=स्टीम मित्रों के साथ जॉइन करें +Steam friends playing Tanks=टैंक्स खेलते स्टीम मित्र +Party IP Address=पार्टी IP पता +You can find this on the party host's screen=यह आपको पार्टी मेज़बान की स्क्रीन पर मिलेगा +Join=जॉइन करें +Connect to a non-cellular data network to play with others!=अन्य लोगों के साथ खेलने के लिए ग़ैर-सेलुलर डेटा नेटवर्क से कनेक्ट करें! +%d has joined the party=%d पार्टी में शामिल हुआ +%d has left the party=%d पार्टी से चला गया +%dms=%dms +Would you like to kick %d from the party?=क्या आप %d को पार्टी से निकालना चाहेंगे? +Cancel=रद्द करें +Kick %d=%d को निकालें +You were kicked from the party!=आपको पार्टी से निकाल दिया गया! +The party host has closed their game=पार्टी मेज़बान ने अपना खेल बंद कर दिया है +Continue=जारी रखें +Please wait for the current game to finish!=कृपया वर्तमान खेल समाप्त होने की प्रतीक्षा करें! +%d would like to join the party=%d पार्टी में शामिल होना चाहता है +%d has started!=%d शुरू हो गया है! +Created access code: %d=ऐक्सेस कोड बनाया गया: %d +Party=पार्टी +Select a multiplayer game mode=मल्टीप्लेयर खेल मोड चुनें +Party=पार्टी +Play with other people who are connected to your local network (or who are port forwarding)=उन अन्य लोगों के साथ खेलें जो आपके स्थानीय नेटवर्क से जुड़े हैं (या जो पोर्ट फ़ॉरवर्डिंग कर रहे हैं) +Online=ऑनलाइन +Access the online Tanks community!=ऑनलाइन टैंक्स समुदाय तक पहुँचें! +Welcome to %d!=%d में आपका स्वागत है! +You now have access to %d for %d=अब आपके पास %d के लिए %d तक की पहुँच है +Tanks Online menu=टैंक्स ऑनलाइन मेनू +Upload level=स्तर अपलोड करें +Select a level to upload...=अपलोड करने के लिए स्तर चुनें... +Level successfully uploaded!=स्तर सफलतापूर्वक अपलोड हुआ! +Browse levels=स्तर ब्राउज़ करें +Uploaded by: %d=अपलोड करने वाला: %d +%d=%d +My uploaded levels=मेरे अपलोड किए गए स्तर +My account=मेरा खाता +Your access code:=आपका ऐक्सेस कोड: +Your access will expire in %d=आपकी पहुँच %d में समाप्त हो जाएगी +Accounts using this access code: %d=इस ऐक्सेस कोड का उपयोग करने वाले खाते: %d +Unlink access code=ऐक्सेस कोड अनलिंक करें +You can control the blue tank using the blue joystick.=आप नीले जॉयस्टिक का उपयोग करके नीले टैंक को नियंत्रित कर सकते हैं। +Use this stick to move the blue tank!=नीले टैंक को हिलाने के लिए इस स्टिक का उपयोग करें! +Skip Tutorial=ट्यूटोरियल छोड़ें +Tap somewhere to shoot in that direction!=उस दिशा में फायर करने के लिए कहीं टैप करें! +Tap the player and drag around the orange circle to aim.=खिलाड़ी पर टैप करें और निशाना लगाने के लिए नारंगी घेरे के चारों ओर खींचें। +Pull your finger out of the orange circle to shoot!¡=फायर करने के लिए नारंगी घेरे से अपनी उँगली बाहर निकालें! +Double-tap the blue tank to lay a mine!=माइन रखने के लिए नीले टैंक पर डबल-टैप करें! +You can see your health, available bullets and mines, and remaining enemy tanks by pressing the bottom arrow!=नीचे का तीर दबाकर आप अपना स्वास्थ्य, उपलब्ध गोलियाँ और माइनें, और शेष दुश्मन टैंक देख सकते हैं! +Press the arrow below!=नीचे का तीर दबाएँ! +Finish of the battle by destroying that grey tank, but watch out - it can move!=उस ग्रे टैंक को नष्ट करके लड़ाई समाप्त करें, लेकिन सावधान - वह चल सकता है! +Congratulations! You are now ready to play!=बधाई हो! अब आप खेलने के लिए तैयार हैं! +Let's go!=चलिए शुरू करें! +Debug menu=डीबग मेनू +Test keyboard=कीबोर्ड परीक्षण +Key test=कुंजी परीक्षण +Press a key to show its code=उसका कोड दिखाने के लिए कोई कुंजी दबाएँ +Test text boxes=टेक्स्ट बॉक्स परीक्षण +Text box test=टेक्स्ट बॉक्स परीक्षण +Text box=टेक्स्ट बॉक्स +UUID box=UUID बॉक्स +Test models=मॉडल परीक्षण +Trace rays:=किरणें ट्रेस करें: +Immersive camera:=इमर्सिव कैमरा: +First person:=प्रथम-पुरुष: +Crusade name=क्रूसेड का नाम +Starting lives=शुरुआती जीवन +Bonus life frequency=बोनस जीवन आवृत्ति +Level names:=स्तर के नाम: +Crusade levels=क्रूसेड स्तर +Rearrange levels=स्तर पुनः व्यवस्थित करें +Add level=स्तर जोड़ें +Select a level to add=जोड़ने के लिए स्तर चुनें +Level position=स्तर की स्थिति +Battle cleared=लड़ाई जीती! +Lives remaining: %d=शेष जीवन: %d +Crusade time: %d=क्रूसेड का समय: %d +You gained a life for clearing %d!=%d जीतने के लिए आपको एक जीवन मिला! +Next level=अगला स्तर +Replay the level=स्तर पुनः खेलें +You finished the crusade!=आपने क्रूसेड पूरा कर लिया! +statistics=आँकड़े +Kills=कत्ल +Coins=सिक्के +Total coins=कुल सिक्के +Deaths=मौतें +Battles=लड़ाइयाँ +Battle=लड़ाई +Attempts=प्रयास +Clear time=पूर्ण समय +Total time=कुल समय +Total=कुल +Item=आइटम +Times used=उपयोग की बार +Hits landed=लगे प्रहार +Accuracy=शुद्धता +Summary=सारांश +Battles cleared=जीती हुई लड़ाइयाँ +Lives remaining=शेष जीवन +Total kills=कुल कत्ल +Total deaths=कुल मौतें +Time elapsed=बीता हुआ समय +Coins spent=खर्च किए गए सिक्के +Coins remaining=शेष सिक्के +Return to title=होम स्क्रीन पर वापस जाएँ +Minigames=मिनी-गेम्स +Arcade Mode=आर्केड मोड +Beat Arcade Mode=आर्केड मोड हराएँ +Castle Crusade=क़िला क्रूसेड +Sports Crusade=स्पोर्ट्स क्रूसेड +Drag and drop crusades to import them=क्रूसेड आयात करने के लिए उन्हें ड्रैग और ड्रॉप करें +Create a crusade=क्रूसेड बनाएँ +Random co-op=यादृच्छिक को-ऑप +Random versus=यादृच्छिक बनाम +Your Local IP Address:=आपका स्थानीय IP पता: + + +§000100200255bird's-eye=§000100200255पक्षी की दृष्टि से +§000100200255circular=§000100200255वृत्ताकार +§000100200255fancy=§000100200255उत्तम +§000100200255unlimited=§000100200255असीमित +§000200000255default=§000200000255डिफ़ॉल्ट +§000200000255on=§000200000255चालू +§000200000255public=§000200000255सार्वजनिक +§000200000255strong=§000200000255मज़बूत +§200000000255off=§200000000255बंद +§200000000255private=§200000000255निजी +§200000000255third person=§200000000255तृतीय-पुरुष +§200100000255angled=§200100000255कोणीय +§200100000255bottom=§200100000255नीचे +§200100000255fast=§200100000255तेज़ +§200100000255V-Sync=§200100000255V-Sync +§200100000255weak=§200100000255कमज़ोर +§255000000255first person=§255000000255प्रथम-पुरुष +Adds designs to the built-in tanks---which can help differentiate them=अंतर्निहित टैंकों में डिज़ाइन जोड़ता है---जो उन्हें अलग पहचानने में मदद करता है +Advanced stats: =उन्नत आँकड़े: +Agree and continue=सहमत हों और जारी रखें +A mine will explode after 10 seconds, if another tank gets near, or if shot.=माइन 10 सेकंड बाद, या कोई अन्य टैंक पास आने पर, या उस पर गोली लगने पर फट जाएगी। +Antialiasing: =एंटीएलियासिंग: +Antialiasing will be enabled=एंटीएलियासिंग सक्षम हो जाएगी +Anticheat: =धोखा-रोधी: +a place to share your creations to an audience of all ages.=एक जगह जहाँ आप अपनी रचनाएँ हर उम्र के दर्शकों के साथ साझा कर सकते हैं। +Audio: =ऑडियो: +Auto ready: =स्वतः तैयार: +Autostart: =स्वतः आरंभ: +Back to minigames=मिनी-गेम्स पर वापस +Back to party=पार्टी पर वापस +Balancing: =संतुलन: +Battle cleared!=लड़ाई जीती! +Battle failed!=लड़ाई हारी! +Beginner crusade=शुरुआती क्रूसेड +Block=ब्लॉक +Bot players=बॉट खिलाड़ी +Browse crusades=क्रूसेड ब्राउज़ करें +Browse crusades the Tanks community has created!=टैंक्स समुदाय द्वारा बनाए गए क्रूसेड ब्राउज़ करें! +Browse levels the Tanks community has created!=टैंक्स समुदाय द्वारा बनाए गए स्तर ब्राउज़ करें! +Browse public parties=सार्वजनिक पार्टियाँ ब्राउज़ करें +Bullet trails show the paths of bullets------Fancy bullet trails enable some extra particle---effects for certain bullet types=गोली के निशान गोलियों का मार्ग दिखाते हैं------उत्तम गोली के निशान कुछ गोली प्रकारों के---लिए अतिरिक्त कण प्रभाव सक्षम करते हैं +but watch out - it can move!=लेकिन सावधान - वह चल सकता है! +Camera=कैमरा +Can also be toggled at any time---by pressing %s=%s दबाकर किसी भी समय भी---चालू/बंद किया जा सकता है +Castle crusade=क़िला क्रूसेड +Changes the angle at which---you view the game field=उस कोण को बदलता है जिस पर---आप खेल मैदान देखते हैं +Chat filter: =चैट फ़िल्टर: +Circle tool=वृत्त उपकरण +Clear tool=साफ़ करें उपकरण +Click here or press %s to send a chat message=चैट संदेश भेजने के लिए यहाँ क्लिक करें या %s दबाएँ +Click to mute %s and prevent them from chatting.---%s is currently not muted.=%s को म्यूट करने और उन्हें चैट करने से रोकने के लिए क्लिक करें।---%s वर्तमान में म्यूट नहीं है। +Click to unmute %s.---%s is currently muted and can't chat.=%s को अनम्यूट करने के लिए क्लिक करें।---%s वर्तमान में म्यूट है और चैट नहीं कर सकता। +Close menu=मेनू बंद करें +Colors: %d=रंग: %d +Configures the placement of item, health,---and ammunition information on the screen.------In the 'bottom' setting, all this information---will be at the bottom of the screen.------In the 'circular' setting, this information will---either be overlaid on your tank---or placed around your cursor.=स्क्रीन पर वस्तु, स्वास्थ्य और---गोला-बारूद जानकारी का स्थान कॉन्फ़िगर करता है।------'नीचे' सेटिंग में, यह सारी जानकारी---स्क्रीन के नीचे होगी।------'वृत्ताकार' सेटिंग में, यह जानकारी---या तो आपके टैंक के ऊपर लगी होगी---या आपके कर्सर के चारों ओर रखी जाएगी। +Connect to a network to play with others!=अन्य लोगों के साथ खेलने के लिए नेटवर्क से कनेक्ट करें! +Constrain mouse: =माउस को सीमित करें: +Copy=कॉपी करें +Countdown time=उल्टी गिनती का समय +Create a level with the 'New level' button!='नया स्तर' बटन से स्तर बनाएँ! +Create crusade=क्रूसेड बनाएँ +Creations deemed inappropriate or offensive may be removed.=अनुचित या आपत्तिजनक मानी जाने वाली रचनाएँ हटाई जा सकती हैं। +Crusade preview: =क्रूसेड पूर्वावलोकन: +# Current battle: =# वर्तमान लड़ाई: +Cut=कट करें +Delete level=स्तर हटाएँ +Deterministic: =नियतात्मक: +Deterministic mode changes the random number---generation to be fixed based on a seed, and---the game speed to be locked and independent---of framerate.------This is useful for fair speedruns but may---provide for a less smooth experience.------If your device can't run Tanks at 60 FPS,---use 30 FPS mode to prevent slowdowns.=नियतात्मक मोड यादृच्छिक संख्या जनरेशन को---बीज पर आधारित निश्चित कर देता है, और---खेल की गति को लॉक और फ्रेमरेट से---स्वतंत्र कर देता है।------यह निष्पक्ष स्पीडरन के लिए उपयोगी है---लेकिन कम सहज अनुभव दे सकता है।------यदि आपका डिवाइस टैंक्स को 60 FPS पर नहीं चला सकता,---धीमी गति से बचने के लिए 30 FPS मोड का उपयोग करें। +Disables all friendly fire in the party.---Tanks on the same team will---not damage each other.---Useful for co-op in bigger parties.=पार्टी में सभी मित्रवत फायर निष्क्रिय करता है।---एक ही टीम के टैंक---एक-दूसरे को क्षति नहीं देंगे।---बड़ी पार्टियों में को-ऑप के लिए उपयोगी। +Disables the framerate limit------May cause issues with inconsistent game speed=फ्रेमरेट सीमा को निष्क्रिय करता है------असंगत खेल गति की समस्याएँ पैदा कर सकता है +Disallows your mouse pointer from---leaving the window while playing=खेलते समय आपके माउस पॉइंटर को---विंडो से बाहर निकलने से रोकता है +%d players in this party:=इस पार्टी में %d खिलाड़ी: +Editor=संपादक +Effect options=प्रभाव विकल्प +Enabling 3D ground may impact---performance in large levels=3D ज़मीन सक्षम करना बड़े स्तरों पर---प्रदर्शन को प्रभावित कर सकता है +Exit=बाहर निकलें +Failed to connect=कनेक्ट करने में विफल +family friendly and respectful to other players!=परिवार के अनुकूल और अन्य खिलाड़ियों के प्रति सम्मानजनक हो! +Fancy terrain enables varied block---and ground colors------May impact performance on larger levels=उत्तम भूभाग विविध ब्लॉक---और ज़मीन के रंग सक्षम करता है------बड़े स्तरों पर प्रदर्शन को प्रभावित कर सकता है +Fight battles in an order,---and see how long you can survive!=क्रम से लड़ाइयाँ लड़ें,---और देखें कि आप कब तक जीवित रह सकते हैं! +Fight other players in this party---in a randomly generated level=इस पार्टी के अन्य खिलाड़ियों के साथ---यादृच्छिक रूप से बनाए गए स्तर में लड़ें +First person: =प्रथम-पुरुष: +Friendly fire: =मित्रवत फायर: +Fullscreen: =फुलस्क्रीन: +Game menu=खेल मेनू +Generate new level=नया स्तर बनाएँ +Getting your IP Address...=आपका IP पता प्राप्त किया जा रहा है... +Glow effects may significantly---impact performance=चमक प्रभाव प्रदर्शन को महत्वपूर्ण---रूप से प्रभावित कर सकते हैं +Graphics: =ग्राफ़िक्स: +Hide/show pause menu=पॉज़ मेनू छिपाएँ/दिखाएँ +Hosting failed:---%s------If you keep getting this error try restarting the game.=होस्टिंग विफल:---%s------यदि आपको यह त्रुटि बार-बार मिले तो खेल पुनः आरंभ करें। +Hotbar: =हॉटबार: +How many extra bot players---to add to parties=पार्टियों में जोड़ने के लिए---कितने अतिरिक्त बॉट खिलाड़ी +Immersive camera: =इमर्सिव कैमरा: +Improvements: =सुधार: +Info bar: =सूचना पट्टी: +Invite Steam friends=स्टीम मित्रों को आमंत्रित करें +Items: =आइटम्स: +Item slot 1=आइटम स्लॉट 1 +Item slot 10=आइटम स्लॉट 10 +Item slot 2=आइटम स्लॉट 2 +Item slot 3=आइटम स्लॉट 3 +Item slot 4=आइटम स्लॉट 4 +Item slot 5=आइटम स्लॉट 5 +Item slot 6=आइटम स्लॉट 6 +Item slot 7=आइटम स्लॉट 7 +Item slot 8=आइटम स्लॉट 8 +Item slot 9=आइटम स्लॉट 9 +Join by IP=IP से जॉइन करें +Join steam parties=स्टीम पार्टियाँ जॉइन करें +Last modified---%s ago=अंतिम बार संशोधित---%s पहले +Last opened %d=अंतिम बार खोला %d +Layered music: =परतदार संगीत: +Leave party=पार्टी छोड़ें +Level and crusade sharing: =स्तर और क्रूसेड साझाकरण: +Level names: =स्तर के नाम: +Levels: =स्तर: +Level save name=स्तर सहेजने का नाम +Level time: =स्तर का समय: +Limiting your framerate may---decrease battery consumption=आपकी फ्रेमरेट सीमित करना---बैटरी की खपत कम कर सकता है +Limits framerate to your screen's refresh rate------May fix issues with screen tearing=फ्रेमरेट को आपकी स्क्रीन की रिफ्रेश दर तक सीमित करता है------स्क्रीन टीयरिंग की समस्याओं को ठीक कर सकता है +Line tool=रेखा उपकरण +Make a copy=एक प्रति बनाएँ +make their party public to other Steam players.=अपनी पार्टी को अन्य स्टीम खिलाड़ियों के लिए सार्वजनिक करें। +Manual limit=मैन्युअल सीमा +Max FPS: §000100200255unlimited=अधिकतम FPS: §000100200255असीमित +Max FPS: §200100000255V-Sync=अधिकतम FPS: §200100000255V-Sync +Max FPS: %s=अधिकतम FPS: %s +Maximum framerate: §000100200255unlimited=अधिकतम फ्रेमरेट: §000100200255असीमित +Maximum framerate: §200100000255V-Sync=अधिकतम फ्रेमरेट: §200100000255V-Sync +Maximum framerate is locked to 60---because of deterministic mode=नियतात्मक मोड के कारण---अधिकतम फ्रेमरेट 60 पर लॉक है +Maximum framerate: %s=अधिकतम फ्रेमरेट: %s +May fix flickering in thin edges---at the cost of performance------Requires restarting the game---to take effect=प्रदर्शन की क़ीमत पर पतले किनारों की---झिलमिलाहट को ठीक कर सकता है------प्रभावी होने के लिए खेल को---पुनः आरंभ करना आवश्यक है +Miscellaneous options=विविध विकल्प +More: =अधिक: +Move down=नीचे जाएँ +My profile=मेरी प्रोफ़ाइल +My uploaded creations=मेरी अपलोड की गई रचनाएँ +network, unless the host is port forwarding.=नेटवर्क से, जब तक मेज़बान पोर्ट फ़ॉरवर्डिंग न कर रहा हो। +New features: =नई सुविधाएँ: +No creations found=कोई रचना नहीं मिली +No levels found=कोई स्तर नहीं मिला +Now finish off the battle by destroying that gray tank,=अब उस ग्रे टैंक को नष्ट करके लड़ाई समाप्त करें, +# off=# बंद +# on=# चालू +Online creations=ऑनलाइन रचनाएँ +Open folder in file manager=फ़ाइल मैनेजर में फ़ोल्डर खोलें +Open game folder=खेल फ़ोल्डर खोलें +Options: =विकल्प: +or=या +Particle effects may significantly---impact performance=कण प्रभाव प्रदर्शन को महत्वपूर्ण---रूप से प्रभावित कर सकते हैं +Party host=पार्टी मेज़बान +Paste=पेस्ट करें +Personalize your tank!=अपने टैंक को व्यक्तिगत बनाएँ! +Pick a name for the copy!=प्रति के लिए एक नाम चुनें! +Pick a username that players---will see in multiplayer=ऐसा उपयोगकर्ता नाम चुनें जो खिलाड़ी---मल्टीप्लेयर में देखेंगे +Play: =खेलें: +Play, chat, and share levels---and crusades with other players!=अन्य खिलाड़ियों के साथ स्तर और---क्रूसेड खेलें, चैट करें और साझा करें! +Players in this party: =इस पार्टी में खिलाड़ी: +Players using Steam can invite friends to their party or=स्टीम का उपयोग कर रहे खिलाड़ी मित्रों को अपनी पार्टी में आमंत्रित कर सकते हैं या +Play random levels, crusades, minigames,---the tutorial, or make your own levels or crusades!=यादृच्छिक स्तर, क्रूसेड, मिनी-गेम्स,---ट्यूटोरियल खेलें या अपने स्तर या क्रूसेड बनाएँ! +Play Tanks in new ways!=टैंक्स को नए तरीक़ों से खेलें! +Please ensure that everything you upload is appropriately=कृपया सुनिश्चित करें कि आप जो भी अपलोड कर रहे हैं वह उचित रूप से +Publicly share and check out---levels and crusades other players---have made!=अन्य खिलाड़ियों द्वारा बनाए गए स्तर और---क्रूसेड सार्वजनिक रूप से साझा करें---और देखें! +Pull your finger out of the orange circle to shoot!=फायर करने के लिए नारंगी घेरे से अपनी उँगली बाहर निकालें! +Quick use ability 1=त्वरित उपयोग क्षमता 1 +Quick use ability 2=त्वरित उपयोग क्षमता 2 +Quick use ability 3=त्वरित उपयोग क्षमता 3 +Quick use ability 4=त्वरित उपयोग क्षमता 4 +Quick use ability 5=त्वरित उपयोग क्षमता 5 +Quit=बाहर निकलें +# Remaining lives: =# शेष जीवन: +Replay level=स्तर पुनः खेलें +Reset controls=नियंत्रण रीसेट करें +Reset to default colors=डिफ़ॉल्ट रंगों पर रीसेट करें +Restart level=स्तर पुनः आरंभ करें +Restart tutorial=ट्यूटोरियल पुनः आरंभ करें +Resume=जारी रखें +Rocket=रॉकेट +Rotate selection=चयन घुमाएँ +Save copy=प्रति सहेजें +Search=खोजें +Second color: =दूसरा रंग: +Set a manual framerate limit=मैन्युअल फ्रेमरेट सीमा सेट करें +Sets port for multiplayer------Make sure all players are using---the same port=मल्टीप्लेयर के लिए पोर्ट सेट करता है------सुनिश्चित करें कि सभी खिलाड़ी---एक ही पोर्ट का उपयोग कर रहे हैं +Shadow options=छाया विकल्प +Shadow quality=छाया गुणवत्ता +Shadow quality: %s=छाया गुणवत्ता: %s +Shadows: =छायाएँ: +Shadows are quite graphically intense---and may significantly reduce framerate=छायाएँ ग्राफ़िक रूप से काफ़ी सघन हैं---और फ्रेमरेट को काफ़ी कम कर सकती हैं +Share a crusade you made with the Tanks community!=टैंक्स समुदाय के साथ अपना बनाया क्रूसेड साझा करें! +Share a level you made with the Tanks community!=टैंक्स समुदाय के साथ अपना बनाया स्तर साझा करें! +Share crusade=क्रूसेड साझा करें +Share level=स्तर साझा करें +Show best run: =सर्वश्रेष्ठ रन दिखाएँ: +Shows indicators for bullets---hidden behind terrain=भूभाग के पीछे छुपी गोलियों के---लिए संकेतक दिखाता है +Shows the following information---at the bottom of the screen:------Game version---Framerate---Network latency (if in a party)---Memory usage=स्क्रीन के निचले हिस्से में---निम्नलिखित जानकारी दिखाता है:------खेल संस्करण---फ्रेमरेट---नेटवर्क विलंबता (पार्टी में होने पर)---मेमोरी उपयोग +Sorting by last modified=अंतिम बार संशोधन के अनुसार क्रमबद्ध +Sorting by name=नाम के अनुसार क्रमबद्ध +Square selection=वर्गाकार चयन +Square tool=वर्ग उपकरण +%s's creations=%s की रचनाएँ +Starting Steam party...=स्टीम पार्टी आरंभ की जा रही है... +Start now=अभी शुरू करें +Start over=फिर से शुरू करें +Stats animations: =आँकड़े एनिमेशन: +Steam visibility: =स्टीम दृश्यता: +Steam workshop=स्टीम वर्कशॉप +Steam workshop upload agreement=स्टीम वर्कशॉप अपलोड समझौता +Take screenshot=स्क्रीनशॉट लें +Tank textures: =टैंक टेक्सचर: +Thank you for your understanding.=आपकी समझ के लिए धन्यवाद। +the next time you start the game.=अगली बार जब आप खेल शुरू करेंगे। +the same port, and that all players are connected to the same=एक ही पोर्ट का, और सभी खिलाड़ी एक ही +The Steam Workshop for Tanks: The Crusades is intended to be=टैंक्स: द क्रूसेड्स के लिए स्टीम वर्कशॉप का उद्देश्य है +The Tanks community looks forward to seeing what you've made!=टैंक्स समुदाय आपकी बनाई हुई चीज़ें देखने के लिए उत्सुक है! +The wait time in seconds after---all players are ready before---the battle begins.=सभी खिलाड़ी तैयार होने के बाद---लड़ाई शुरू होने से पहले---प्रतीक्षा का समय (सेकंड में)। +Timer: =टाइमर: +Toggle automatic zoom=स्वचालित ज़ूम चालू/बंद करें +Toggle showing IP address=IP पता दिखाना चालू/बंद करें +To join with an IP address, make sure that everyone is using=IP पते से जॉइन करने के लिए, सुनिश्चित करें कि सभी +Trace rays: =किरणें ट्रेस करें: +Unlimited=असीमित +Upload crusade=क्रूसेड अपलोड करें +User interfaces: =उपयोगकर्ता इंटरफ़ेस: +View: =दृश्य: +View everything you have uploaded=आपके द्वारा अपलोड की गई हर चीज़ देखें +Void rocket=शून्य रॉकेट +V-Sync=V-Sync +V-Sync: =V-Sync: +Wand tool=जादू की छड़ी उपकरण +Warn before closing the game---while in an editor=संपादक में रहते हुए खेल बंद---करने से पहले चेतावनी दें +Warn before exit: =बाहर निकलने से पहले चेतावनी दें: +Welcome to Tanks!=टैंक्स में आपका स्वागत है! +What's new in %d: =%d में क्या नया है: +When enabled and playing a built-in---crusade that you've already completed---at least once, the time difference from---your best run's time will show upon---clearing a level.=सक्षम होने पर और कम से कम एक बार---पूरा किए गए अंतर्निहित क्रूसेड को खेलते समय,---स्तर जीतने पर आपके सर्वश्रेष्ठ रन के---समय से अंतर दिखाया जाएगा। +When enabled, levels will---start playing automatically---4 seconds after they are---loaded (if the play button---isn't clicked earlier)=सक्षम होने पर, स्तर लोड होने के---4 सेकंड बाद स्वचालित रूप से---शुरू हो जाएँगे (यदि खेलें बटन---पहले नहीं दबाया गया) +When enabled, the backgrounds of---the crusade preview and stats---screens display an animation of all---the crusade levels scrolling by.=सक्षम होने पर, क्रूसेड पूर्वावलोकन---और आँकड़े स्क्रीनों की पृष्ठभूमि---क्रूसेड के सभी स्तरों के स्क्रॉल---होने का एनिमेशन दिखाती हैं। +When enabled, time spent---in the current level attempt---and crusade will be displayed=सक्षम होने पर, वर्तमान स्तर प्रयास---और क्रूसेड में बिताया गया---समय प्रदर्शित होगा +When layered music is enabled, different---instruments will be added to the soundtrack---based on criteria such as remaining tank---types or arcade rampage level.--- ---This may cause lag and desynchronization---of music on some devices.=परतदार संगीत सक्षम होने पर, शेष टैंक---प्रकार या आर्केड रैम्पेज स्तर जैसे---मानदंडों के आधार पर ध्वनि-पथ में---विभिन्न वाद्य जोड़े जाएँगे।--- ---यह कुछ डिवाइसों पर संगीत की लैग---और बेमेल का कारण बन सकता है। +When off, skips directly to the summary tab---of the crusade end stats screen=बंद होने पर, क्रूसेड एंड आँकड़े स्क्रीन के---सारांश टैब पर सीधे चला जाता है +When this option is enabled---while hosting a party,---other players' positions and---velocities will be checked---and corrected if invalid.------Weaker settings work better---with less stable connections.=पार्टी होस्ट करते समय यह विकल्प---सक्षम होने पर, अन्य खिलाड़ियों---की स्थिति और गति की जाँच की---जाएगी और अमान्य पाए जाने पर---सही की जाएगी।------कमज़ोर सेटिंग्स कम-स्थिर---कनेक्शन के साथ बेहतर काम करती हैं। +Window options=विंडो विकल्प +Window resolution=विंडो रिज़ॉल्यूशन +X-ray bullets: =एक्स-रे गोलियाँ: +You can control the tank in the circle using the keyboard.=आप घेरे में टैंक को कीबोर्ड का उपयोग करके नियंत्रित कर सकते हैं। +You can find this on the---party host's screen=यह आपको पार्टी मेज़बान---की स्क्रीन पर मिलेगा +You can see your health, available bullets and mines,=आप अपना स्वास्थ्य, उपलब्ध गोलियाँ और माइनें देख सकते हैं, +You have no levels=आपके पास कोई स्तर नहीं है +Your access code: =आपका ऐक्सेस कोड: +Your Local IP Address: %s (Port: %d)=आपका स्थानीय IP पता: %s (पोर्ट: %d)