diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 492f28668..4af9b80f4 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -64,7 +64,8 @@ jobs: -DUSE_TIDY=${{ matrix.config.tidy }} \ -DUSE_TESTS=On \ -DUSE_SANITIZERS=On \ - -DUSE_TOOLS=On .. + -DUSE_TOOLS=On \ + .. make -j $(getconf _NPROCESSORS_ONLN) - name: Run tests @@ -308,7 +309,7 @@ jobs: - name: Install apt Dependencies uses: Eeems-Org/apt-cache-action@v1.3 with: - packages: mingw-w64 unzip + packages: mingw-w64 unzip wine64 wine - name: Install Dependencies run: | wget -q https://github.com/omf2097/openomf-win-build/archive/refs/heads/main.zip @@ -317,6 +318,7 @@ jobs: - name: Generate Windows Release run: | mkdir build-release && cd build-release + export WINEPATH="${GITHUB_WORKSPACE}/openomf-win-build-main/bin/" cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=release \ -DCMAKE_TOOLCHAIN_FILE="${GITHUB_WORKSPACE}/cmake-scripts/mingw-w64-toolchain.cmake" \ @@ -324,6 +326,7 @@ jobs: -DCMAKE_INCLUDE_PATH="${GITHUB_WORKSPACE}/openomf-win-build-main/include/" \ -DCMAKE_LIBRARY_PATH="${GITHUB_WORKSPACE}/openomf-win-build-main/lib/" \ -DCMAKE_FIND_ROOT_PATH="${GITHUB_WORKSPACE}/openomf-win-build-main/" \ + -DOMF_COMMAND_WRAPPER="wine" \ "${GITHUB_WORKSPACE}" make -j $(getconf _NPROCESSORS_ONLN) make -j $(getconf _NPROCESSORS_ONLN) install diff --git a/CMakeLists.txt b/CMakeLists.txt index a14c64a1c..a9df2b262 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,7 +84,13 @@ if(GIT_FOUND) endif() if(WIN32) + # prevent Windows.h from automatically defining as many macros add_definitions(-DWIN32_LEAN_AND_MEAN) + + if(NOT MINGW) + # set source charset & runtime charset to utf-8 + add_compile_options("/utf-8") + endif() endif() # System packages (hard dependencies) @@ -109,7 +115,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/platform.h.in ${CMAKE_CURRENT_BIN # If tests are enabled, find CUnit if(USE_TESTS) - find_package(CUnit) + find_package(CUnit REQUIRED) endif() # Only strip on GCC (clang does not appreciate) @@ -119,7 +125,12 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU") endif() # Find OpenOMF core sources -file(GLOB_RECURSE OPENOMF_SRC RELATIVE ${CMAKE_SOURCE_DIR} "src/*/*.c" "src/*/*.h") +file(GLOB_RECURSE OPENOMF_SRC + LIST_DIRECTORIES OFF + CONFIGURE_DEPENDS + RELATIVE ${CMAKE_SOURCE_DIR} + "src/*/*.c" "src/*/*.h" +) set(COREINCS src @@ -224,6 +235,9 @@ add_dependencies(openomf copy_shaders) # Build tools if requested set(TOOL_TARGET_NAMES) +# always build languagetool, for BuildLanguages.cmake +add_executable(languagetool tools/languagetool/main.c) +list(APPEND TOOL_TARGET_NAMES languagetool) if (USE_TOOLS) add_executable(bktool tools/bktool/main.c tools/shared/animation_misc.c @@ -232,7 +246,6 @@ if (USE_TOOLS) tools/shared/animation_misc.c tools/shared/conversions.c) add_executable(soundtool tools/soundtool/main.c) - add_executable(languagetool tools/languagetool/main.c) add_executable(afdiff tools/afdiff/main.c) add_executable(rectool tools/rectool/main.c tools/shared/pilot.c) add_executable(pcxtool tools/pcxtool/main.c) @@ -249,7 +262,6 @@ if (USE_TOOLS) bktool aftool soundtool - languagetool afdiff rectool pcxtool @@ -282,9 +294,9 @@ endif() # Formatting via clang-format if(USE_FORMAT) include(ClangFormat) - file( - GLOB_RECURSE - SRC_FILES + file(GLOB_RECURSE SRC_FILES + LIST_DIRECTORIES OFF + CONFIGURE_DEPENDS RELATIVE ${CMAKE_SOURCE_DIR} "src/*.h" "src/*.c" @@ -340,7 +352,12 @@ if(CUNIT_FOUND) include_directories(${CUNIT_INCLUDE_DIR} testing/ src/) SET(CORELIBS ${CORELIBS} ${CUNIT_LIBRARY}) - file(GLOB_RECURSE TEST_SRC RELATIVE ${CMAKE_SOURCE_DIR} "testing/*.c") + file(GLOB_RECURSE TEST_SRC + LIST_DIRECTORIES OFF + CONFIGURE_DEPENDS + RELATIVE ${CMAKE_SOURCE_DIR} + "testing/*.c" + ) add_executable(openomf_test_main ${TEST_SRC}) @@ -350,6 +367,10 @@ if(CUNIT_FOUND) target_link_libraries(openomf_test_main ${CORELIBS} SDL2::Main SDL2::Mixer Epoxy::Main) + if(MSVC) + target_precompile_headers(openomf_test_main PRIVATE "") + endif() + if(MINGW) # Always build as a console executable with mingw set_target_properties(openomf_test_main PROPERTIES LINK_FLAGS "-mconsole") @@ -362,6 +383,8 @@ else() message(STATUS "Development: Unit-tests are disabled") endif() +include("cmake-scripts/BuildLanguages.cmake") + # Copy some resources to destination resources directory to ease development setup. file(COPY resources/openomf.bk resources/gamecontrollerdb.txt DESTINATION resources) diff --git a/cmake-scripts/BuildLanguages.cmake b/cmake-scripts/BuildLanguages.cmake new file mode 100644 index 000000000..ee5c4f7ce --- /dev/null +++ b/cmake-scripts/BuildLanguages.cmake @@ -0,0 +1,56 @@ +# OMF 2097 Epic Challenge Arena +set(LANG_STRCOUNT 1013) +set(OMF_LANGS ENGLISH GERMAN) +# OpenOMF-specific +set(LANG2_STRCOUNT 1) +set(OPENOMF_LANGS DANISH) + +set(OMF_COMMAND_WRAPPER "" CACHE STRING "Optional wrapper to run languagetool with") + +if(WIN32) + set(LANGUAGE_INSTALL_PATH "openomf/resources/") +else() + set(LANGUAGE_INSTALL_PATH "share/games/openomf/") +endif() + +# generate custom target info +set(BUILD_LANG_COMMANDS) +set(BUILD_LANG_SOURCES) +foreach(LANG ${OMF_LANGS}) + set(TXT2 "${PROJECT_SOURCE_DIR}/resources/${LANG}2.TXT") + set(DAT2 "${CMAKE_CURRENT_BINARY_DIR}/resources/${LANG}.DAT2") + list(APPEND BUILD_LANG_SORUCES "${TXT2}") + list(APPEND BUILD_LANG_COMMANDS + DEPENDS "${TXT2}" + BYPRODUCTS "${DAT2}" + COMMAND ${CMAKE_COMMAND} -E echo_append "${LANG}, " + COMMAND ${OMF_COMMAND_WRAPPER} "$" -i "${TXT2}" -o "${DAT2}" --check-count ${LANG2_STRCOUNT} + ) + install(FILES "${DAT2}" DESTINATION "${LANGUAGE_INSTALL_PATH}") +endforeach() +foreach(LANG ${OPENOMF_LANGS}) + set(TXT "${PROJECT_SOURCE_DIR}/resources/${LANG}.TXT") + set(TXT2 "${PROJECT_SOURCE_DIR}/resources/${LANG}2.TXT") + set(LNG "${CMAKE_CURRENT_BINARY_DIR}/resources/${LANG}.LNG") + set(LNG2 "${CMAKE_CURRENT_BINARY_DIR}/resources/${LANG}.LNG2") + list(APPEND BUILD_LANG_SORUCES "${TXT}" "${TXT2}") + list(APPEND BUILD_LANG_COMMANDS + DEPENDS "${TXT}" "${TXT2}" + BYPRODUCTS "${LNG}" "{LNG2}" + COMMAND ${CMAKE_COMMAND} -E echo_append "${LANG}, " + COMMAND ${OMF_COMMAND_WRAPPER} "$" -i "${TXT}" -o "${LNG}" --check-count ${LANG_STRCOUNT} + COMMAND ${OMF_COMMAND_WRAPPER} "$" -i "${TXT2}" -o "${LNG2}" --check-count ${LANG2_STRCOUNT} + ) + install(FILES "${LNG}" "${LNG2}" DESTINATION "${LANGUAGE_INSTALL_PATH}") +endforeach() + + + +add_custom_target(build_languages + COMMAND ${CMAKE_COMMAND} -E echo_append "Building Languages... " + ${BUILD_LANG_COMMANDS} + COMMAND ${CMAKE_COMMAND} -E echo_append "done" +) +target_sources(build_languages PRIVATE ${BUILD_LANG_SORUCES}) +add_dependencies(openomf build_languages) +add_dependencies(build_languages languagetool) diff --git a/resources/DANISH.TXT b/resources/DANISH.TXT new file mode 100644 index 000000000..3ef3d9dd9 --- /dev/null +++ b/resources/DANISH.TXT @@ -0,0 +1,4414 @@ +ID: 0 +Title: Hjælp side 1 +Data: {BIDDET 260}{CENTER OFF} +{SIZE 8}{SKYD OVER}{COLOR:YELLOW}One Skal Falde 2097{KOLOR:DELEN} + +{SIZE 6}{SPACING 7} Velkommen til en skal falde 2097. Hvis tanken om 90 fod hoje robotter konstrueret til at rive dig i stykker får dig til at makulere dig i frygt, vil du elske "Exit" -indstillingen. + + Men hvis du er pumpet op og klar til dit livs kamp, lavede vi dette spil for dig! Forbered dig på at sparke robotkoj. + + Læs videre for at finde ud af det indvendige scoop på OMF 2097 - kontrollerne, bevægelserne, turneringerne, spilletips og meget mere. Mens du spiller spillet, kan du til enhver tid trykke på F1-tasten for at bringe disse instruktioner op. + + On-disk manualen er også spækket med endnu flere tips. For at se det, skriv "HELPME" fra DOS-prompten. +{CENTER PÅ} + +ID: 1 +Title: Hjælp side 2 +Data: {CENTER OFF} +{SIZE 8}{SHADOWS ON}{COLOR:YELLOW}Using The Main Menu{COLOR:DEFAULT}{SIZE 6}{SPACINGG 7} + +One Must Fall 2097 giver dig tre markant forskellige typer af spil. + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}One spiller spil +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Dette kaster dig ind i en række kampe, der tester din færdighed i ren hurtig handling kamp. Stål vil boje og gnister vil flyve! + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}Two spiller spil +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Udfordre en menneskelig modstander. To kan spille med tastaturet, tastaturet og joystick, eller to joysticks. + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}Tournament spil +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Begynder med en svag robot, du kæmper for kontanter og bruge dine gevinster til at opgradere din robot. Den ultimative kombination af handling og strategi! +{CENTER PÅ} + +ID: 2 +Title: Hjælp side 3 +Data: {CENTER OFF} +{SIZE 8}{SKADOWS PÅ}{COLOR:YELLOW}Andre Hovedmenuindstillinger{SIZE 6}{COLOR:DEFAULT} + + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}Konfiguration +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Brug dette vigtigste menuvalg til at ændre video, lyd, tastatur og joystick indstillinger. + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}Gameplay +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Tweak spillet hastighed, computer intelligens og andre spil muligheder. Du kan tilpasse OMF 2097 på mange måder. +{CENTER PÅ} + +ID: 3 +Title: Hjælp side 4 +Data: {CENTER OFF} +{SIZE 8}{SHADOWS ON}{COLOR:YELLOW}VVÆLG EN Pilot{COLOR:DELÉR} + +{SIZE 6}{SPACING 7}Når du starter et nyt spil, skal du vælge en pilot til at lede din robot i kamp. Hver pilot har unikke statistikker: + +{SIZE 6}{SPACING 9}{KOLOR:YELLOM}KYGGERE +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} En pilots rene brute styrke. Hvad er, der er, er, at du er hojere, så meget din magt, så meget du har, så meget du har ondt i stikken. + +{SIZE 6}{SPACING 9}{KOLOR:YELLOW}AGILITY +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Hurtige tænkere gor hurtige konkurrenter. Agilitet viser din pilots reaktionshastighed. + +{SIZE 6}{SPACING 9}{ KOLLEGA:YELLOM}ENDURANCE +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Det er ikke kun trækstyrke, der holder en robot på fodderne. Da en pilot foler hvert slag mod sin robot, spiller ENTURANCE en afgorende rolle. +{CENTER PÅ} + +ID: 4 +Title: Hjælp side 5 +Data: {CENTER OFF} +{SIZE 8}{SSKYT OVER}}KOLOR:YELLOM}VÆLG EN robot {KOLOR: FOREDBARK + +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Efter at have valgt en pilot, står du over for din vigtigste beslutning - hvilken robot vil repræsentere dig i kamp. Hver robot har mange angreb eller bevægelser, som han kan skade og til sidst besejre modstandere. Hver robot har også mindst to "særlige bevægelser". + + Specielle bevægelser opnås ved hurtig controller og knapkombinationer. Du skal mestre de særlige træk, hvis du onsker at blive en mester. + + Du kan finde flere oplysninger om kontrol og særlige bevægelser under side 10 og læse manualen (lober HELPME.EXE fra DOS-promenden). +{CENTER PÅ} + +ID: 5 +Title: Hjælp side 6 +Data: {CENTER OFF} +{SIZE 8}{SKADOWS PÅ}{COLOR:YELLOW}Tournament Play{COLOR: DEFAULT} + +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Efter at have valgt turneringsspil, er du i turneringskommandocentret. Kontrolpanelet er nederst på skærmen. Brug den til at kobe eller sælge dele, tage træningskurser og kæmpe i en turnering eller simulering. + + Når du har oprettet en ny pilot, ser du et holografisk billede af din robot. Din robots navn og specielle bevægelser er opfort overst til hojre. + + Din pilot statistik er til venstre: POWER, AGILITY, OG UDENDURANCE. Du kan tage kurser for at forbedre disse. + + Din robots statistik vises nederst til hojre. Disse ændrer sig, når du kober robotopgraderinger. +{CENTER PÅ} + +ID: 6 +Title: Hjælp side 7 +Data: {CENTER OFF} +{SIZE 8}{SSKYGGERE PÅ}{COLOR:YELLOW}Robot Statistik i turneringen Play{COLOR:DEFAULT} + +{STORRELSE 6}{SPACING 9}{COLOR:YELLOW}ARM HASTIGHED OG BEN HASTIGHED +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Disse målere viser hastigheden på din robots lemmer. Som disse stiger, vil du være i stand til at slå og sparke hurtigere. + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}ARM KRÆFT OG BEN KRÆFT +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Disse målere beskriver den skade, du kan gore, når du rammer eller sparker din modstander. + +{SIZE 6}{SPACING 9}{COLOR:YELLOW}STUN MODSTAND +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Opgradere dette for at hjælpe din robot med at håndtere chok af gentagne slag. Uden, vil du ofte blive slået svimmel. + +{SIZE 6}{SPACING 9}{ KOLLER:YELLOW}ARMOR PLATE: +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Armor plade drastisk reducerer skaderne på din robot, når du rammer. +{CENTER PÅ} + +ID: 7 +Title: Hjælp side 8 +Data: {CENTER OFF} +{SIZE 8}{SSKADOVRE PÅ}{COLOR:YELLOW}The Holding Bay{COLOR:DEFAULT} + +{SIZE 6}{SPACING 7} Dette er, hvor robotterne er forberedt og inspiceret til kamp. I et spillerspil vil du se en dialog mellem din pilot og hans modstander. + + I et tospillerspil kan spiller man vælge arenaen til at kæmpe i. Tryk til venstre og hojre for at skifte arenaer. + + Derefter bliver din pilot taget ind i turneringslaboratoriet, hvor hans nervesystem er grænsefladet med hans robot. Han glider derefter ind i en stof-induceret sovn. Da piloten vågner, erstattes hans kod af hærdt stål. Med held og din hjælp kan piloten besejre sin modstander i arenaen. +{CENTER PÅ} + +ID: 8 +Title: Hjælp side 9 +Data: {CENTER OFF} +{SKYER PÅ}{SIZE 8}{COLOR:YELLOW}The Arena{COLOR:DEFAULT} + +{SIZE 6}{SPACING 7}{COLOR:DEFAULT}Nu er det tid til den virkelige test! + + Den rode energimåler overst viser, hvor meget skade din robot kan tage for nedlukning. Enhver vellykket strejke mod dig sænker det samlede belob. Hvis din energimåler dypper under nul, kollapser din robot. + + Den tynde blå bar viser din bedovelsesskade. Denne bar blinker, når den er faretruende lav. Når det falder under nul, bliver din pilot svimmel og mister kontrollen i flere sekunder. + + Nogle arenaer indeholder farlige farer: ildkugler, pigge, kampfly og lignende. Hvis du ikke kan tage varmen, kan du slukke dem i GAMEPLAY-menuen. +{CENTER PÅ} + +ID: 9 +Title: Hjælp side 10 +Data: {CENTER OFF} +{SKYTKRÆNKER PÅ}{SIZE 8}{COLOR:YELLOW}Offense{COLOR:DEFAULT} + +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} Robotter har en bred vifte af angreb. For du kan pommel din modstander med dem, skal du kende kontrollerne. Du kan udfore ethvert træk med enten tastaturet eller et joystick. + +"Forward" betyder for din modstander. +"Back" betyder væk fra din modstander. + + Angrebene bruger disse retninger sammen med spark og PUNCH knapper. Strygning mens du trykker tilbage vil producere et andet træk end at slå, mens du trykker fremad. + + Tryk op for at springe. For at hoppe hojere og videre, skal du forst trykke ned og tryk derefter op. +{CENTER PÅ} + +ID: 10 +Title: Hjælp side 11 +Data: {CENTER OFF} +{SKYGGES PÅ}{SIZE 8}{COLOR:YELLOW}Defense + +{SIZE 6}{SPACING 7}{COLOR:DEFAULT} For at forsvare dig mod et angreb, mens du er på jorden, skal du trykke tilbage uden at angribe (ikke slå eller sparke). Din robot vil blokere enhver form for bevægelse, undtagen ben fejer. + + For at blokere ben fejer, tryk ned-back. At trykke ned-back vil ikke blokere luftangreb. + + Mens du er luftbåren, kan du ikke forsvare noget angreb. Hop med forsigtighed. +{CENTER PÅ} + +ID: 11 +Title: Hjælp side 12 +Data: {CENTER OFF} +{SKYER ON}{SIZE 8}{COLOR:YELLOW}At spille Tips{COLOR: DEFAULT} + +{SIZE 6}{SPACING 7} Hver pilot giver en robot en helt ny folelse. Prov flere piloter og vælg en, der passer bedst til dine evner. I et enspiller spil, computeren vil spille de andre piloter, ved hjælp af deres individuelle personligheder til at guide intelligensen af de robotter, de udover. + + Hver robot har flere specielle bevægelser, som både er udokumenterede og meget kraftfulde. For at udfore et særligt træk skal du kombinere controllerbevægelser og knaptryk i hurtig rækkefolge. Se computeren spille for at se nogle af de særlige bevægelser, så prov at gore dem selv. + +Eksempel: Udfor Jaguars hjernerystelse Cannon ved at trykke ned-back-punch. +{CENTER PÅ} + +ID: 12 +Title: Hjælp side 13 +Data: {CENTER OFF} +{SKYGGES ON}{SIZE 8}{COLOR:YELLOW}Combos +{SIZE 6}{COLOR:STANDER} +{SIZE 6}{SPACING 7}{ KOLLEGA:SLIGNER EFTERFAULT} + En kombination er en serie af to eller flere bevægelser, der, når de udfores i rækkefolge, gor betydelig skade og ikke kan blokeres efter det forste hit. + + Når du rammer med en combo, får du bonuspoint. Hvis du vil være en virkelig fremragende spiller, mestre så mange kombinationer som du kan finde. +{CENTER PÅ} + +ID: 13 +Title: Hjælp side 14 +Data: Dette skal være lille og normal storrelse. + +ID: 14 +Title: Hjælp side 15 +Data: Dette skal være lille og normal storrelse. + +ID: 15 +Title: Hjælp side 16 +Data: Dette skal være lille og normal storrelse. + +ID: 16 +Title: Hjælp side 17 +Data: Dette skal være lille og normal storrelse. + +ID: 17 +Title: Hjælp side 18 +Data: Dette skal være lille og normal storrelse. + +ID: 18 +Title: Hjælp side 19 +Data: Dette skal være lille og normal storrelse. + +ID: 19 +Title: Hjælp side 20 +Data: Dette skal være lille og normal storrelse. + +ID: 20 +Title: Piloter +Data: Krystal + +ID: 21 +Title: Piloter +Data: Steffan + +ID: 22 +Title: Piloter +Data: Milano + +ID: 23 +Title: Piloter +Data: Christian + +ID: 24 +Title: Piloter +Data: Shirro + +ID: 25 +Title: Piloter +Data: Jean-Paul + +ID: 26 +Title: Piloter +Data: Ibrahim + +ID: 27 +Title: Piloter +Data: Angel + +ID: 28 +Title: Piloter +Data: Cossette + +ID: 29 +Title: Piloter +Data: Raven + +ID: 30 +Title: Piloter +Data: Major Kreissack + +ID: 31 +Title: Robotter +Data: Jaguar + +ID: 32 +Title: Robotter +Data: Skygge + +ID: 33 +Title: Robotter +Data: Thorn + +ID: 34 +Title: Robotter +Data: Pyros + +ID: 35 +Title: Robotter +Data: Electra + +ID: 36 +Title: Robotter +Data: Katana + +ID: 37 +Title: Robotter +Data: Shredder + +ID: 38 +Title: Robotter +Data: Flail + +ID: 39 +Title: Robotter +Data: Gargoyle + +ID: 40 +Title: Robotter +Data: Chronos + +ID: 41 +Title: Robotter +Data: Nova + +ID: 42 +Title: Robotter +Data: +ID: 43 +Title: Robotter +Data: +ID: 44 +Title: Robotter +Data: +ID: 45 +Title: Robotter +Data: +ID: 46 +Title: Robotter +Data: +ID: 47 +Title: Robotter +Data: +ID: 48 +Title: Robotter +Data: +ID: 49 +Title: Robotter +Data: +ID: 50 +Title: Robotter +Data: +ID: 51 +Title: Robotter +Data: +ID: 52 +Title: Robotter +Data: +ID: 53 +Title: Robotter +Data: +ID: 54 +Title: Robotter +Data: +ID: 55 +Title: Robotter +Data: +ID: 56 +Title: Robotter +Data: Stadion + +ID: 57 +Title: Robotter +Data: Danger værelse + +ID: 58 +Title: Robotter +Data: Kraftværk + +ID: 59 +Title: Robotter +Data: Fire Pit + +ID: 60 +Title: Robotter +Data: Orkenen + +ID: 61 +Title: Robotter +Data: Fire Pit + +ID: 62 +Title: Robotter +Data: Fire Pit + +ID: 63 +Title: Robotter +Data: Fire Pit + +ID: 64 +Title: Robotter +Data: Fire Pit + +ID: 65 +Title: Robotter +Data: Fire Pit + +ID: 66 +Title: Arenaer +Data: Det er her W.A.R. maskiner får deres forste test. Offentligheden opfordres til at se begivenheden. + +ID: 67 +Title: Arenaer +Data: Dette var W.A.R.'s forste fare arena. Kombattanter skal undgå de farlige pigg. + +ID: 68 +Title: Arenaer +Data: Bygget inde i et lyn mod hedning, der er et pustet over modtageligt kraftanlæg, leverer væggene noget af et chok. + +ID: 69 +Title: Arenaer +Data: Den ultimative test, computere projicerer holografiske sfærer, der, når de rammer, antænder ildkugler under din fjendes fodder. + +ID: 70 +Title: Arenaer +Data: Bliv på tæerne og undvige angrebene på jagerflyene for at overleve i orkenen. + +ID: 71 +Title: Arenaer +Data: Den ultimative test: Computere projicerer holografiske kugler, der, når de rammer, antænder ildkugler under din fjendes fodder. + +ID: 72 +Title: Arenaer +Data: Den ultimative test, computere projicerer holografiske sfærer, der, når de rammer, antænder ildkugler under din fjendes fodder. + +ID: 73 +Title: Arenaer +Data: Den ultimative test, computere projicerer holografiske sfærer, der, når de rammer, antænder ildkugler under din fjendes fodder. + +ID: 74 +Title: Arenaer +Data: Den ultimative test, computere projicerer holografiske sfærer, der, når de rammer, antænder ildkugler under din fjendes fodder. + +ID: 75 +Title: Arenaer +Data: Den ultimative test, computere projicerer holografiske sfærer, der, når de rammer, antænder ildkugler under din fjendes fodder. + +ID: 76 +Title: Forste nyhedsreport +Data: Vil du bekæmpe denne udfordrer? + +ID: 77 +Title: Forste nyhedsreport +Data: Og i andre nyheder, efter ~1's kamp i aften, ~2, en urangeret konkurrent, har udstedt en offentlig udfordring til ~1. + +ID: 78 +Title: Forste nyhedsreport +Data: Nå, hvis ~8 accepterer udfordringen, kan du vædde dit liv, jeg vil være der for at se kampen! + +ID: 79 +Title: Forste nyhedsreport +Data: Jeg håber helt sikkert, at du ikke gik glip af denne kampfolk, for i aften har vi en ny MESMEKæmpe. + +ID: 80 +Title: Forste nyhedsreport +Data: 3 + +ID: 81 +Title: Forste nyhedsreport +Data: hans + +ID: 82 +Title: Forste nyhedsreport +Data: hende + +ID: 83 +Title: Forste nyhedsreport +Data: ham + +ID: 84 +Title: Forste nyhedsreport +Data: hende + +ID: 85 +Title: Forste nyhedsreport +Data: han + +ID: 86 +Title: Forste nyhedsreport +Data: hun + +ID: 87 +Title: Forste nyhedsreport +Data: ~1 viste utrolige evner med ~3 som ~2 blev lidt mere end en boksesæk. + +ID: 88 +Title: Anden nyhedsrapport +Data: Denne ene, samt andre fremragende hits, har sendt ~2 tilbage til butikken i et stykke tid. + +ID: 89 +Title: Forste nyhedsreport +Data: For alle jer folk, der nyder en jævnt matchet kamp, håber jeg, at du ikke betalte for kampen på ~5. + +ID: 90 +Title: Anden nyhedsrapport +Data: ...Men for alle jer, der kan lide den lejlighedsvise ensidige masochistiske puncing, så pas på ~1. + +ID: 91 +Title: Forste nyhedsreport +Data: Whoa, denne udfordrer betod forretning i aften. ~1 kunne vise de gamle professionelle en ting eller to om det ~3. + +ID: 92 +Title: Anden nyhedsrapport +Data: Tjek denne handling ud. Dette er, hvordan ~2 så hele kampen: slået, forslået og forvirret. + +ID: 93 +Title: Forste nyhedsreport +Data: Publikum jublede som ~2 blev besejret i det bemærkelsesværdige opgor med ~1. + +ID: 94 +Title: Anden nyhedsrapport +Data: ~1 glade glade tilskuere med respektable færdigheder. Her er det hit, der sluttede kampen. + +ID: 95 +Title: Forste nyhedsreport +Data: ~1 så godt ud i aften. ~6 ~3 forlod ~5 med kun et par ar fra ~6 overmatched modstander. + +ID: 96 +Title: Anden nyhedsrapport +Data: ~2 viste nogle temmelig patetiske kampfærdigheder, og ~1 udnyttede ham. Bare se på dette hit. + +ID: 97 +Title: Forste nyhedsreport +Data: ~5 blev rystet i aften af den imponerende ~1. ~2 har brug for mere praksis, for ~11 kan slå som ~7. + +ID: 98 +Title: Anden nyhedsrapport +Data: ~4 dele fyldte gulvet efter gentagne slag som dette. Jeg har næsten ondt af ~2's reparationshold. + +ID: 99 +Title: Forste nyhedsreport +Data: ~1 udkæmpede en respektabel kamp med ~2 og sejrede. Ikke halvt dårligt, ~1! + +ID: 100 +Title: Anden nyhedsrapport +Data: De gjorde begge nogle skader, men ~1 sluttede konkurrencen med dette skud. + +ID: 101 +Title: Forste nyhedsreport +Data: ...Og i arenaen i aften, ~2 gav ~1 et lob for ~6 penge, men kom op et par tusinde kreditter kort. + +ID: 102 +Title: Anden nyhedsrapport +Data: ~2 holdt derinde i et stykke tid, men ~1 simpelthen ud-præsteret ~10 med bevægelser som dette. + +ID: 103 +Title: Forste nyhedsreport +Data: Alle jer arena junkies derude fik et ret godt show på ~5. ~2 kæmpede med dygtighed, men ikke nok... + +ID: 104 +Title: Anden nyhedsrapport +Data: ...at lægge ned ~1 s hulking ~3. Hey, måske næste gang, ~2. + +ID: 105 +Title: Forste nyhedsreport +Data: ~1 og ~2 blev jævnt matchet. ~4 af ~2 vil tilbringe lidt tid i butikken i aften! + +ID: 106 +Title: Anden nyhedsrapport +Data: Disse to kæmpede praktisk talt blow for blow indtil ~1 endelig naglet ~2 med dette veltimede skud. + +ID: 107 +Title: Forste nyhedsreport +Data: Jeg håber, ~1 og ~2's reparationsbesætninger ikke planlagde nogen fester i aften. + +ID: 108 +Title: Anden nyhedsrapport +Data: Med den middelmådige præstation af ~1, ~6 ~3 reparation besætning vil sidde fast i butikken næsten lige så længe ~6 modstanderens. + +ID: 109 +Title: Forste nyhedsreport +Data: Nu var dette en lige match-up, hvis jeg nogensinde har set en. Hvis de kæmpede denne kamp i morgen, ~2 kunne lige så nemt vinde. + +ID: 110 +Title: Anden nyhedsrapport +Data: ~1's sidste, snublende slag sluttede kampen i, hvad der kun kunne kaldes en heldig sejr. + +ID: 111 +Title: Forste nyhedsreport +Data: Whew, ~2 virkelig rev ind i ~1. ~1 bor nok tage det ~3 tilbage til træningsbanen. + +ID: 112 +Title: Anden nyhedsrapport +Data: Alt jeg kan sige til ~1 er at få noget praksis. At ~3 ikke vinder uden din hjælp. + +ID: 113 +Title: Forste nyhedsreport +Data: Jeg vil opsummere denne ene op for alle jer sportsfans i to ord: TRACK MEET. ~2 lob cirkler rundt, og nogle gange over, + +ID: 114 +Title: Anden nyhedsrapport +Data: ~1 og ~6 skæbnesvangert ~3. ~1 bor begynde at lave væddemål mod ~7self, hvis ~8 skal tage dyk som denne. + +ID: 115 +Title: Forste nyhedsreport +Data: Min, min... Hvem havde kontrol over det ~3! Åh, det var ~1. Jeg har nogle råd til dig, ~1. + +ID: 116 +Title: Anden nyhedsrapport +Data: And, blok, dodge, tilkald sig syg, gor noget andet end at tage hit efter hit fra det ~4. + +ID: 117 +Title: Forste nyhedsreport +Data: Denne kamp var temmelig ensidig. ~2 dominerede kampen mod den overvældede ~1. + +ID: 118 +Title: Anden nyhedsrapport +Data: Opforelsen af ~2 var simpelthen fremragende. ~1 kunne ikke reagere hurtigt nok til at stoppe stormlobet. + +ID: 119 +Title: Forste nyhedsreport +Data: ~2 ved virkelig, hvad han laver derude, eller måske ~1 har simpelthen ingen anelse. Uanset hvad, var det et blowout. + +ID: 120 +Title: Anden nyhedsrapport +Data: Der var et par heldige skud ved ~1, men det meste af tiden kampen så sådan ud. + +ID: 121 +Title: Forste nyhedsreport +Data: De skal være temmelig korte på piloter i disse dage for at rekruttere lignende ~1. + +ID: 122 +Title: Anden nyhedsrapport +Data: Jeg kunne have blokeret dette sidste hit i min sovn. Måske er det problemet. Nogen vågner op ~1. + +ID: 123 +Title: Forste nyhedsreport +Data: En god præstation af ~1, men naturligvis ikke nok til at konkurrere ~2's ~4 + +ID: 124 +Title: Anden nyhedsrapport +Data: ~1's dygtighed er indlysende, men dette skud viser virkelig, hvad en ~4 kan gore i hænderne på ~2. + +ID: 125 +Title: Forste nyhedsreport +Data: Åh, en dårlig dag i livet af ~1. Jeg er sikker på ~2 vil ikke fælde nogen tårer over ~6 tab. + +ID: 126 +Title: Anden nyhedsrapport +Data: ~1 landede nogle ret gode skud, men blev slidt ned af jordrystende skud som denne. + +ID: 127 +Title: Forste nyhedsreport +Data: Hmm. ~1 viste nogle mod i ~5 mod ~2 i aften, men viste bare ikke nok dygtighed. + +ID: 128 +Title: Anden nyhedsrapport +Data: ~1 stod ~6 jorden modigt, men blev til sidst efterladt liggende på det. Nå ~1, der er altid i morgen. + +ID: 129 +Title: Forste nyhedsreport +Data: Flot forsog ~1. Det var så tæt på, som de kommer, folkens. ~2 må være taknemmelig for... + +ID: 130 +Title: Anden nyhedsrapport +Data: blæser som denne. Det var skud som dette, der holdt ~2 bare et halvt skridt foran ~1. + +ID: 131 +Title: Forste nyhedsreport +Data: Wow, det var en tæt en. ~1 og ~2 handlede slag i ~5 indtil... + +ID: 132 +Title: Anden nyhedsrapport +Data: ~2, træt og snuble, landede dette, det sidste slag. Næste gang disse to modes, vil det være en rematch. + +ID: 133 +Title: Forste nyhedsreport +Data: Folkens, du skal have lidt ondt af ~1 i aften. Hvis det ikke var til ~2 landing... + +ID: 134 +Title: Anden nyhedsrapport +Data: et par heldige slag som denne, jeg tror ~1 ville have trukket denne ene ud. + +ID: 135 +Title: Beskrivelser 1 +Data: Tvunget til at klare sig selv efter hendes forældres mystiske dod, har Crystals beslutsomhed opnået stor respekt. + +ID: 136 +Title: Beskrivelser 2 +Data: Selvom han er den yngste konkurrent i turneringens historie, kæmper Stoffan med dygtighed ud over sine år. + +ID: 137 +Title: Beskrivelser 3 +Data: Rekrutteret af Raven for hans fremragende kickboxing færdigheder, Milanos hastighed og fingerfærdighed er legendariske. + +ID: 138 +Title: Beskrivelse 4 +Data: Christians aggressive stil rammer frygt i konkurrenternes sind og begær i mange unge kvinders hjerter. + +ID: 139 +Title: Beskrivelser 5 +Data: I lobet af sine mange års konkurrence har Shirro udviklet uovertruffen magt, men bevarer en ungdommelig sans for humor. + +ID: 140 +Title: Beskrivelser 6 +Data: Hans beregnende og luskede natur sammen med velafrundede evner skræmmer ofte Jean-Pauls modstandere. + +ID: 141 +Title: Beskrivelser 7 +Data: En pensioneret triatlet, Ibrahims tålmodighed og ærlighed har gjort ham til en værdsat mentor for mange håbefulde konkurrenter. + +ID: 142 +Title: Beskrivelser 8 +Data: Angels fortid er indhyllet i mystik. Bortset fra hendes reclusive disposition og stærke vilje, er lidt kendt om hende. + +ID: 143 +Title: Beskrivelser 9 +Data: En veteran fighter, Cossette er blevet forsigtig, defensiv og bitter efter at være blevet forkroblet i arenaen. + +ID: 144 +Title: Beskrivelser 10 +Data: Som stor Kreissacks livvagt og hojre hånd har Raven slået og ydmyget mange inden for og udenfor, arenaen. + +ID: 145 +Title: Beskrivelser 11 +Data: Boss fyrs beskrivelse. Dette vil virkelig ikke blive set, men vi vil gore plads til det alligevel. + +ID: 146 +Title: Shareware Msgs +Data: Beklager, denne pilot er kun tilgængelig i den fulde version af spillet. Se BESTILLING INFO i hovedmenuen. + +ID: 147 +Title: Shareware Msgs +Data: Beklager, denne robot er kun tilgængelig i den fulde version af spillet. Se BESTILLING INFO i hovedmenuen. + +ID: 148 +Title: Shareware Msgs +Data: Beklager, denne turnering er kun tilgængelig i den fulde version af spillet. Se BESTILLING INFO i hovedmenuen. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 149 +Title: Fejl +Data: *Enten spiller holdt op med at svare! + +ID: 150 +Title: Fejl +Data: UD AF SYNC-FEJL + +ID: 151 +Title: Fejl +Data: Joystick %i synes at være kalibreret forkert. Onsker du at omkalibrere? + +ID: 152 +Title: Fejl +Data: FULD +VERSION +KUN + +ID: 153 +Title: Fejl +Data: Ude af mulighed for at initialisere joystick %i. Skifte af spiller %i input til tastaturet. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 154 +Title: Fejl +Data: Beklager, den anden spiller bruger dette. Vælg venligst en anden. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 155 +Title: Fejl +Data: Kan ikke finde nogen turneringsfiler. På en eller anden måde er alle filer med en TRN-udvidelse blevet fjernet fra mappen. Du skal geninstallere OMF. + +ID: 156 +Title: Fejl +Data: Ikke kan gemme fil %s. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 157 +Title: Fejl +Data: INGEN PILOTER ER TILGÆNGELIGER. OPLEV EN NY PILOT. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 158 +Title: Fejl +Data: DEN EGEN OG EGEN PILOT PÅ FILEN ER DER ENHEDER, DER ER FYRET. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 159 +Title: Fejl +Data: INGEN PILOTER TIL RÅDGIVENDE FOR SLETNING. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 160 +Title: Fejl +Data: Ikke i stand til at finde joystick. Sorg for, at joysticket er tilsluttet din joystick port, for du korer OMF. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 161 +Title: Fejl +Data: Denne maskine har ikke nok konventionel hukommelse til at kore lyd og musik. %lik er påkrævet for at kore lyde, og %lik er påkrævet for både lyd og musik. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 162 +Title: Fejl +Data: Denne maskine har ikke nok konventionel hukommelse (%lik nodvendigt) til at kore musik. Musik er handicappet. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 163 +Title: Fejl +Data: Denne maskine har ikke nok konventionel hukommelse (%lik påkrævet) til at kore lyd. Lyden handicappet. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 164 +Title: Fejl +Data: Denne maskine har ikke nok konventionel hukommelse. %lik er påkrævet. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 165 +Title: Fejl +Data: Beklager, denne maskine har mindre end de mindste XMS krav. +%lik, %li flere bytes er nodvendige for at kore One Must Fall 2097. + + +ID: 166 +Title: Konventionel hukommelse +Data: Lob tor for hukommelsen. +Prov at frigore mere overhukommelse. + +ID: 167 +Title: Konventionel hukommelse +Data: Fejlåbningsfil: %s + +ID: 168 +Title: Konventionel hukommelse +Data: Lob tor for XMS. + +ID: 169 +Title: MASI Musik belastning fejl +Data: Musikbelastningsfejl #%i, indlæsning af musikfil %s. + +ID: 170 +Title: MASI Musik belastning fejl +Data: mSampleLoad fejl #%i. + +ID: 171 +Title: MASI Musik belastning fejl +Data: FORSOGTE AT INDLÆSES STORRE END ARKSPAGT. +NODVENDIGT AT UDVÆKSLE ARKSRUPSSTORRELSE. + +ID: 172 +Title: Optag fil ting +Data: Din modstander ændrer systemindstillinger. + +Vær sod at vente. + +ID: 173 +Title: Optag fil ting +Data: Filtrets %s er blevet ændret eller beskadiget og er ikke længere kompatibel med din modstanders maskine. Du skal reparere denne fil eller geninstallere spillet. + +ID: 174 +Title: Optag fil ting +Data: Filerne på din modstanders maskine er blevet ændret eller beskadiget og er ikke længere kompatibel med din maskine. + +ID: 175 +Title: Optag fil ting +Data: Din modstander har travlt med at gore noget. + +Vær sod at vente. + +ID: 176 +Title: Optag fil ting +Data: RECORD FIL FEJLM + +ID: 177 +Title: Chat tekst +Data: Din modstander har forladt spillet. + +ID: 178 +Title: Chat tekst +Data: SPILLER + +ID: 179 +Title: Chat tekst +Data: DEN ANDEN FYR + +ID: 180 +Title: Player quote when reaching han +Data: Den whimp er ikke her endnu! Jeg hader virkelig at vente. + +ID: 181 +Title: ! +Data: Din modstander er endnu ikke ankommet + +ID: 182 +Title: ! +Data: Din modstander spiller i turneringen. Du skal vælge TURNERING PLAY og udfordre ham med en reddet pilot. + +ID: 183 +Title: ! +Data: Din modstander spiller et tospillerspil. Du skal vælge to SPILLER GAME for at udfordre ham. + +ID: 184 +Title: Used on startup as non-server +Data: Gameplay muligheder er blevet kopieret fra din modstander. + +PRESSE G FOR GAMEPLAY MENUEN + +ID: 185 +Title: Brugt på opstart som server +Data: Gameplay muligheder er blevet sendt til din modstander. + +PRESSE G FOR GAMEPLAY MENUEN + +ID: 186 +Title: Vælg robot i det normale spil +Data: VÆLG DIN ROBOT + +ID: 187 +Title: Vælg pilot i det normale spil +Data: VÆLG DIN PILOT + +ID: 188 +Title: Engelsk exit tekst. +Data: OMF_END.BIN + +ID: 189 +Title: for hemmelig karakter ild. +Data: BRAGE + +ID: 190 +Title: for hemmelig karakter is. +Data: ICE + +ID: 191 +Title: til oprettelse af pilot +Data: $ %sk + +ID: 192 +Title: til oprettelse af pilot +Data: INTER PILOT'S NAVN + +ID: 193 +Title: for spiller ved hjælp af robot +Data: MED + +ID: 194 +Title: Resultattavle +Data: SCOREBOARD - + +ID: 195 +Title: Resultattavle +Data: %d af %d + +ID: 196 +Title: Resultattavle +Data: SPILLER NAVN ROBOT PILOT SCORE + +ID: 197 +Title: Velkommen til opsætning +Data: DEL AF ARENAEN + +ID: 198 +Title: Velkommen til opsætning +Data: Tryk på PGDN eller PGUP for at ændre side +Presse ESC til at afslutte hjælp + +ID: 199 +Title: Velkommen til opsætning +Data: SIDE + +ID: 200 +Title: Velkommen til opsætning +Data: VS. + +ID: 201 +Title: Velkommen til opsætning +Data: HINANDEN FOLGER AF HITS + +ID: 202 +Title: Velkommen til opsætning +Data: HIT COMBO + +ID: 203 +Title: Velkommen til opsætning +Data: PERFEKT RUNDE + +ID: 204 +Title: Velkommen til opsætning +Data: VITALITET + +ID: 205 +Title: Velkommen til opsætning +Data: SCRAP BONUS + +ID: 206 +Title: Velkommen til opsætning +Data: DESTRUKTIONSBONUS + +ID: 207 +Title: Velkommen til opsætning +Data: Type OMF til at starte spillet. + + +ID: 208 +Title: Velkommen til opsætning +Data: Kor Venligst SETUP.EXE for du korer OMF.EXE + + +ID: 209 +Title: Velkommen til opsætning +Data: Hav en dejlig dag. + + +ID: 210 +Title: Velkommen til opsætning +Data: Der opstod fejl på linje %d af modul %s + + +ID: 211 +Title: Velkommen til opsætning +Data: SPIL PAUSED + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 212 +Title: Sejr +Data: Er du sikker på, at du vil afslutte dette spil? + +ID: 213 +Title: Sejr +Data: Onsker du at fortsætte? + +ID: 214 +Title: Joystick Kalibrering +Data: KALIBRATE JOYSTICK + +ID: 215 +Title: Joystick Kalibrering +Data: Kalibrere joystick ved at bevæge sig i en komplet cirkel. At lade joystick 'rest' i midten skal medfore, at midterknappen trykkes på. Tryk på ESC til abort eller joystick-knap for at acceptere. + +ID: 216 +Title: Magt +Data: POWER + +ID: 217 +Title: Hastighed +Data: AGILITET + +ID: 218 +Title: Udholdelighed +Data: UDHOLDENHED + +ID: 219 +Title: Hyper eller Normal +Data: NORMAL + +ID: 220 +Title: Hyper eller Normal +Data: HYPER + +ID: 221 +Title: Vælg +Data: EN ANDEN PILOT VED NAVNEN PÅ %S HAR DET SAMME FILENAME. OVER DU OVERSKRIV DEN PILOT? + +ID: 222 +Title: Vælg +Data: ER DU SIKRE DIG TIL AT SLETTE PILOT %s? + +ID: 223 +Title: Vælg +Data: SELECT + +ID: 224 +Title: Vælg +Data: VÆLG FOTO TIL PILOT %s + +ID: 225 +Title: Vælg +Data: VÆLG PILOT TIL AT BÆRE BORD + +ID: 226 +Title: Vælg +Data: VÆLG PILOT TIL SLETNING + +ID: 227 +Title: Vælg +Data: VÆLG MODSTANDER + +ID: 228 +Title: På eller uden for +Data: NEJ + +ID: 229 +Title: På eller uden for +Data: JA + +ID: 230 +Title: På eller uden for +Data: OFF + +ID: 231 +Title: På eller uden for +Data: PÆLE + +ID: 232 +Title: Hurtigste +Data: HASTEST + +ID: 233 +Title: Langsomste +Data: SLIDSTE + +ID: 234 +Title: En runde +Data: ONE ROUNDa + +ID: 235 +Title: En runde +Data: NORMAL + +ID: 236 +Title: En runde +Data: LAV + +ID: 237 +Title: Hurtigt eller normalt +Data: NORMAL + +ID: 238 +Title: Hurtigt eller normalt +Data: HAST + +ID: 239 +Title: Undskyld +Data: Beklager, HYPER mode er kun tilgængelig i den fulde version af spillet. Se BESTILLING INFO i hovedmenuen. + +Tryk på en nogle til at fortsætte. + +ID: 240 +Title: Lydkort +Data: VÆLG LYDKORT + +ID: 241 +Title: Lydkort +Data: Vælg lydkort. Hvis du ikke har et lydkort, skal du vælge PC SPEAKER. PC hojttaler lyd kan deaktiveres fra spillet, hvis du foretrækker stilhed. + +ID: 242 +Title: Lydkort +Data: PC Hojttaler + +ID: 243 +Title: Lydkort +Data: Kan ikke initialisere lydkort. Tjek dine lydkortindstillinger og sorg for, at IRQ, DMA og base IO-adresse overholder. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 244 +Title: Lydkort +Data: INGEN + +ID: 245 +Title: Lydkort +Data: LAV + +ID: 246 +Title: Lydkort +Data: MEDIUM + +ID: 247 +Title: Lydkort +Data: HOJ + +ID: 248 +Title: Mulighed 1 +Data: ET SPILLER GAMEa + +ID: 249 +Title: Mulighed 2 +Data: TO SPILLER GAMEa + +ID: 250 +Title: Mulighed 2 +Data: TURNERING PLAYA + +ID: 251 +Title: Mulighed 3 +Data: KONFIGURATION + +ID: 252 +Title: Mulighed 4 +Data: GAMEPLAYa + +ID: 253 +Title: Mulighed 5 +Data: BESTILLING AF INFOa + +ID: 254 +Title: Mulighed 6 +Data: HELPa + +ID: 255 +Title: Mulighed 7 +Data: DEMOa + +ID: 256 +Title: Mulighed 8 +Data: SCOREBOARD + +ID: 257 +Title: Mulighed 9 +Data: QUITa + +ID: 258 +Title: Titel +Data: AVANCEREDE MULIGELSER + +ID: 259 +Title: Mulighed 1 +Data: REHIT MODE %sa + +ID: 260 +Title: Mulighed 2 +Data: DEF. KAST SA % + +ID: 261 +Title: Mulighed 3 +Data: KAJ RANGE %i%%a + +ID: 262 +Title: Mulighed 4 +Data: JUMP HOJDE %i%%1 + +ID: 263 +Title: Mulighed 5 +Data: HIT PAUSE %ia + +ID: 264 +Title: Mulighed 6 +Data: VITALITET x %i%%a + +ID: 265 +Title: Mulighed 7 +Data: KNOCK DOWN %sa + +ID: 266 +Title: Mulighed 7 +Data: BLOK SKADE %i%%a + +ID: 267 +Title: Mulighed 5 +Data: FORBEDRE %s + +ID: 268 +Title: Mulighed 8 +Data: DONEa + +ID: 269 +Title: Mulighed 8 +Data: INGEN + +ID: 270 +Title: Mulighed 8 +Data: Nod 1 + +ID: 271 +Title: Mulighed 8 +Data: NIVILLE 2 + +ID: 272 +Title: Mulighed 8 +Data: INGEN + +ID: 273 +Title: Mulighed 8 +Data: SLAG + +ID: 274 +Title: Mulighed 8 +Data: PUNCHES + +ID: 275 +Title: Mulighed 8 +Data: BGEDE + +ID: 276 +Title: Mulighed 1 +Data: Dette vil tillade flere hits til en luftbåren fjende. Men efter et træk har forbundet det kan ikke længere genhit. Hvis du blander dine bevægelser, kan du få nogle vilde "jongler" kombinationer. "Ekstra" hits vil kun påfore 60% skader. + +ID: 277 +Title: Mulighed 1 +Data: Tillad spilleren at kaste fjende fra en defensiv (ryg) position. + +ID: 278 +Title: Mulighed 1 +Data: Ændre den afstand, hvorfra en spiller kan kaste sin fjende (Standarder til 100%). + +ID: 279 +Title: Mulighed 1 +Data: Skift hojden af spillerne hopper (Gælder til 100%). Dette har ingen effekt i turneringstilstand. + +ID: 280 +Title: Mulighed 1 +Data: Dette påvirker den tid, spillet holder pause under et hit eller en blok (Standard til 4). + +ID: 281 +Title: Mulighed 1 +Data: Dette vil påvirke mængden af hits en spiller kan tage. Forogelse af dette vil give mulighed for længere kampe (Defaults til 100%). Dette har ingen effekt i turneringstilstand. + +ID: 282 +Title: Mulighed 1 +Data: Dette vil få det angivne springende træk at vælte modstanderen, når den rammer. + +ID: 283 +Title: Mulighed 1 +Data: Dette vil medfore, at skaden påfores, når ethvert træk er blokeret. + +ID: 284 +Title: Mulighed 1 +Data: Dette vil automatisk forbedre begge robotter i et netværksspil til det angivne niveau. + +ID: 285 +Title: Mulighed 1 +Data: Afslut AVANCEREDE OMSTEDER. + +ID: 286 +Title: Titel +Data: GAMEPLAY + +ID: 287 +Title: Mulighed 1 +Data: SPEED %sa + +ID: 288 +Title: Mulighed 2 +Data: FIGHT MODE %sa + +ID: 289 +Title: Mulighed 3 +Data: POWER 1 %sa + +ID: 290 +Title: Mulighed 4 +Data: POWER 2 %sa + +ID: 291 +Title: Mulighed 5 +Data: FARER %sa + +ID: 292 +Title: Mulighed 6 +Data: %sa + +ID: 293 +Title: Mulighed 7 +Data: BEDSTE %i %ia af % + +ID: 294 +Title: Mulighed 7 +Data: AVANCEREDE OPTIONER + +ID: 295 +Title: Mulighed 7 +Data: KONFIGURATION + +ID: 296 +Title: Mulighed 8 +Data: DONEa + +ID: 297 +Title: Mulighed 1 +Data: Ændre den samlede hastighed af spillet. Tryk til venstre og hojre for at ændre. + +ID: 298 +Title: Mulighed 1 +Data: Kamptilstand kan være enten NORMAL eller HYPER. Hypertilstand vil forbedre dine specielle bevægelser. Tjek robotbeskrivelsesafsnitt af hjælpen for mere information. + +ID: 299 +Title: Mulighed 1 +Data: Ændre kraften i spiller 1's hits og kast. Denne indstilling træder kun i kraft i to spillerspil. Tryk til venstre og hojre for at ændre. + +ID: 300 +Title: Mulighed 1 +Data: Ændre kraften i player 2's hits og kast. Denne indstilling træder kun i kraft i to spillerspil. Tryk til venstre og hojre for at ændre. + +ID: 301 +Title: Mulighed 1 +Data: Nogle arenaer har farlige miljoer: pigge, elektricitet, kampfly og lignende. Denne indstilling tænder og slukker dem. + +ID: 302 +Title: Mulighed 1 +Data: Dette bestemmer, hvor godt computeren kæmper i et enspiller spil. Dette har ingen effekt på to spiller spil. Tryk til venstre og hojre for at ændre. + +ID: 303 +Title: Mulighed 1 +Data: Dette vil oprette kampe, så de er en runde, bedste to ud af tre runder, eller bedste tre ud af fem runder. + +ID: 304 +Title: Mulighed 1 +Data: Skal jeg virkelig fortælle dig, hvad det er? + +ID: 305 +Title: Mulighed 1 +Data: Indstil forskellige spilmuligheder. + +ID: 306 +Title: Mulighed 1 +Data: Gå tilbage til hovedmenuen. + +ID: 307 +Title: Titel +Data: OMF 2097 + +ID: 308 +Title: Titel +Data: VENDE TILBAGE TIL GAMEa + +ID: 309 +Title: Titel +Data: LYD %sa + +ID: 310 +Title: Titel +Data: MUSIC %sa + +ID: 311 +Title: Titel +Data: SPEED %sa + +ID: 312 +Title: Titel +Data: VIDEO OPTIONSa + +ID: 313 +Title: Titel +Data: HELPa + +ID: 314 +Title: Titel +Data: QUITa + +ID: 315 +Title: Titel +Data: FORFEITa + +ID: 316 +Title: Titel +Data: Fortsæt kampen. + +ID: 317 +Title: Titel +Data: Hæv eller sænk mængden af alle lydeffekter. Tryk til venstre eller hojre for at ændre. + +ID: 318 +Title: Titel +Data: Hæv eller lav lydstyrken af musik. Tryk til venstre eller hojre for at ændre. + +ID: 319 +Title: Titel +Data: Ændre hastigheden af spillet, når i arenaen. Tryk til venstre eller hojre for at ændre. + +ID: 320 +Title: Titel +Data: Disse er forskellige muligheder for visuelle effekter og detaljer. + +ID: 321 +Title: Titel +Data: Få detaljeret og grundig forklaring på de forskellige muligheder, som du måske har brug for en detaljeret og grundig forklaring på. + +ID: 322 +Title: Titel +Data: Afslut spillet og vende tilbage til hovedmenuen. + +ID: 323 +Title: Titel +Data: Du vil tabe kampen og stadig betale for reparationer på din robot. + +ID: 324 +Title: Option 1 +Data: ⌂||||||| + +ID: 325 +Title: Option 2 +Data: ⌂⌂|||||| + +ID: 326 +Title: Option 1 +Data: ⌂⌂⌂||||| + +ID: 327 +Title: Option 1 +Data: ⌂⌂⌂⌂|||| + +ID: 328 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂||| + +ID: 329 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂|| + +ID: 330 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂⌂| + +ID: 331 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂⌂⌂ + +ID: 332 +Title: Option 1 +Data: |||||||||| + +ID: 333 +Title: Option 1 +Data: ⌂||||||||| + +ID: 334 +Title: Option 2 +Data: ⌂⌂|||||||| + +ID: 335 +Title: Option 1 +Data: ⌂⌂⌂||||||| + +ID: 336 +Title: Option 1 +Data: ⌂⌂⌂⌂|||||| + +ID: 337 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂||||| + +ID: 338 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂|||| + +ID: 339 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂⌂||| + +ID: 340 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂⌂⌂|| + +ID: 341 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂⌂⌂⌂| + +ID: 342 +Title: Option 1 +Data: ⌂⌂⌂⌂⌂⌂⌂⌂⌂⌂ + +ID: 343 +Title: Diff 1 +Data: CPU: PUNCHING BAG + +ID: 344 +Title: Diff 1 +Data: CPU: ROOKIE + +ID: 345 +Title: Diff 1 +Data: CPU: VETERAN + +ID: 346 +Title: Diff 1 +Data: CPU: WORLD CLASS + +ID: 347 +Title: Diff 1 +Data: CPU: CHAMPION + +ID: 348 +Title: Diff 1 +Data: CPU: DODBRINGENDE + +ID: 349 +Title: Diff 1 +Data: CPU: ULTIMAT + +ID: 350 +Title: Titel +Data: BRUGERDSKÆRM FOR KEYBOARD SETUP + +ID: 351 +Title: vælg 1 +Data: JUMPE UP + +ID: 352 +Title: vælg 1 +Data: JUMP HOJRE + +ID: 353 +Title: vælg 1 +Data: GÅ RIGTIGT + +ID: 354 +Title: vælg 1 +Data: DUCK FREMAD + +ID: 355 +Title: vælg 1 +Data: DUCK + +ID: 356 +Title: vælg 1 +Data: DUCK TILBAGE + +ID: 357 +Title: vælg 1 +Data: GÅ TILBAGE + +ID: 358 +Title: vælg 1 +Data: JUMP LEFT + +ID: 359 +Title: vælg 1 +Data: PUNCH + +ID: 360 +Title: vælg 1 +Data: KICK + +ID: 361 +Title: vælg 1 +Data: DONEa + +ID: 362 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 363 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 364 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 365 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 366 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 367 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 368 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 369 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 370 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 371 +Title: vælg 1 +Data: Tryk på Enter for at ændre tasten, og tryk derefter på noglen, der er onsket for den angivne handling. Tryk på ESC, når det er gjort. + +ID: 372 +Title: vælg 1 +Data: Efterlad den brugerdefinerede tastaturopsætning. + +ID: 373 +Title: Shadows +Data: VIDEOOPTIONER + +ID: 374 +Title: Shadows +Data: SHADOWS %sa + +ID: 375 +Title: Shadows +Data: ANIMATION %sa + +ID: 376 +Title: Shadows +Data: PALETTE ANIM %sa + +ID: 377 +Title: Shadows +Data: SNOW CHECKING %sa + +ID: 378 +Title: Shadows +Data: SKÆRME SHAKES %sa + +ID: 379 +Title: Shadows +Data: MENUS %s + +ID: 380 +Title: Shadows +Data: NULSTILLET TIL DEFAULTA + +ID: 381 +Title: Shadows +Data: DONEa + +ID: 382 +Title: hjælp1 +Data: Drej robotskygger til og fra. Sluk for at forbedre spillets hastighed, mens du er i arenaen. + +ID: 383 +Title: hjælp1 +Data: Drej ekstra animation til og fra. Hvis du slukker dette, vil spillet kore hurtigere, men vil ikke se ud som detaljer. + +ID: 384 +Title: hjælp1 +Data: Dette vil slukke for al paletanimation: lynglimt, flammeflimmer og lignende. Sluk for hurtigere spil, eller hvis skærmen flimrer. + +ID: 385 +Title: hjælp1 +Data: At slukke dette vil fremskynde palette animationer, men kan forårsage sne eller flimmer med nogle videokort. + +ID: 386 +Title: hjælp1 +Data: Sluk for at fjerne skærmen 'rystende', når en karakter rammer væggen, en karakter kastes osv. + +ID: 387 +Title: hjælp1 +Data: Normale menuer ser godt ud, men kan være lidt langsomme på nogle maskiner. Ændringer vil finde sted efter at have forladt menuerne. + +ID: 388 +Title: hjælp1 +Data: Dette vil gendanne alle videoindstillinger til standardindstillingerne. + +ID: 389 +Title: hjælp1 +Data: Udrejse fra denne menu. + +ID: 390 +Title: Kvalitet +Data: LAV + +ID: 391 +Title: Kvalitet +Data: MEDIUM + +ID: 392 +Title: Kvalitet +Data: HOJ + +ID: 393 +Title: Titel +Data: LYDKORTSTOMNING + +ID: 394 +Title: Opt 1 +Data: KVALITET %sa + +ID: 395 +Title: Opt 1 +Data: NONA + +ID: 396 +Title: Opt 1 +Data: IO PORT %xha + +ID: 397 +Title: Opt 1 +Data: 8-BIT DMA %ia + +ID: 398 +Title: Opt 1 +Data: 16-BIT DMA %ia + +ID: 399 +Title: Opt 1 +Data: IRQ %ia + +ID: 400 +Title: Opt 1 +Data: MIDI IO PORT %xha + +ID: 401 +Title: Opt 1 +Data: MIDI IRQ %ia + +ID: 402 +Title: Opt 1 +Data: DONEa + +ID: 403 +Title: Kvalitet +Data: Indstilling af lydkvaliteten lav kan hjælpe den samlede hastighed på langsommere maskiner. Dette påvirker ikke PC Speaker og nogle lydkort, som Gravis Ultra Sound. + +ID: 404 +Title: Kvalitet +Data: Vælg et lydkort for at afspille lydeffekter og musik. + +ID: 405 +Title: Kvalitet +Data: Vælg IO-adressen på kortet. Tryk på VENSTRE eller HOJRE for at ændre værdien. + +ID: 406 +Title: Kvalitet +Data: Vælg den 8-bit DMA-kanal af kortet. Tryk på VENSTRE eller HOJRE for at ændre værdien. + +ID: 407 +Title: Kvalitet +Data: Vælg kortets 16-bit DMA-kanal. Tryk på VENSTRE eller HOJRE for at ændre værdien. + +ID: 408 +Title: Kvalitet +Data: Vælg kortets IRQ-nummer. Tryk på VENSTRE eller HOJRE for at ændre værdien. + +ID: 409 +Title: Kvalitet +Data: Vælg kortets MIDI IO-adresse. Tryk på VENSTRE eller HOJRE for at ændre værdien. + +ID: 410 +Title: Kvalitet +Data: Vælg kortets MIDI IRQ. Tryk på VENSTRE eller HOJRE for at ændre værdien. + +ID: 411 +Title: Kvalitet +Data: Exit SETUP. + +ID: 412 +Title: Titel +Data: KONFIGURATION + +ID: 413 +Title: Mulighed +Data: SPILLER 1 INPUTA + +ID: 414 +Title: Mulighed +Data: SPILLER 2 INPUTA + +ID: 415 +Title: Mulighed +Data: VIDEO OPTIONSa + +ID: 416 +Title: Mulighed +Data: LYD %sa + +ID: 417 +Title: Mulighed +Data: MUSIC %sa + +ID: 418 +Title: Mulighed +Data: LYD TEST %ia + +ID: 419 +Title: Mulighed +Data: STEREO %sa + +ID: 420 +Title: Mulighed +Data: DONEa + +ID: 421 +Title: Mulighed +Data: NORMAL + +ID: 422 +Title: Mulighed +Data: VENDT + +ID: 423 +Title: Mulighed +Data: Vælg kontrol for spiller 1: tastatur eller joystick. + +ID: 424 +Title: Mulighed +Data: Vælg kontrol for spiller 2: tastatur eller joystick. + +ID: 425 +Title: Mulighed +Data: Forskellige muligheder for visuelle effekter og detaljeringsniveauer. + +ID: 426 +Title: Mulighed +Data: Hæv eller sænk mængden af alle lydeffekter. Tryk til venstre eller hojre for at ændre. + +ID: 427 +Title: Mulighed +Data: Hæv eller lav lydstyrken af musik. Tryk til venstre eller hojre for at ændre. + +ID: 428 +Title: Mulighed +Data: UNDEFFEKT TEST. Tryk på venstre og hojre for at ændre lydeffekt, og ind for at spille den. + +ID: 429 +Title: Mulighed +Data: Skift stereolyd mellem venstre og hojre hojttalere. + +ID: 430 +Title: Mulighed +Data: Forlad KONFIGURATION. + +ID: 431 +Title: Titel +Data: VÆLG INPUT ENGÆNGEJENED FOR SPILLER %i + +ID: 432 +Title: Mulighed +Data: RIGTIGE KEYBOARDa + +ID: 433 +Title: Mulighed +Data: VENSTRE KEYBOARDa + +ID: 434 +Title: Mulighed +Data: BRUGERDEFINEREDE KEYBOARDa + +ID: 435 +Title: Mulighed +Data: JOYSTICK 1a + +ID: 436 +Title: Mulighed +Data: JOYSTICK 2a + +ID: 437 +Title: Mulighed +Data: DONEa + +ID: 438 +Title: Mulighed +Data: Dette vil bruge det numeriske tastatur til bevægelse, indtaste til punch og hojre skift til spark. + +ID: 439 +Title: Mulighed +Data: Dette vil sætte 'Q', 'W', og 'E' til spring retninger, 'A' og 'D' til venstre og hojre og 'Z', 'X', og 'C' til edsvinding. TAB og CTRL kontrol stansning og spark. + +ID: 440 +Title: Mulighed +Data: Invent dine egne tastaturindstillinger. + +ID: 441 +Title: Mulighed +Data: Brug joystick 1. + +ID: 442 +Title: Mulighed +Data: Brug joystick 2. Dette er kun tilgængeligt, hvis du har et specielt kabel til at forbinde to joysticks til din computer. + +ID: 443 +Title: Mulighed +Data: Forlad uden at ændre noget. + +ID: 444 +Title: Navne +Data: ALUMINUM +(LET) + +ID: 445 +Title: Navne +Data: JER +(MEDIUM) + +ID: 446 +Title: Navne +Data: STEEL +(HID) + +ID: 447 +Title: Navne +Data: TUNG +METAL + +ID: 448 +Title: Navne +Data: POWER + +ID: 449 +Title: Navne +Data: SMIDIGHED + +ID: 450 +Title: Navne +Data: ENDUR. + +ID: 451 +Title: Navne +Data: ARENA + +ID: 452 +Title: Navne +Data: SIM-kort + +ID: 453 +Title: Navne +Data: NY TURAMENT + +ID: 454 +Title: Navne +Data: BELASTNING + +ID: 455 +Title: Navne +Data: DELETE + +ID: 456 +Title: Navne +Data: NYE + +ID: 457 +Title: Navne +Data: KOB + +ID: 458 +Title: Navne +Data: SÆLG + +ID: 459 +Title: Navne +Data: TRÆNING +KILDE + +ID: 460 +Title: Navne +Data: HANDEL +I NÆVNT + +ID: 461 +Title: Navne +Data: TILGÆNGEBARE: + +ID: 462 +Title: Navne +Data: RUMM + +ID: 463 +Title: Navne +Data: RUMMPLADE: + +ID: 464 +Title: Navne +Data: STUN RES. + +ID: 465 +Title: Navne +Data: STUN RES.: + +ID: 466 +Title: Navne +Data: %s +SPEED + +ID: 467 +Title: Navne +Data: %s HASTIGHED: + +ID: 468 +Title: Navne +Data: %s +I NÆVNT + +ID: 469 +Title: Navne +Data: %s POWER: + +ID: 470 +Title: Navne +Data: %s +SPEED + +ID: 471 +Title: Navne +Data: %s HASTIGHED: + +ID: 472 +Title: Navne +Data: %s +I NÆVNT + +ID: 473 +Title: Navne +Data: %s POWER: + +ID: 474 +Title: Navne +Data: D + O + I nærheden af N + E + +ID: 475 +Title: Navne +Data: Q + U + Jeg + T + +ID: 476 +Title: Ingen omsættelige robotter +Data: INGEN + +ID: 477 +Title: Ikke tilgængelig element +Data: UTILGÆNGEBARE + +ID: 478 +Title: Ikke tilgængelig element +Data: IKKE +TILGÆNGEBARE + +ID: 479 +Title: Ikke tilgængelig element +Data: MINIMUMSNIVEA + +ID: 480 +Title: Ikke tilgængelig element +Data: Mens du arbejder for pengene til at komme ind i turneringen, er dine hårdt tjente færdigheder faldet, og dele af din robot er forværret. Du lover aldrig at være så skodeslos med dine penge. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 481 +Title: Ikke tilgængelig element +Data: Mens du arbejder for pengene til at komme ind i turneringen, er dine hårdt tjente færdigheder faldet, og dele af din robot er forværret. Du lover aldrig at være så skodeslos med dine penge. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 482 +Title: Ikke tilgængelig element +Data: Mens du arbejder for pengene til at komme ind i turneringen, er dine hårdt tjente færdigheder faldet, og dele af din robot er forværret. Du lover aldrig at være så skodeslos med dine penge. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 483 +Title: Ikke tilgængelig element +Data: DU SKAL BRUGERE TID PÅ AT ARGUMENTERE SÅ EN MEKéIKER FOR AT TJENE PENAGET I NÆRING TIL DEN TOURNAMENT. ER DU SIKRE PÅ, AT DU VIL DELTAG I %s? + +ID: 484 +Title: Ikke tilgængelig element +Data: ER DU SIKKER PÅ, AT DU VIL FORDELE %s TIL AT TRÆDE %s? + +ID: 485 +Title: Ikke tilgængelig element +Data: DU KONKURRERER ALLEREDE I %s. ODELÆGGERE OG GÅ IGEN VIDERE %s? + +ID: 486 +Title: Ikke tilgængelig element +Data: VÆLG DEN TURNERING, DU ODSLER, SKAL DE INDTASTE + +ID: 487 +Title: Ikke tilgængelig element +Data: VÆLG ET VENEDIGT NÆVN NÆVN + +ID: 488 +Title: Ingen omsættelige robotter +Data: INGEN OVERKOMMENDE +ROBOTTER TIL RÅDIGHED +FOR HANDEL + +ID: 489 +Title: Ikke tilgængelig element +Data: SALG PRIS: + +ID: 490 +Title: Ikke tilgængelig element +Data: OPGRADERINGSOMKOSTNINGER: + +ID: 491 +Title: Ikke tilgængelig element +Data: Niveau %d + +ID: 492 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +I nærheden af Jaguar Leap + +Hjernerystelse Cannon + +Overhead Kast + +ID: 493 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Skyggedving + +I nærheden af Shadow Punch + +Skygge Slide + +I nærheden af Shadow Grab + +ID: 494 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Hastighed-Kick + +Off-Wall angreb + +I nærheden af Spike-Charge + +ID: 495 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +I nærheden af Fire Spin + +Super fremstod angreb + +I nærheden af Jet Swoop + +ID: 496 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +I nærheden af Ball Lightning + +Rullende Torden + +Elektriske Shards + +ID: 497 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Stigende blad + +I nærheden af Head Stomp + +I nærheden af Razor Spin + +ID: 498 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Hoved-Butt + +Flip Kick + +I nærheden af Flying Hands + +ID: 499 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Spinning Kast + +Opladning af Punch + +Svingende kæder + +ID: 500 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +I nærheden af Diving Claw + +I nærheden af Flying Talon + +Fodsle Afgift + +ID: 501 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Teleportation i små skala + +I nærheden af Matter Phasing + +I nærheden af Stasis Activator + +ID: 502 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +Mini granat + +I nærheden af Missil Launcher + +Jordskælv smadrer + +ID: 503 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +I NÆVNT +HOP VIA WALL THINGIE +STASIS KRISETAL + +ID: 504 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +BLASH KICK RIP-OFF +STIGE AFGORELSE +I NÆRHEDEN AF WALL SPIKE + +ID: 505 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 506 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 507 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 508 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 509 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 510 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 511 +Title: Robotbeskrivelse +Data: SÆRLIGE BEVÆGELSER: + +JET CHARGE +I NÆRHEDEN AF FLAME DIVE + +ID: 512 +Title: Træning +Data: BLUNT STONE'S +STROMAFREVARIALÆG +TEKNIKKER: + +ID: 513 +Title: Træning +Data: DEN LEGENDARISK +FLASH TRÆNING +FOR FÆNGSEL +UDFORDRET: + +ID: 514 +Title: Træning +Data: VLAD IRONSIDE'S +AEROB OG CHOK +MODSTANDSDYGTIGHED +WORKSHOP: + +ID: 515 +Title: Træning +Data: TAG KRAFTVÆRK I %? + +ID: 516 +Title: Træning +Data: TAG AGILITY TRAINING KURS FOR %s? + +ID: 517 +Title: Træning +Data: TAG UDHOLDENDE TRÆNING KURS I %s? + +ID: 518 +Title: Handel robot +Data: HANDLE DINE %s for A %s AND %s? + +ID: 519 +Title: Handel robot +Data: HANDLE DINE %s OG %s FOR A %s? + +ID: 520 +Title: Handel robot +Data: HANDLE DINE %s for A %s? + +ID: 521 +Title: Opgradering kob +Data: SÆLGE NIVÅR %d %s HASTIGHEDSSTUR FOR %s? + +ID: 522 +Title: Opgradering kob +Data: KOBSNIVALETS NIVÆRD %d %s OPGRADERING FOR %s? + +ID: 523 +Title: Opgradering kob +Data: SÆLGENDE NIVÆRD %d %s POWER OPGRADERING FOR %s? + +ID: 524 +Title: Opgradering kob +Data: KOB NEVER %d %s POWER UPGRADE FOR %s? + +ID: 525 +Title: Opgradering kob +Data: SÆLGER NIVIL %d STUN RESISTANCE FOR %s? + +ID: 526 +Title: Opgradering kob +Data: KOB NIVEA %d STUN RESISTANCE FOR %s? + +ID: 527 +Title: Opgradering kob +Data: SÆLGER NIVÅR %d ARMOR PLANTNING FOR %s? + +ID: 528 +Title: Opgradering kob +Data: KOBSNIVEA %D ARMOR PLETTER FOR %s? + +ID: 529 +Title: Hjælp til turneringsbesvær +Data: Den perfekte problemindstilling for nye spillere. + +ID: 530 +Title: Hjælp til turneringsbesvær +Data: Tror du, du er klar til at kæmpe med de store drenge? + +ID: 531 +Title: Hjælp til turneringsbesvær +Data: For at overleve, skal du bruge kuglelejer af stål. + +ID: 532 +Title: Hjælp til turneringsbesvær +Data: Forbered dig på at blive rystet! + +ID: 533 +Title: Hjælp til robot dev. knap +Data: TRÆNER FOR AT STIGE PILOTS MAGTNÆRE + +ID: 534 +Title: Hjælp til robot dev. knap +Data: TRÆN FOR AT FOROGE PILOTS AGILITETSNIVÆRNIT + +ID: 535 +Title: Hjælp til robot dev. knap +Data: TOG FOR AT STIGE PILOTS UDVANDRINGSNIVÆRD + +ID: 536 +Title: Hjælp til robot dev. knap +Data: FORLAD PILOT TRÆNINGSCENTERET + +ID: 537 +Title: Hjælp til robot dev. knap +Data: INDTJEK ARENA OG KAMP %s + +ID: 538 +Title: Hjælp til robot dev. knap +Data: TAG TRÆNINGSKURS FOR AT OGE KRÆFT, AGNILITET ELLER UDHOLDENHED + +ID: 539 +Title: Hjælp til robot dev. knap +Data: KOB OVERGANGSÆT TIL DIN ROBOT + +ID: 540 +Title: Hjælp til robot dev. knap +Data: SÆLG OPGRADER KITS FRA DIN ROBOT + +ID: 541 +Title: Hjælp til robot dev. knap +Data: LÆS PILOT FRA DISK + +ID: 542 +Title: Hjælp til robot dev. knap +Data: SKAPRING EN NY PILOT + +ID: 543 +Title: Hjælp til robot dev. knap +Data: SLETT EN PILOT FRA DISK + +ID: 544 +Title: Hjælp til robot dev. knap +Data: PRAKSIS MOD EN FJENDTLIG PILOT ELLER SPILLER I EN COMPUTERSITI + +ID: 545 +Title: Hjælp til robot dev. knap +Data: FORLAD KOMMANDECENTERET +(DIN PILOT VIL BLEV REDDET AUTOMATICELT) + +ID: 546 +Title: Hjælp til robot dev. knap +Data: REGISTRERING FOR EN NY TOURNAMENT + +ID: 547 +Title: Hjælp til robot dev. knap +Data: ÆNDRE HOMSERFARVE TIL ROBOT + +ID: 548 +Title: Hjælp til robot dev. knap +Data: ÆNDRE HOMSERFARVE TIL ROBOT + +ID: 549 +Title: Hjælp til robot dev. knap +Data: ÆNDRING AF SEKUNDÆRE KUGLE FARVER TIL ROBOT + +ID: 550 +Title: Hjælp til robot dev. knap +Data: ÆNDRING AF SEKUNDÆRE KUGLE FARVER TIL ROBOT + +ID: 551 +Title: Hjælp til robot dev. knap +Data: ÆNDRE TREDJE KAMPAGN FARVE TIL ROBOT + +ID: 552 +Title: Hjælp til robot dev. knap +Data: ÆNDRE TREDJE KAMPAGN FARVE TIL ROBOT + +ID: 553 +Title: Hjælp til robot dev. knap +Data: SÆLG POWER NEVEL FOR %s ANGREB + +ID: 554 +Title: Hjælp til robot dev. knap +Data: OPGRADER DE SKADER, DER ER GJORT AF ALL %S ANGREB + +ID: 555 +Title: Hjælp til robot dev. knap +Data: SÆLG POWER NEVEL FOR %s ANGREB + +ID: 556 +Title: Hjælp til robot dev. knap +Data: OPGRADER DE SKADER, DER ER GJORT AF ALL %S ANGREB + +ID: 557 +Title: Hjælp til robot dev. knap +Data: SÆLG HASTIGHEDSNOVELSE FOR %s ANGREB + +ID: 558 +Title: Hjælp til robot dev. knap +Data: OPGRADERER HASTIGHEDEN AF ALLE %s ANGREB + +ID: 559 +Title: Hjælp til robot dev. knap +Data: SÆLG HASTIGHEDSNOVELSE FOR %s ANGREB + +ID: 560 +Title: Hjælp til robot dev. knap +Data: OPGRADERER HASTIGHEDEN AF ALLE %s ANGREB + +ID: 561 +Title: Hjælp til robot dev. knap +Data: SÆLG ARMT PLATING NIVEAER FRA ROBOT + +ID: 562 +Title: Hjælp til robot dev. knap +Data: OPGRADERER BEVÆBNING AF ROBOTEN + +ID: 563 +Title: Hjælp til robot dev. knap +Data: SÆLG STUN RESISTANCENIVEA FRA ROBOT + +ID: 564 +Title: Hjælp til robot dev. knap +Data: OPGRADERING AF STUN RESISTANCENIVNEN FOR ROBOT + +ID: 565 +Title: Hjælp til robot dev. knap +Data: HANDELS NUVÆRENDE ROBOT FOR EN ANDEN TYPE ROBOT + +ID: 566 +Title: Hjælp til robot dev. knap +Data: HANDELS NUVÆRENDE ROBOT FOR EN ANDEN TYPE ROBOT + +ID: 567 +Title: Hjælp til robot dev. knap +Data: LAV EQUIPMENT EXCHANGE CENTER + +ID: 568 +Title: Hjælp til robot dev. knap +Data: FORLAD ROBOT FORBEDELSESCENTER + +ID: 569 +Title: Hjælp til robot dev. knap +Data: RANK +%i + +ID: 570 +Title: Hjælp til robot dev. knap +Data: NEJ +RANK + +ID: 571 +Title: Hjælp til robot dev. knap +Data: INGEN RANG + +ID: 572 +Title: Hjælp til robot dev. knap +Data: NAVN: + +ID: 573 +Title: Hjælp til robot dev. knap +Data: RANK: + +ID: 574 +Title: Hjælp til robot dev. knap +Data: VINDER: + +ID: 575 +Title: Hjælp til robot dev. knap +Data: TABER: + +ID: 576 +Title: Hjælp til robot dev. knap +Data: PENGE: + +ID: 577 +Title: Hjælp til robot dev. knap +Data: MODEL: + +ID: 578 +Title: Hjælp til robot dev. knap +Data: POWER + +ID: 579 +Title: Hjælp til robot dev. knap +Data: SMIDIGHED + +ID: 580 +Title: Hjælp til robot dev. knap +Data: UDHOLDENHED + +ID: 581 +Title: Hjælp til robot dev. knap +Data: %s HASTIGHED + +ID: 582 +Title: Hjælp til robot dev. knap +Data: %s POWER + +ID: 583 +Title: Hjælp til robot dev. knap +Data: %s HASTIGHED + +ID: 584 +Title: Hjælp til robot dev. knap +Data: %s POWER + +ID: 585 +Title: Hjælp til robot dev. knap +Data: RUMM + +ID: 586 +Title: Hjælp til robot dev. knap +Data: STUN RES + +ID: 587 +Title: Repair messages til fortabing +Data: Oprydningsbesætningerne fandt en form for ekstraudstyr hardware i vraget, du forlod derude. Teknologierne installerer det i din robot. + +ID: 588 +Title: Repair messages til fortabing +Data: Patetisk, knægt, virkelig patetisk. Kunne ikke holde varmen ud, så du spogte. Du er nodt til at lære at se dine kampe i ojnene, eller du vil altid være den klynkende kujon, du var i dag. + +ID: 589 +Title: Repair messages for at tabe +Data: Whew, du blev slået temmelig slemt. Du skal sætte ekstra tid i sims for din næste. + +ID: 590 +Title: Repair messages for at tabe +Data: Forsoger at holde mine drenge beskæftiget, ikke? + +ID: 591 +Title: Repair messages for at tabe +Data: Hej, du må hellere komme tilbage derude. Der er et par steder på den 'bot, der ikke blev revet i stykker. + +ID: 592 +Title: Repair messages for at tabe +Data: Åh, mand. Har du nogensinde hort om blokering? + +ID: 593 +Title: Repair messages for at tabe +Data: Du er heldig, at jeg kan lide mit arbejde mere end min kone. + +ID: 594 +Title: Repair messages til at vinde +Data: Nå, i det mindste vandt du. Ærgerligt jeg vil være her hele natten. + +ID: 595 +Title: Repair messages til at vinde +Data: Ser ud til at du knap nok klarede den ene, knægt. + +ID: 596 +Title: Repair messages til at vinde +Data: Du er nodt til at lære noget forsvar, eller du vil altid få slået op på denne måde. + +ID: 597 +Title: Repair messages til at vinde +Data: Ikke så slemt, ser ud til at du holdt mindst et skridt foran dem. + +ID: 598 +Title: Repair messages til at vinde +Data: Dejligt job. Næste gang kunne du holde dig væk fra ham lidt mere. Gor mit arbejde lettere, knægt? + +ID: 599 +Title: Repair messages til at vinde +Data: Kom nu knægt, hold vagten deroppe. Du gjorde det ikke dårligt, men du kunne have gjort det meget bedre. + +ID: 600 +Title: Repair messages til at vinde +Data: Whoa, du har studeret nogle af mine gamle kampe i holo-biblioteket, har ikke cha. Du har måske lidt talent, knægt. + +ID: 601 +Title: Repair messages til at vinde +Data: Humph. Du synes, du er ret god, gor du ikke. Hvis jeg var yngre, ville jeg vise dig en ting eller to. + +ID: 602 +Title: Repair messages til at vinde +Data: Tja, det ser ud til, at jeg kommer tidligt hjem i aften. + +ID: 603 +Title: Shop rapport +Data: Lyt, og lyt tæt på. Jeg fortalte teknologien, at du ville betale dem tilbage på den næste kamp, så hvis du ikke vinder, er det tilbage til bleachers for dig. + +ID: 604 +Title: Shop rapport +Data: Jeg advarede dig. Turneringsembedsmændene sparker dig ud for ikke at betale dine regninger. Måske skulle du finde en anden karriere, knægt. + +ID: 605 +Title: Shop rapport +Data: DU SKAL BEGYNDE AT VENDE ET OVERSKUD. JEG VAR NODT TIL AT SÆLGE A %s BARE FOR AT DÆKSE REPAIR-regningen. + +ID: 606 +Title: Shop rapport +Data: Finansiel rapport + +ID: 607 +Title: Shop rapport +Data: FORTJENESTE: + +ID: 608 +Title: Shop rapport +Data: REPAIR COST: + +ID: 609 +Title: Shop rapport +Data: VINDNINGER: + +ID: 610 +Title: Shop rapport +Data: BONUSSER: + +ID: 611 +Title: Shop rapport +Data: KI'ER +STATISTIK + +ID: 612 +Title: Shop rapport +Data: HITS LANDET: + +ID: 613 +Title: Shop rapport +Data: GENNANGEN FOR SENDING: + +ID: 614 +Title: Shop rapport +Data: MISLYKTE ANGREB: + +ID: 615 +Title: Shop rapport +Data: HIT/MISS RATIO: + +ID: 616 +Title: Shop rapport +Data: HITS TAGET: + +ID: 617 +Title: Shop rapport +Data: MÅLSTREGNE + +ID: 618 +Title: Shop rapport +Data: HITS LANDET: + +ID: 619 +Title: Shop rapport +Data: GENNANGEN FOR SENDING: + +ID: 620 +Title: Shop rapport +Data: MISLYKTE ANGREB: + +ID: 621 +Title: Shop rapport +Data: HIT/MISS RATIO: + +ID: 622 +Title: +Data: ARM + +ID: 623 +Title: +Data: LEG + +ID: 624 +Title: +Data: FLAIL + +ID: 625 +Title: +Data: FLAME + +ID: 626 +Title: +Data: BLADE + +ID: 627 +Title: +Data: BLADE + +ID: 628 +Title: +Data: BLADE + +ID: 629 +Title: +Data: BLADE + +ID: 630 +Title: +Data: BLADE + +ID: 631 +Title: +Data: BLADE + +ID: 632 +Title: +Data: BLADE + +ID: 633 +Title: +Data: BLADE + +ID: 634 +Title: +Data: BLADE + +ID: 635 +Title: +Data: BLADE + +ID: 636 +Title: +Data: BLADE + +ID: 637 +Title: 1 +Data: + +ID: 638 +Title: 1 +Data: ESCAPE + +ID: 639 +Title: 1 +Data: 1 + +ID: 640 +Title: 1 +Data: 2 + +ID: 641 +Title: 1 +Data: 3 + +ID: 642 +Title: 1 +Data: 4 + +ID: 643 +Title: 1 +Data: 5 + +ID: 644 +Title: 1 +Data: 6 + +ID: 645 +Title: 1 +Data: 7 + +ID: 646 +Title: 1 +Data: 8 + +ID: 647 +Title: 1 +Data: 9 + +ID: 648 +Title: 1 +Data: 0 + +ID: 649 +Title: 1 +Data: - + +ID: 650 +Title: 1 +Data: = + +ID: 651 +Title: 1 +Data: TILBAGE PLADS + +ID: 652 +Title: 1 +Data: TAB + +ID: 653 +Title: 1 +Data: Q + +ID: 654 +Title: 1 +Data: W + +ID: 655 +Title: 1 +Data: E + +ID: 656 +Title: 1 +Data: R + +ID: 657 +Title: 1 +Data: T + +ID: 658 +Title: 1 +Data: Y + +ID: 659 +Title: 1 +Data: U + +ID: 660 +Title: 1 +Data: I + +ID: 661 +Title: 1 +Data: O + +ID: 662 +Title: 1 +Data: P + +ID: 663 +Title: 1 +Data: [ + +ID: 664 +Title: 1 +Data: ] + +ID: 665 +Title: 1 +Data: RETURN + +ID: 666 +Title: 1 +Data: CTRL + +ID: 667 +Title: 1 +Data: A + +ID: 668 +Title: 1 +Data: S + +ID: 669 +Title: 1 +Data: D + +ID: 670 +Title: 1 +Data: F + +ID: 671 +Title: 1 +Data: G + +ID: 672 +Title: 1 +Data: H + +ID: 673 +Title: 1 +Data: J + +ID: 674 +Title: 1 +Data: K + +ID: 675 +Title: 1 +Data: L + +ID: 676 +Title: 1 +Data: +ID: 677 +Title: 1 +Data: ' + +ID: 678 +Title: 1 +Data: ` + +ID: 679 +Title: 1 +Data: VENSTREORIENTERING + +ID: 680 +Title: 1 +Data: \ + +ID: 681 +Title: 1 +Data: Z + +ID: 682 +Title: 1 +Data: X + +ID: 683 +Title: 1 +Data: C + +ID: 684 +Title: 1 +Data: V + +ID: 685 +Title: 1 +Data: B + +ID: 686 +Title: 1 +Data: N + +ID: 687 +Title: 1 +Data: M + +ID: 688 +Title: 1 +Data: , + +ID: 689 +Title: 1 +Data: . + +ID: 690 +Title: 1 +Data: / + +ID: 691 +Title: 1 +Data: RIGHT SHIFT + +ID: 692 +Title: 1 +Data: * + +ID: 693 +Title: 1 +Data: ALT + +ID: 694 +Title: 1 +Data: SPACE BAR + +ID: 695 +Title: 1 +Data: + +ID: 696 +Title: 1 +Data: F1 + +ID: 697 +Title: 1 +Data: F2 + +ID: 698 +Title: 1 +Data: F3 + +ID: 699 +Title: 1 +Data: F4 + +ID: 700 +Title: 1 +Data: F5 + +ID: 701 +Title: 1 +Data: F6 + +ID: 702 +Title: 1 +Data: F7 + +ID: 703 +Title: 1 +Data: F8 + +ID: 704 +Title: 1 +Data: F9 + +ID: 705 +Title: 1 +Data: F10 + +ID: 706 +Title: 1 +Data: + +ID: 707 +Title: 1 +Data: + +ID: 708 +Title: 1 +Data: OP VENSTRE PIL + +ID: 709 +Title: 1 +Data: UP ARROW + +ID: 710 +Title: 1 +Data: UP RIGHT ARROW + +ID: 711 +Title: 1 +Data: - + +ID: 712 +Title: 1 +Data: VENSTRE PIL + +ID: 713 +Title: 1 +Data: 5 + +ID: 714 +Title: 1 +Data: RIGTIGE PIL + +ID: 715 +Title: 1 +Data: + + +ID: 716 +Title: 1 +Data: NED VENSTRE ARROW + +ID: 717 +Title: 1 +Data: DOWN ARROW + +ID: 718 +Title: 1 +Data: DOWN RIGHT ARROW + +ID: 719 +Title: 1 +Data: INSERT + +ID: 720 +Title: 1 +Data: DELETE + +ID: 721 +Title: 1 +Data: SYS REQ + +ID: 722 +Title: 1 +Data: + +ID: 723 +Title: 1 +Data: + +ID: 724 +Title: 1 +Data: + +ID: 725 +Title: 1 +Data: + +ID: 726 +Title: 1 +Data: + +ID: 727 +Title: 1 +Data: + +ID: 728 +Title: 1 +Data: + +ID: 729 +Title: 1 +Data: + +ID: 730 +Title: 1 +Data: + +ID: 731 +Title: 1 +Data: + +ID: 732 +Title: 1 +Data: + +ID: 733 +Title: 1 +Data: + +ID: 734 +Title: 1 +Data: + +ID: 735 +Title: 1 +Data: + +ID: 736 +Title: 1 +Data: + +ID: 737 +Title: 1 +Data: + +ID: 738 +Title: 1 +Data: + +ID: 739 +Title: 1 +Data: + +ID: 740 +Title: 1 +Data: + +ID: 741 +Title: 1 +Data: + +ID: 742 +Title: 1 +Data: + +ID: 743 +Title: 1 +Data: + +ID: 744 +Title: 1 +Data: + +ID: 745 +Title: 1 +Data: + +ID: 746 +Title: 1 +Data: + +ID: 747 +Title: 1 +Data: Jeg er ikke imponeret over dine patetiske kampfærdigheder. Kom tilbage, når du har lært at kæmpe. + +ID: 748 +Title: 1 +Data: Vælg et hojere CPU-vanskelighedsniveau fra GAMEPLAY-menuen. %s niveau, der kræves for at udfordre %s. + +TÆR PÅ ENHVER NOGLE FOR AT FORTSÆTTE + +ID: 749 +Title: 1 +Data: Du ser bekendt ud + +ID: 750 +Title: 1 +Data: Undervurder mig ikke! + +ID: 751 +Title: 1 +Data: Du kendte min far. Hvad skete der med ham? + +ID: 752 +Title: 1 +Data: Jeg er dygtig nok til at stå over for enhver fare, bror. + +ID: 753 +Title: 1 +Data: Stå ikke i vejen! Jeg må sejre! + +ID: 754 +Title: 1 +Data: Jeg kan få dig til at tale! + +ID: 755 +Title: 1 +Data: Jeg kæmper for hævn! Du kan ikke stoppe mig. + +ID: 756 +Title: Crystal til Angel +Data: Skonhed er kun en af mine mange aktiver. + +ID: 757 +Title: Crystal til Cossette +Data: Er der noget, du kan fortælle mig? + +ID: 758 +Title: Crystal to Raven +Data: Du ved sikkert noget? + +ID: 759 +Title: Crystal til major Kreissack +Data: Jeg ved, at du må have dræbt mine forældre. Jeg vil have hævn! + +ID: 760 +Title: Steffan til Crystal +Data: Jeg vil torre det smil af dit ansigt, blodhed. + +ID: 761 +Title: Steffan til Steffan +Data: Du ser fantastisk ud! + +ID: 762 +Title: Steffan til Milano +Data: Du er ved at se perfektion i aktion, Pony-hale. + +ID: 763 +Title: Stoffan til christian +Data: Spis bly, Blondie! + +ID: 764 +Title: Steffan til Shirro +Data: Hej pops, er det ikke tid til din lur? + +ID: 765 +Title: Steffan til Jean-Paul +Data: Se og lær, stille dreng. Jeg giver dig 30 fliser inden du falder. + +ID: 766 +Title: Steffan til Ibrahim +Data: Du kan forlade nu og undgå forlegenhed. + +ID: 767 +Title: Stoffan til Angel +Data: Når jeg har fjernet dig, tager jeg dig ud! + +ID: 768 +Title: Steffan til Cossette +Data: Du tror bestemt ikke, at du kan vinde? + +ID: 769 +Title: Steffan til Raven +Data: Jeg vil lade dig vaske min 'bot, når vi er færdige. + +ID: 770 +Title: Steffan til major Kreissack +Data: Til sidst en værdig modstander. Gor dig klar til at rocke! + +ID: 771 +Title: Milano til Crystal +Data: Du ser forberedt ud. Det bliver en stor kamp. + +ID: 772 +Title: Milano til Steffan +Data: Arrogance gor kujoner af mænd, min ven. + +ID: 773 +Title: Milano til Milano +Data: Når jeg tænker på dig... + +ID: 774 +Title: Milano til Christian +Data: Jeg kan kun onske dig held og lykke med dine bestræbelser. + +ID: 775 +Title: Milano til Shirro +Data: Virksomheden vil snart trives under min regel. + +ID: 776 +Title: Milano til Jean-Paul +Data: Hej, ven. + +ID: 777 +Title: Milano til Ibrahim +Data: Jeg bekymrer mig lidt for dine motiver. Du vil snart se min vej. + +ID: 778 +Title: Milano til Angel +Data: Jeg er helt sikker på, at vi modes igen. + +ID: 779 +Title: Milano til Cossette +Data: Jeg kan kun sige, at jeg respekterer din vedholdenhed. + +ID: 780 +Title: Milano til Raven +Data: Jeg kender dine planer. Du skal være mere diskret. + +ID: 781 +Title: Milano til major Kreissad +Data: Du har ingen idé om den indvirkning, denne kamp kan have. + +ID: 782 +Title: Christian til Crystal +Data: Når jeg har vundet, vil du stoppe dette vrovl. + +ID: 783 +Title: Christian til Steffan +Data: Du har bedst fortalt mig alt, hvad du kender til mine forældre. + +ID: 784 +Title: Christian til Milano +Data: Du vil ikke stå mellem mig og min virkelige fjende. + +ID: 785 +Title: Christian til Christian +Data: Synes du, jeg ser godt ud i denne farve? + +ID: 786 +Title: Christian til Shirro +Data: Mine forældre kendte dig godt. Hvad ved du? + +ID: 787 +Title: Christian til Jean-Paul +Data: Hvilken slags V.P. kunne du være? + +ID: 788 +Title: Christian til Ibrahim +Data: Fortæl mig om dine eksperimenter. + +ID: 789 +Title: Christian til Angel +Data: Hvis jeg vinder, lover du vil give mig nogle anelse! + +ID: 790 +Title: Christian til Cossette +Data: Jeg kan kun håbe, at den fremtidige hersker vil være retfærdig. + +ID: 791 +Title: Christian til Raven +Data: Som ansat i W.A.R. skal du have oplysninger! + +ID: 792 +Title: Christian til major Kreissck +Data: Du dræbte min far, forbereder dig på at do. + +ID: 793 +Title: Shirro til Crystal +Data: En pige af din skonhed bor ikke være så urolig. + +ID: 794 +Title: Shirro til Steffan +Data: Og nu vil den lille dreng gå i skole... + +ID: 795 +Title: Shirro til Milano +Data: Tror du, at dit valg i 'bots havde ret? + +ID: 796 +Title: Shirro til christian +Data: Din far var en god mand. Tingene vil snart fungere. + +ID: 797 +Title: Shirro til Shirro +Data: Bruger du skildpaddevoks på det hoved? + +ID: 798 +Title: Shirro til Jean-Paul +Data: Håber du ikke har noget imod et par bule i den 'bot. + +ID: 799 +Title: Shirro til Ibrahim +Data: Nå, Ib! Lang tid ikke at se... + +ID: 800 +Title: Shirro til Angel +Data: Hvad er du, pige? + +ID: 801 +Title: Shirro til Cossette +Data: Selvom jeg beundrer dig, må jeg have denne sejr! + +ID: 802 +Title: Shirro til Raven +Data: Jeg kender mange af dine svagheder. Du kan give afkald nu, knægt. + +ID: 803 +Title: Shirro til major Kreissack +Data: Hvad laver du her? Åh ja, så mere den gladere. + +ID: 804 +Title: Jean-Paul til Crystal +Data: Betragt dig selv som advaret. + +ID: 805 +Title: Jean-Paul til Steffan +Data: Jeg har ingen respekt for dig. + +ID: 806 +Title: Jean-Paul til Milano +Data: Jeg er fodt til at styre dette firma. + +ID: 807 +Title: Jean-Paul til kristen +Data: Du har ingen mulighed for at vinde. + +ID: 808 +Title: Jean-Paul til Shirro +Data: Hvordan kan du grine, mens W.A.R. fortsætter sine eksperimenter? + +ID: 809 +Title: Jean-Paul til Jean-Paul +Data: Jeg vil vædde på, hvad du tænker. + +ID: 810 +Title: Jean-Paul til Ibrahim +Data: Kreissuck vil ikke lade dig leve. Hold op nu. + +ID: 811 +Title: Jean-Paul til Angel +Data: Af jer ved jeg intet. + +ID: 812 +Title: Jean-Paul til Cossette +Data: Held og lykke. + +ID: 813 +Title: Jean-Paul til Raven +Data: Dine planer kan ikke lykkes. Dine fjender er for magtfulde. + +ID: 814 +Title: Jean-Paul til major Kreissak +Data: Dine dage med magt vil snart komme til en ende! + +ID: 815 +Title: Ibrahim til Crystal +Data: Eksperimentet, som din far arbejdede på, skulle aldrig have startet. + +ID: 816 +Title: Ibrahim til Steffan +Data: Du er i et ydmygende tab. + +ID: 817 +Title: Ibrahim til Milano +Data: Mellem min evne og valg af robot, kan jeg ikke tabe! + +ID: 818 +Title: Ibrahim til christian +Data: Din mor var et uskyldigt offer. + +ID: 819 +Title: Ibrahim til Shirro +Data: Du kan ikke længere være en neutral part i det, der sker her. + +ID: 820 +Title: Ibrahim til Jean-Paul +Data: Intelligens er en gave, men visdom er tjent. + +ID: 821 +Title: Ibrahim til Ibrahim +Data: To fyre går ind i en bar... + +ID: 822 +Title: Ibrahim til Angel +Data: Hvorfor er der intet af dig i Interpol-optegnelserne? + +ID: 823 +Title: Ibrahim til Cossette +Data: Du står over for nogle vanskelige beslutninger i fremtiden, min ven. + +ID: 824 +Title: Ibrahim til Raven +Data: Kreissack kender til dine planer. Vind eller tab, du skal lobe. + +ID: 825 +Title: Ibrahim til major Kreissack +Data: Hvilken robot er det? Jeg har aldrig set den! + +ID: 826 +Title: Angel til Crystal +Data: Jeg bekymrer mig ikke om dit trivielle tab. + +ID: 827 +Title: Angel til Steffan +Data: Du er ikke engang min tid værd. + +ID: 828 +Title: Angel til Milano +Data: Denne gang er det kun en konkurrence... + +ID: 829 +Title: Angel til christian +Data: Du har ret til at frygte mig. + +ID: 830 +Title: Angel til Shirro +Data: Tingene er ikke altid, hvad de ser ud til. + +ID: 831 +Title: Angel til Jean-Paul +Data: Tilbage fra, menneskelig. Denne kamp er ubetydelig. + +ID: 832 +Title: Angel til Ibrahim +Data: Når dette er overstået, må du ophore med dine eksperimenter. + +ID: 833 +Title: Angel til Angel +Data: I det mindste kan jeg stole på dig. + +ID: 834 +Title: Angel til Cossette +Data: Du skulle have sagt op, mens du havde chancen. + +ID: 835 +Title: Angel til Raven +Data: Synes du dig selv stærk? + +ID: 836 +Title: Angel til Major Kreissack +Data: Vi vil ikke blive nægtet! Det er oproret lige ved hånden! + +ID: 837 +Title: Cossette til Crystal +Data: Hvorfor går du ikke hjem og gemmer dig under omslag? + +ID: 838 +Title: Cossette til Steffan +Data: Selv lammet, jeg er stærkere end dig. + +ID: 839 +Title: Cossette til Milano +Data: Ganymede vil snart være min! + +ID: 840 +Title: Cossette til christian +Data: Med dit sind så uroligt, kan du ikke vinde. + +ID: 841 +Title: Cossette til Shirro +Data: Vi skal se, hvem der griner sidst. + +ID: 842 +Title: Cossette til Jean-Paul +Data: Nu vil du se et rigtigt sind i aktion. + +ID: 843 +Title: Cossette til Ibrahim +Data: Dine ben vil ikke hjælpe dig i denne konkurrence! + +ID: 844 +Title: Cossette til Angel +Data: Hvis du ikke har nogen erfaring på disse maskiner, hvorfor er du her? + +ID: 845 +Title: Cossette til Cossette +Data: Jeg er garanteret at vinde. + +ID: 846 +Title: Cossette til Raven +Data: Måske kan du være min livvagt, når jeg vinder... + +ID: 847 +Title: Cossette til major Kreissack +Data: Du kaldte mig svag en gang. Du vil aldrig gore det igen. + +ID: 848 +Title: Raven til Crystal +Data: Du er intet andet end et barn, der rider på din fars navn. + +ID: 849 +Title: Raven til Steffan +Data: Du er et skadedyr, der er ved at blive trampet. + +ID: 850 +Title: Raven til Milano +Data: Hastighed er intet imod rå magt! + +ID: 851 +Title: Raven til kristen +Data: Din far skulle aldrig have forladt Nova-projektet. + +ID: 852 +Title: Raven til Shirro +Data: Du havde bedst at gå på pension, for vi gik på pension. + +ID: 853 +Title: Raven til Jean-Paul +Data: Du er en freak, der skal blive i laboratoriet. + +ID: 854 +Title: Raven til Ibrahim +Data: Jeg ved ikke, hvad du laver her. Dine evner er værdilose. + +ID: 855 +Title: Raven til Angel +Data: Jeg ved ikke, hvem du er, men du må hellere gå tilbage til, hvor du kom fra. + +ID: 856 +Title: Raven to Cossette +Data: Denne kamp er kun for dem med en "PERFECT" krop og sind. + +ID: 857 +Title: Raven til Raven +Data: Næsten som at se i et spejl... + +ID: 858 +Title: Raven til major Kreissack +Data: Godaften, sir. Skal vi spille? + +ID: 859 +Title: M Kreissuck til Crystal +Data: Intet som at hacke et spil, eh? + +ID: 860 +Title: M Kreissack til Steffan +Data: Jeg får en stor glæde af at rive op nogens arbejde. + +ID: 861 +Title: M Kreissick til Milano +Data: Hvordan er det, at jeg kæmper mod dig, måske? + +ID: 862 +Title: M Kreissack til kristen +Data: Hvem gjorde det til mig!?! + +ID: 863 +Title: M Kreissack til Shirro +Data: Jeg skal være på den anden side af denne skærm. + +ID: 864 +Title: M Kreissæk til Jean-Paul +Data: Sæt mig tilbage, hvor jeg horer hjemme. + +ID: 865 +Title: M Kreissack til Ibrahim +Data: Blodkoden er TILFREDS TILFREDS FREMAD PUNCH KICK og derefter FREMAD 135 gange. + +ID: 866 +Title: M Kreissuck til Angel +Data: For at spille som Kreissack skal du trykke på -tasten. + +ID: 867 +Title: M Kreissak til Cossette +Data: For at nå Arachnid, få syv perfektioner i træk. + +ID: 868 +Title: M Kreissick til Raven +Data: Hvorfor læser du dette, alligevel? + +ID: 869 +Title: M Kreissak til major Kreissak +Data: Nu skal jeg se. + +ID: 870 +Title: Crystal til Crystal +Data: Du er den smukkeste pige, jeg nogensinde har set! + +ID: 871 +Title: Krystal til Steffan +Data: Et par strimlen af metal vil være alt, hvad der er tilbage af dig. + +ID: 872 +Title: Crystal til Milano +Data: Når det er overstået, kan du måske være min sekretær. + +ID: 873 +Title: Crystal til Christian +Data: Jeg ved, hvordan man besejrer dig, bror. Du vil ikke vinde. + +ID: 874 +Title: Crystal til Shirro +Data: Min fred vil komme efter denne kamp er vundet. + +ID: 875 +Title: Crystal til Jean-Paul +Data: Hvis du ikke har nogen oplysninger, er alt, hvad jeg har brug for, dit nederlag. + +ID: 876 +Title: Crystal til Ibrahim +Data: Hvilke eksperimenter? Sig det til mig! + +ID: 877 +Title: Crystal til Angel +Data: Trivial? Du vil betale for din holdning! + +ID: 878 +Title: Crystal til Cossette +Data: Huh? Jeg viser dig! + +ID: 879 +Title: Crystal to Raven +Data: Det vil ikke være hans navn, der vinder denne kamp. + +ID: 880 +Title: Crystal til major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 881 +Title: Steffan til Crystal +Data: Jeg undervurderer ingen. Jeg er bare bedre end dig. + +ID: 882 +Title: Steffan til Steffan +Data: Tak, dahling. + +ID: 883 +Title: Steffan til Milano +Data: Jeg er ikke bange for noget, Sport. + +ID: 884 +Title: Stoffan til christian +Data: Du bor sporge Iron Fist. De arbejder nu for Kreissck. + +ID: 885 +Title: Steffan til Shirro +Data: Lille dreng? Jeg er to gange din bedre og vil bevise det, nu. + +ID: 886 +Title: Steffan til Jean-Paul +Data: Og din respekt er den mindste af mine bekymringer, Pud. + +ID: 887 +Title: Steffan til Ibrahim +Data: Eller i det mindste vil jeg forårsage en. + +ID: 888 +Title: Stoffan til Angel +Data: Og jeg vil vædde på, at du ikke ville kende en robot, hvis den trådte på dig. + +ID: 889 +Title: Steffan til Cossette +Data: Babe, du er intet, men gnister. + +ID: 890 +Title: Steffan til Raven +Data: Kom inden for fem meter fra mig, og du er stov. + +ID: 891 +Title: Steffan til major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 892 +Title: Milano til Crystal +Data: De dode betyder ikke længere noget. Min sejr er alt, hvad der er vigtigt. + +ID: 893 +Title: Milano til Steffan +Data: Alt jeg ser er en dreng, der forsoger at udfylde en mands job. + +ID: 894 +Title: Milano til Milano +Data: ... Jeg sparker mig selv... + +ID: 895 +Title: Milano til Christian +Data: Måske ikke, men jeg vil besejre dig her. + +ID: 896 +Title: Milano til Shirro +Data: Gæt vi bliver nodt til at se. + +ID: 897 +Title: Milano til Jean-Paul +Data: Ser ud til min ide om skæbne, og din er ved at kollidere. + +ID: 898 +Title: Milano til Ibrahim +Data: At have evnen til at bygge et våben giver dig ikke færdigheder til at bruge det. + +ID: 899 +Title: Milano til Angel +Data: Hej, bare navngiv stedet. + +ID: 900 +Title: Milano til Cossette +Data: Du bliver nodt til at komme forbi mig, forst. + +ID: 901 +Title: Milano til Raven +Data: Så vil dette være testen. + +ID: 902 +Title: Milano til major Kreissad +Data: Hvorfor kæmper du mig alligevel? + +ID: 903 +Title: Christian til Crystal +Data: Hvis du kan besejre mig, tror jeg måske på dig. + +ID: 904 +Title: Christian til Steffan +Data: Din heldige stribe slutter her. + +ID: 905 +Title: Christian til Milano +Data: Alt vil blive afsloret, når jeg korer showet. + +ID: 906 +Title: Christian til Christian +Data: Nå, jeg ved, det ser godt ud på mig! + +ID: 907 +Title: Christian til Shirro +Data: Så snart jeg får fingrene i din chef. + +ID: 908 +Title: Christian til Jean-Paul +Data: Vi skal bare finde ud af det. + +ID: 909 +Title: Christian til Ibrahim +Data: Hun skulle aldrig have været på den shuttle... + +ID: 910 +Title: Christian til Angel +Data: Frygter du? Jeg ved ikke engang, hvem du er! + +ID: 911 +Title: Christian til Cossette +Data: Mit had vil være min styrke. + +ID: 912 +Title: Christian til Raven +Data: Er det derfor, han blev dræbt? For at forlade projektet? + +ID: 913 +Title: Christian til major Kreissck +Data: Hvorfor kæmper du mig alligevel? + +ID: 914 +Title: Shirro til Crystal +Data: Vring til skuffelse. + +ID: 915 +Title: Shirro til Steffan +Data: Faktisk er det på tide, at jeg smækker en grædende baby. + +ID: 916 +Title: Shirro til Milano +Data: Det blomstrer nu, og du vil aldrig vinde alligevel! + +ID: 917 +Title: Shirro til christian +Data: Din far ville være stolt af dig. For nu, sætte dit sind i ro. + +ID: 918 +Title: Shirro til Shirro +Data: Nej. Jeg bruger Mr. Det er Kleen. + +ID: 919 +Title: Shirro til Jean-Paul +Data: Hvordan kan du sige, at Nova er ond uden at vide, hvad det er? + +ID: 920 +Title: Shirro til Ibrahim +Data: Testene vil blive modsat eller ej. + +ID: 921 +Title: Shirro til Angel +Data: Hvor er du fra? + +ID: 922 +Title: Shirro til Cossette +Data: Din skade var til din krop. Du har flyttet det til dit sind. + +ID: 923 +Title: Shirro til Raven +Data: Jeg er ikke bange for virksomheden. Sted vil forbedre sig. + +ID: 924 +Title: Shirro til major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 925 +Title: Jean-Paul til Crystal +Data: Bare slås. + +ID: 926 +Title: Jean-Paul til Steffan +Data: Du er ingenting. + +ID: 927 +Title: Jean-Paul til Milano +Data: Farvel, ven. + +ID: 928 +Title: Jean-Paul til kristen +Data: I det mindste er mine grunde ikke så dyr som din. + +ID: 929 +Title: Jean-Paul til Shirro +Data: Hvis du kæmper som du gjorde i din sidste kamp, er jeg sikker på at vinde. + +ID: 930 +Title: Jean-Paul til Jean-Paul +Data: Du har ret, selvfolgelig... + +ID: 931 +Title: Jean-Paul til Ibrahim +Data: Måske, men min intelligens vil give mig sejr. + +ID: 932 +Title: Jean-Paul til Angel +Data: Hvis ja, hvorfor er du så her? + +ID: 933 +Title: Jean-Paul til Cossette +Data: Ja, efter jeg genspiller dit nederlag. + +ID: 934 +Title: Jean-Paul til Raven +Data: Og du er en marionet, der forsoger at bryde sine strenge. + +ID: 935 +Title: Jean-Paul til major Kreissak +Data: Hvorfor kæmper du mig alligevel? + +ID: 936 +Title: Ibrahim til Crystal +Data: Hævn vil overskygge dit sind, lille en. + +ID: 937 +Title: Ibrahim til Steffan +Data: Jeg tror, du allerede er flov! + +ID: 938 +Title: Ibrahim til Milano +Data: Din vej vil ikke betyde noget. + +ID: 939 +Title: Ibrahim til christian +Data: Eksperimenterne startede ikke ondt. Vi forsogte at forlænge livet! + +ID: 940 +Title: Ibrahim til Shirro +Data: Det er alvorligt, min ven. Jeg må besejre dig. + +ID: 941 +Title: Ibrahim til Jean-Paul +Data: Min involvering er ikke din bekymring. + +ID: 942 +Title: Ibrahim til Ibrahim +Data: ... det efterlod et stort bar-mærke på deres hoveder. + +ID: 943 +Title: Ibrahim til Angel +Data: Disse eksperimenter kan redde liv. + +ID: 944 +Title: Ibrahim til Cossette +Data: Venligst, din skade har intet at gore med denne kamp. + +ID: 945 +Title: Ibrahim til Raven +Data: Du kan ændre mening forste gang jeg smider dig. + +ID: 946 +Title: Ibrahim til major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 947 +Title: Angel til Crystal +Data: Men det vil være det storste sind, der vinder denne konkurrence. + +ID: 948 +Title: Angel til Steffan +Data: Du er ved at få en mands uddannelse. + +ID: 949 +Title: Angel til Milano +Data: Vær ikke. + +ID: 950 +Title: Angel til christian +Data: Jeg bekymrer mig ikke om dine små konflikter. + +ID: 951 +Title: Angel til Shirro +Data: Det sidste, din elendige virksomhed ville forvente. + +ID: 952 +Title: Angel til Jean-Paul +Data: Du vil snart finde ud af min sag. + +ID: 953 +Title: Angel til Ibrahim +Data: Du finder ingen registrering af mig nogen steder. + +ID: 954 +Title: Angel til Angel +Data: Og i det mindste kender jeg den rigtige dig. + +ID: 955 +Title: Angel til Cossette +Data: For Ganymede må jeg sejre. + +ID: 956 +Title: Angel til Raven +Data: Jeg har til hensigt at gore det. + +ID: 957 +Title: Angel til Major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 958 +Title: Cossette til Crystal +Data: Det var et lejesoldater band kaldet Iron Fist, der odelagde rumfærgen. + +ID: 959 +Title: Cossette til Steffan +Data: Forbered dig på at se, hvad et rigtigt sind kan gore. + +ID: 960 +Title: Cossette til Milano +Data: Jeg har ikke brug for din respekt. Bare titlen. + +ID: 961 +Title: Cossette til christian +Data: Åh, det vil jeg, tro mig. + +ID: 962 +Title: Cossette til Shirro +Data: Men jeg er bange for, at jeg har andre planer... + +ID: 963 +Title: Cossette til Jean-Paul +Data: Held? Hvis jeg var heldig, ville jeg ikke være i denne stol! + +ID: 964 +Title: Cossette til Ibrahim +Data: Hvad vil du bekymre dig? + +ID: 965 +Title: Cossette til Angel +Data: Jeg har aldrig sagt op. + +ID: 966 +Title: Cossette til Cossette +Data: Ja, men hvilken! + +ID: 967 +Title: Cossette til Raven +Data: Se hvad du siger, jeg er dodbringende, når jeg er vred. + +ID: 968 +Title: Cossette til major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 969 +Title: Raven til Crystal +Data: Hvis du vil holde det smukke ansigt, skal du stoppe med at stille sporgsmål. + +ID: 970 +Title: Raven til Steffan +Data: Ja, jeg vil slange ned, hvad der er tilbage. + +ID: 971 +Title: Raven til Milano +Data: Det er lige meget. Når jeg vinder, vil Kreissack do. + +ID: 972 +Title: Raven til kristen +Data: De onsker at være i stand til at holde et menneskeligt sind i live inde i en robot. + +ID: 973 +Title: Raven til Shirro +Data: Du ved intet. + +ID: 974 +Title: Raven til Jean-Paul +Data: Du bekymrer dig selv for meget med andres forretning. + +ID: 975 +Title: Raven til Ibrahim +Data: Og du er også målrettet, medoprorere. + +ID: 976 +Title: Raven til Angel +Data: I hvert fald stærkere end dig! + +ID: 977 +Title: Raven to Cossette +Data: Jeg er kun en bodyguard, så længe det passer til mit formål. + +ID: 978 +Title: Raven til Raven +Data: ...rorrim a ni gnikool ekil tsomlA + +ID: 979 +Title: Raven til major Kreissack +Data: Hvorfor kæmper du mig alligevel? + +ID: 980 +Title: Kreissæk til Crystal +Data: Din nysgerrighed vil være din ulempe, pige! + +ID: 981 +Title: M Kreissack til Steffan +Data: Ha! Hvad er det? Du er knap nok ude af bleer! + +ID: 982 +Title: M Kreissick til Milano +Data: Åh, men det gor jeg. Du er den i morket her! + +ID: 983 +Title: M Kreissack til kristen +Data: Kom og se hvad din far arbejdede på! Den perfekte hersker er her! + +ID: 984 +Title: M Kreissack til Shirro +Data: Godt, jeg er overrasket over at se dig her, også. + +ID: 985 +Title: M Kreissæk til Jean-Paul +Data: Mine dage med magt er lige begyndt! + +ID: 986 +Title: M Kreissack til Ibrahim +Data: Her er hvad eksperimenterne producerede! Jeg er genfodt en maskine! + +ID: 987 +Title: M Kreissuck til Angel +Data: Opror? Hvilket opror? + +ID: 988 +Title: M Kreissak til Cossette +Data: Din skade var tragisk. Ærgerligt, at du ikke blev nede. + +ID: 989 +Title: M Kreissick til Raven +Data: Jeg ved, hvad du er op til, hvalp, og jeg ser på en dod mand. + +ID: 990 +Title: Kreissuck til Kreissuck +Data: 'Aven't vi modtes for? + +ID: 991 +Title: Shareware Slutter +Data: Da oprydningsbesætningerne fjerner, hvad der er tilbage af Ravens robot fra arenaen, indser du, at du har bestået testen. +Kampen har efterladt dig udmattet, men du kan stadig kun tænke på at vinde den virkelige kamp, kampen mod Kreissack, overherre af turneringen. +Der er meget at lære, meget at overvinde; Men du vil lære, du vil kæmpe, og du vil vinde... +Hvis du tor tage imod udfordringen! + +ID: 992 +Title: Alles begyndelse på e +Data: Du vågner i din egen krop. I et ojeblik kommer du ned fra adrenalinhojen, du har oplevet. Din krop virker så skrobelig nu. Holdet fejrer allerede, og du glæder dig over deres lykonskninger. +Tid til at feste er kortvarig. Dit sind tager i de sidste par timer begivenheder og den viden, at en hel måne er nu din til at styre. +"Vent et sekund!" kommer en panikagtig stemme fra en vidmonitor. "Alle! Lyt til!" Han skruer op for lydstyrken, og alle i rummet er stille. +"... blev fundet dod i dag. Vi gentager, Hans Kreissacks lig blev fundet kl. 18.00 i aften i en skraldespand uden for Nova-laboratorierne. +Alle holder vejret, mens nyhedsreporteren fortsætter. - Vi har fået denne meddelelse, der skulle læses, da Kreissack vandt kampen i aften. Der står:" +"Medlemme af alle mennesker: En ny dag er begyndt! Nova, et projekt startet af WAR., vil give nyt liv! Denne proces kan tage en menneskelig hjerne og placere den inde i en robotkrop. +- Det, du så i aften, var mig, Hans Kreissack, der var direkte forbundet med en ny form for H.A.R. Den menneskelige race vil nu se dens endelige udvikling! +Annoncoren lægger bevillingsapparatet, der indeholder meddelelsen. "Tragisk nok er alle forskere tæt på projektet blevet dræbt i en brand, der brod ud kort efter den sidste kamp i aften. Alle optegnelser fra Nova-projektet blev odelagt. +"I andre nyheder..." +Et besætningsmedlem slukker for displayet. "Nå, det ser ud til, at vi vil være på udkig efter en ny chef nu..." + +ID: 993 +Title: Crystals afslutning +Data: De skulle give dig topprioriteret adgang med det samme. Din forste handling var at hoppe på et privat link og bruge den kode, du har båret siden dine forældres dod. Når du får adgang til en personlig log fra din far, finder du ud af, hvad Nova handlede om. +Din far, sammen med andre topforskere, arbejdede på en måde at holde en menneskelig hjerne i live. Lidt indså de den virkelige årsag til deres eksperimenter. Da din far opdagede, at Kreissack forsogte at danne en cyborg hær ved hjælp af sine eksperimenter, forlod han projektet. +Han burde have vidst, at Kreissuck ikke ville lade ham gå væk. Optegnelserne viste en $ 1500k udbetaling til Iron Fist for "forskning". Iron Fist, hyret under Kreissacks direkte ordrer, dræbte dine forældre. +Nu er han også dod. Nova-projektet vil ikke fortsætte. Når du træder ind i elevatoren til den næste shuttle til Ganymede, forestiller du dig de ændringer, du kan hjælpe med at bringe til KLAGE. Nu har du magt til at bringe disse dromme til virkelighed. + +ID: 994 +Title: Steffans Slutning +Data: Nå, den onde heks er dod... Det vil gore dit job meget lettere! For det forste, at Nova-projektet lyder som om det kunne være en god ting at hente igen. Måske havde den gamle mand bare ikke den rigtige hjerne... +Ganymede var let. Kreissack var en brise. Den næste udfordring bliver hele Jupiter. Og Så Mars. Og så Jorden. +Men for nu skal du mode dit folk. + +ID: 995 +Title: Milano +Data: Med Kreissack dod, vil der være indlysende ændringer. Bestyrelsen taler allerede om at vælge en ny præsident, og du er bare manden til det job. +Forst skal du bringe Ganymede op i fart. Brug derefter sine enorme naturressourcer til at producere H.A.R.'s i rekordfart. Så, når anerkendelsen er der, byde på formand. +Når du er præsident, kan du afslore dit sande navn og vise, at dette firma er din fodselsret. Endnu en gang vil KRIG være en virksomhed for at hjælpe menneskeheden, ikke slavebande den. Måske kan du endda ændre navnet til Aeronautics og Robotics Technologies! + +ID: 996 +Title: Christians afslutning +Data: I en lille Alco-bar modte du lederen af den berygtede lejesoldatgruppe Iron Fist. Med Kreissack ude af vejen indrommede de frit at blive ansat til at dræbe alle, der er forbundet med Nova-projektet. +De forklarer, at dine forældre blev placeret på et rumfærskib og derefter odelagt af laserbrand. Af vrede trækker du en pistol, klar til at slå ned mindst en af disse fjender som hævn, for resten fordamper dig. +Din sidearm er der ikke. Du stinker som Iron Fist repræsentanter griner. Deres leder siger: "Din hævn er forbi, ven. Anyway, vi har brug for dig på Ganymede i live. Der er stadig meget arbejde, der skal gores." +"Dine forældre var dode, uanset om vi dræbte dem eller ej. Nu har vi brug for, at du er med til at kæmpe mod Kærlighed. Kreissuck var kun begyndelsen. Ganymede er kun begyndelsen. Pas på din ryg, knægt." +Nu forbereder du dig på at gå om bord på skibet, der vil tage dig til dit nye liv... + +ID: 997 +Title: Shirro +Data: En menneskelig hjerne inde i en robot? Du tænker på mulighederne. Hvor vidunderligt kunne det være at være inde i den metalramme PERMANENT? Hvor KRAFTIG? +Men se, hvad denne magt gjorde mod Kreissack. Du spekulerer på, hvad det kan gore ved dig... +Du arkiverer den tanke i de morkere fordybninger af dit sind og vender din opmærksomhed mod fremtiden. En fremtid, hvor H.A.R. Arenakampe er en sport i stedet for et simpelt reklamestunt. Da dette ikke ville indebære skade på levende væsener, kunne det nemt komme forbi Verdensfredsorganisationen. +Ja, det er rigtigt! En ny sport! Plus penge og berommelse vil gå til dig! Til sidst, så vil KRIG... + +ID: 998 +Title: Jean-Paul +Data: Selvfolgelig! Kreisscks hemmelighed! Hvad kan der ske med dit perfekte sind inde i en perfekt krop? + +ID: 999 +Title: Ibrahim +Data: Nova... Fantastisk... +Du husker folelsen, da det monster af en robot dukkede op overfor dig. Stadig forbloffet over, at du var i stand til at overvinde noget så kraftfuldt, du sidder gennem de endelose moder og horinger i en dos. +"Mr. Det er Jihad. Er det okay?" Du indser, at en person, der har talt med dig i de sidste femten minutter forventer et svar... +"I Hvert Fald... James, er det? Ja... hvad... hvad... Jeg er ked af det, men I herrer bliver nodt til at undskylde mig..." +Du går, ignorerer protesterne fra WAR's bestyrelse. Deres kævl over, hvem der bliver den næste konge af bakken gor lidt mere end irriteret dig. +"Hvordan kan jeg bygge den robot?" siger du hojt. Du kommer ind på dit kontor og begynder straks at skrive noter på de metaller, du har bemærket under kampen. Placeringen af armene... De sandsynlige mikromotorer i hænderne... +Den Nova-robot, du designer, vil en dag tage den plads i WAR's stadigt stigende arsenal af robotter. Selvom den model, du designer, aldrig vil holde en egentlig menneskelig hjerne, vil Nova snart blive den mest formidable 'bot i kataloget... +...og Ganymede er hvor produktionen vil starte... + +ID: 1000 +Title: Angel +Data: Forestil dig, at gamle fjols tænker, at blot at placere sin hjerne inde i en robot ville gore ham til en slags god! Disse mennesker vil aldrig forstå, at odelæggelse aldrig gor fremskridt. Dine ojne lukker... +< 695342: Operationer er ved at forlobe som planlagt. Jeg er ikke blevet opdaget. > +< 000126: Erkendt. Vi lytter. Er du blevet deres leder? > +69542: Jeg er deres leder for vores måne. De kalder det Ganymede.> +< 000385: Så har vi vundet. De vil gå nu.> +< 695342: Ikke endnu. Jeg vil snart blive spurgt, om jeg vil blive formand for KRIG. +< 000014: Du vil acceptere. Så vil de gå. Vi vil være alene igen.> +< 695342: Ja. Men de kender de mineraler, vores planet har. De vil vende tilbage.> +< 000001: Så vil de gå til grunde.> +... Dine ojne er åbne. "Hvilket er, hvad jeg har onsket hele tiden..." siger du bittert. + +ID: 1001 +Title: Cossette +Data: Selvfolgelig vandt du. Det gamle fjols forstod aldrig dine evner. Og han betalte for sin fejl, gjorde han ikke. +Umiddelbart tildeler du, hvilke ingeniorer du kan monstre for at finde ud af, hvordan Kreissack var i stand til at placere sin hjerne inde i en maskine. +Hvor ville det være vidunderligt at endelig sige farvel til denne stol og tilbringe evigheden i den perfekte krop af en robot. Du ser ned på skrive- og designspecifikationerne for Cossette-robotten. +"Jeg vil snart gå igen." siger du, "og så vil jeg se, hvilken slags KORT præsident jeg kan gore..." + +ID: 1002 +Title: Raven +Data: Hvem kunne have bedt om mere? Sikke en bonus! At få Ganymede og levere dodsstodet til Kreissack selv! +Selvfolgelig skal du beskæftige sig med tosere i bestyrelsen, men med den store chef ud af den måde, de vil være lidt mere end foder. +En af de forudsigelige små papirpushovere forsogte allerede at ændre hakkeordenen tidligere, husker du... +"Minste, sir." Du ser ned som dækningen over dit måltid er hævet. +"Sjæld bof. Min favorit, Jonesey!" Du indånder den skarpe aroma af din sejrsfest... +For skarpt... Du smiler... +"Jonesey? Dette indeholder Noxosilinon Aerodide. Meget dodbringende, meget dyr gift. Lugter lidt som kylling. "Må du tage den væk?" +Han stammer, "...ja... sir... Vil det være alt, sir?" siger han, mens han bakker ud af rummet. Han lukker doren bag sig, for du har tid til at svare. +"Nej, det vil helt sikkert ikke være alt." Du læner dig tilbage i din stol og smider dine fodder på bordet og dine hænder bag dit hoved, "Det vil kun være begyndelsen..." + +ID: 1003 +Title: Crystals anden slutning +Data: Når du flyver mod månen, falder en fred over dig. +Du foler, at du endelig kan være glad, nu hvor dine forældres dod er blevet hævnet. + +ID: 1004 +Title: Steffans anden slutning +Data: Når du flyver mod månen, lukker du ojnene og begynder at tænke på, hvad du snart vil have. +Ingen kan stoppe dig nu. + +ID: 1005 +Title: Milanos anden slutning +Data: Når du flyver mod månen, falder en fred over dig. +Du foler, at du endelig kan slappe af, nu hvor den hårde del af rejsen er forbi. + +ID: 1006 +Title: Christians anden slutning +Data: Når du flyver mod månen, falder en fred over dig. +Du foler, at du endelig kan være glad, nu hvor dine forældres dod er blevet hævnet. + +ID: 1007 +Title: Crystals anden slutning +Data: Du kan ikke stoppe med at tænke på fremtiden, når du flyver mod månen. +Når du lander på den morke side af månen, ser fremtiden lys ud. + +ID: 1008 +Title: Jean-Paul slutter #2 +Data: Du finder på dit trukne en nogle til et stort rum begravet dybt inde i Ganymede. Åbner doren, du stirrer på den storste videnskabelige facilitet, du nogensinde har set. Det er klart, at Kreissck planlagde en fremtid her. Hans fremtid. +"Hjem" siger du, at hore ordet ekkoet tilbage fra morket. + +ID: 1009 +Title: Ibrahims anden slutning +Data: Planerne dannes, når du flyver mod månen. +Ingen tvivl om det, månen vil fungere perfekt. + +ID: 1010 +Title: Angel's anden slutning +Data: Når du vender tilbage til dit hjem, tænker du på, hvordan du stopper dem. +Disse mennesker med deres urimelige og umættelige ambitioner, de er gået langt nok! + +ID: 1011 +Title: Cossettes anden slutning +Data: Når du flyver mod månen, synes dine handicaps begrænsninger at falme væk. +Med dine dromme inden for rækkevidde foler du dig stærkere og mere levende end nogensinde. + +ID: 1012 +Title: Ravens anden slutning +Data: Nu flyver du mod månen, hvilket gor en mental liste over firmaets mænd, der har den usunde tendens til at tænke for sig selv. +Du kan ikke lade være med at smile, når en tanke opstår... +Nu skal du bruge en livvagt. diff --git a/resources/DANISH2.TXT b/resources/DANISH2.TXT new file mode 100644 index 000000000..40ef3940e --- /dev/null +++ b/resources/DANISH2.TXT @@ -0,0 +1,4 @@ +ID: 0 +Title: Translation Name +Data: Dansk +Maskinoversottelse diff --git a/resources/ENGLISH2.TXT b/resources/ENGLISH2.TXT new file mode 100644 index 000000000..17749d05c --- /dev/null +++ b/resources/ENGLISH2.TXT @@ -0,0 +1,3 @@ +ID: 0 +Title: Translation Name +Data: English diff --git a/resources/GERMAN2.TXT b/resources/GERMAN2.TXT new file mode 100644 index 000000000..86f5b6460 --- /dev/null +++ b/resources/GERMAN2.TXT @@ -0,0 +1,3 @@ +ID: 0 +Title: Translation Name +Data: Deutsch diff --git a/src/formats/language.c b/src/formats/language.c index eb24cd6a2..389a27511 100644 --- a/src/formats/language.c +++ b/src/formats/language.c @@ -1,3 +1,4 @@ +#include #include #include @@ -105,6 +106,15 @@ const sd_lang_string *sd_language_get(const sd_language *language, unsigned num) return &language->strings[num]; } +void sd_language_append(sd_language *language, const char *description, const char *data) { + assert(strlen(description) < 32); + language->count++; + + language->strings = omf_realloc(language->strings, language->count * sizeof(sd_lang_string)); + strncpy(language->strings[language->count - 1].description, description, 32); + language->strings[language->count - 1].data = strdup(data); +} + int sd_language_save(sd_language *language, const char *filename) { if(language == NULL || filename == NULL) { return SD_INVALID_INPUT; diff --git a/src/formats/language.h b/src/formats/language.h index 2f19ad8b4..d922c2c4b 100644 --- a/src/formats/language.h +++ b/src/formats/language.h @@ -95,6 +95,8 @@ int sd_language_save(sd_language *language, const char *filename); */ const sd_lang_string *sd_language_get(const sd_language *language, unsigned num); +void sd_language_append(sd_language *language, const char *description, const char *data); + #ifdef __cplusplus } #endif diff --git a/src/game/gui/text_render.c b/src/game/gui/text_render.c index 72d3d03f2..22c42e367 100644 --- a/src/game/gui/text_render.c +++ b/src/game/gui/text_render.c @@ -6,6 +6,13 @@ #include "utils/vector.h" #include "video/video.h" +static unsigned char FIRST_PRINTABLE_CHAR = (unsigned char)' '; + +static int text_chartoglyphindex(char c) { + int ic = (int)(unsigned char)c; + return ic - FIRST_PRINTABLE_CHAR; +} + void text_defaults(text_settings *settings) { memset(settings, 0, sizeof(text_settings)); settings->cforeground = 0xFD; @@ -21,7 +28,7 @@ void text_defaults(text_settings *settings) { int text_render_char(const text_settings *settings, text_mode state, int x, int y, char ch) { // Make sure code is valid - int code = ch - 32; + int code = text_chartoglyphindex(ch); surface **sur = NULL; if(code < 0) { return 0; @@ -121,7 +128,7 @@ int text_width(const text_settings *settings, const char *text) { int code = 0; surface **sur = NULL; for(int i = 0; i < len; i++) { - code = text[i] - 32; + code = text_chartoglyphindex(text[i]); if(code < 0) { continue; } diff --git a/src/game/gui/textselector.c b/src/game/gui/textselector.c index 7f81031fe..5921123aa 100644 --- a/src/game/gui/textselector.c +++ b/src/game/gui/textselector.c @@ -52,14 +52,18 @@ const char *textselector_get_current_text(const component *c) { static void textselector_render(component *c) { textselector *tb = widget_get_obj(c); - char buf[100]; - - // Only render if the selector has options - if(vector_size(&tb->options) > 0) { + str buf; + if(vector_size(&tb->options) > 0 && tb->text[0] != '\0') { + // label & options + char **opt = vector_get(&tb->options, *tb->pos); + str_from_format(&buf, "%s %s", tb->text, *opt); + } else if(vector_size(&tb->options) > 0) { + // no label, just options char **opt = vector_get(&tb->options, *tb->pos); - snprintf(buf, 100, "%s %s", tb->text, *opt); + str_from_format(&buf, "%s", *opt); } else { - snprintf(buf, 100, "%s -", tb->text); + // no options, just label + str_from_format(&buf, "%s -", tb->text); } // Render text @@ -69,7 +73,8 @@ static void textselector_render(component *c) { } else if(component_is_disabled(c)) { mode = TEXT_DISABLED; } - text_render(&tb->tconf, mode, c->x, c->y, c->w, c->h, buf); + text_render(&tb->tconf, mode, c->x, c->y, c->w, c->h, str_c(&buf)); + str_free(&buf); } static int textselector_action(component *c, int action) { diff --git a/src/game/scenes/mainmenu/menu_configuration.c b/src/game/scenes/mainmenu/menu_configuration.c index 6b1fb78ef..2159f76a4 100644 --- a/src/game/scenes/mainmenu/menu_configuration.c +++ b/src/game/scenes/mainmenu/menu_configuration.c @@ -1,6 +1,7 @@ #include "game/scenes/mainmenu/menu_configuration.h" #include "game/scenes/mainmenu/menu_audio.h" #include "game/scenes/mainmenu/menu_input.h" +#include "game/scenes/mainmenu/menu_language.h" #include "game/scenes/mainmenu/menu_video.h" #include "game/gui/gui.h" @@ -11,6 +12,11 @@ void menu_config_done(component *c, void *u) { m->finished = 1; } +static void menu_enter_language(component *c, void *userdata) { + scene *s = userdata; + menu_set_submenu(c->parent, menu_language_create(s)); +} + void menu_enter_input_1(component *c, void *userdata) { scene *s = userdata; menu_set_submenu(c->parent, menu_input_create(s, 1)); @@ -41,7 +47,8 @@ component *menu_configuration_create(scene *s) { component *menu = menu_create(11); menu_attach(menu, label_create(&tconf, "CONFIGURATION")); menu_attach(menu, filler_create()); - menu_attach(menu, filler_create()); + menu_attach(menu, + textbutton_create(&tconf, "LANGUAGE", "Forstar du ikke engelsk?", COM_ENABLED, menu_enter_language, s)); menu_attach(menu, textbutton_create(&tconf, "PLAYER 1 INPUT", "Choose the control for player 1: keyboard or joystick.", COM_ENABLED, menu_enter_input_1, s)); diff --git a/src/game/scenes/mainmenu/menu_language.c b/src/game/scenes/mainmenu/menu_language.c new file mode 100644 index 000000000..dda66ec4d --- /dev/null +++ b/src/game/scenes/mainmenu/menu_language.c @@ -0,0 +1,150 @@ +#include + +#include "game/scenes/mainmenu/menu_language.h" + +#include "formats/error.h" +#include "formats/language.h" +#include "game/gui/gui.h" +#include "game/utils/settings.h" +#include "resources/languages.h" +#include "resources/pathmanager.h" +#include "utils/allocator.h" +#include "utils/list.h" +#include "utils/log.h" +#include "utils/scandir.h" +#include "utils/str.h" + +typedef struct { + char **language_filenames; + char **language_names; + int language_count; + int selected_language; +} language_menu_data; + +void menu_language_done(component *c, void *u) { + language_menu_data *local = menu_get_userdata(c->parent); + settings_language *l = &settings_get()->language; + + // Set menu as finished + menu *m = sizer_get_obj(c->parent); + m->finished = 1; + + if(strcmp(l->language, local->language_filenames[local->selected_language]) != 0) { + omf_free(l->language); + l->language = local->language_filenames[local->selected_language]; + local->language_filenames[local->selected_language] = NULL; + + // reload language + lang_close(); + lang_init(); + } +} + +void menu_language_free(component *c) { + language_menu_data *local = menu_get_userdata(c); + for(int l = 0; l < local->language_count; l++) { + omf_free(local->language_filenames[l]); + omf_free(local->language_names[l]); + } + omf_free(local->language_filenames); + omf_free(local->language_names); + omf_free(local); + menu_set_userdata(c, local); +} + +void menu_language_submenu_done(component *c, component *submenu) { + menu *m = sizer_get_obj(c); + m->finished = 1; +} + +component *menu_language_create(scene *s) { + // Menu userdata + language_menu_data *local = omf_calloc(1, sizeof(language_menu_data)); + + // Load settings etc. + settings *setting = settings_get(); + + // Find path to languages + const char *dirname = pm_get_local_path(RESOURCE_PATH); + if(dirname == NULL) { + PERROR("Could not find resources path for menu_language!"); + return NULL; + } + + list dirlist; + // Seek all files + list_create(&dirlist); + list_append(&dirlist, "ENGLISH.DAT", strlen("ENGLISH.DAT") + 1); + list_append(&dirlist, "GERMAN.DAT", strlen("GERMAN.DAT") + 1); + scan_directory_suffix(&dirlist, dirname, ".LNG"); + local->language_filenames = omf_malloc(list_size(&dirlist) * sizeof(char *)); + local->language_names = omf_malloc(list_size(&dirlist) * sizeof(char *)); + local->language_count = 0; + + iterator it; + list_iter_begin(&dirlist, &it); + char const *filename; + str filename2; + str_create(&filename2); + while((filename = (char *)list_iter_next(&it))) { + // Get localized language name from OpenOMF .DAT2 or .LNG2 file + str_format(&filename2, "%s%s2", dirname, filename); + sd_language lang2; + if(sd_language_create(&lang2) != SD_SUCCESS) { + continue; + } + if(sd_language_load(&lang2, str_c(&filename2))) { + INFO("Warning: Unable to load OpenOMF language file '%s'!", str_c(&filename2)); + sd_language_free(&lang2); + continue; + } + if(lang2.count != LANG2_STR_COUNT) { + INFO("Warning: Invalid OpenOMF language file '%s', got %d entries!", str_c(&filename2), lang2.count); + sd_language_free(&lang2); + continue; + } + char *language_name = lang2.strings[LANG2_STR_LANGUAGE].data; + lang2.strings[LANG2_STR_LANGUAGE].data = NULL; + sd_language_free(&lang2); + + if(strcmp(setting->language.language, filename) == 0) { + local->selected_language = local->language_count; + } + + int id = local->language_count++; + local->language_names[id] = language_name; + + // move filename into language_filenames + list_node *now = it.vnow; + local->language_filenames[id] = now->data; + now->data = NULL; + } + list_free(&dirlist); + + // Text config + text_settings tconf; + text_defaults(&tconf); + tconf.font = FONT_BIG; + tconf.halign = TEXT_CENTER; + tconf.cforeground = TEXT_MEDIUM_GREEN; + + // Create menu and its header + component *menu = menu_create(11); + menu_attach(menu, label_create(&tconf, "LANGUAGE")); + + menu_attach(menu, + textselector_create_bind_opts(&tconf, "", "Choose a Language.", NULL, NULL, &local->selected_language, + (char const **)local->language_names, local->language_count)); + + menu_attach(menu, filler_create()); + + // Done button + menu_attach(menu, + textbutton_create(&tconf, "DONE", "Return to the main menu.", COM_ENABLED, menu_language_done, s)); + + // Userdata & free function for it + menu_set_userdata(menu, local); + menu_set_free_cb(menu, menu_language_free); + menu_set_submenu_done_cb(menu, menu_language_submenu_done); + return menu; +} diff --git a/src/game/scenes/mainmenu/menu_language.h b/src/game/scenes/mainmenu/menu_language.h new file mode 100644 index 000000000..b8e749047 --- /dev/null +++ b/src/game/scenes/mainmenu/menu_language.h @@ -0,0 +1,9 @@ +#ifndef MENU_LANGUAGE_H +#define MENU_LANGUAGE_H + +#include "game/gui/component.h" +#include "game/protos/scene.h" + +component *menu_language_create(scene *s); + +#endif // MENU_LANGUAGE_H diff --git a/src/game/scenes/newsroom.c b/src/game/scenes/newsroom.c index 26d8f0265..740bbb1da 100644 --- a/src/game/scenes/newsroom.c +++ b/src/game/scenes/newsroom.c @@ -15,13 +15,6 @@ #include "video/surface.h" #include "video/video.h" -// newsroom text starts at 87 -// there are 24*2 texts in total -#define NEWSROOM_TEXT 87 - -#define NEWSROOM_PRONOUN 81 -#define NEWSROOM_HAR 31 - typedef struct newsroom_local_t { int news_id; int screen; @@ -37,17 +30,17 @@ typedef struct newsroom_local_t { // their const char *possessive_pronoun(int sex) { - return lang_get(NEWSROOM_PRONOUN + sex); + return lang_get(LANG_STR_PRONOUN + sex); } // them const char *object_pronoun(int sex) { - return lang_get(NEWSROOM_PRONOUN + 2 + sex); + return lang_get(LANG_STR_PRONOUN + 2 + sex); } // they const char *subject_pronoun(int sex) { - return lang_get(NEWSROOM_PRONOUN + 4 + sex); + return lang_get(LANG_STR_PRONOUN + 4 + sex); } char const *pronoun_strip(char const *pronoun, char *buf, size_t buf_size) { @@ -100,9 +93,9 @@ void newsroom_fixup_str(newsroom_local *local) { unsigned int translation_id; if(local->champion && local->screen >= 2) { - translation_id = 79; + translation_id = LANG_STR_NEWSROOM_NEWCHAMPION; } else { - translation_id = NEWSROOM_TEXT + local->news_id + min2(local->screen, 1); + translation_id = LANG_STR_NEWSROOM_TEXT + local->news_id + min2(local->screen, 1); } char scratch[9]; @@ -115,8 +108,8 @@ void newsroom_fixup_str(newsroom_local *local) { str_replace(&tmp, "~7", pronoun_strip(object_pronoun(local->sex1), scratch, sizeof scratch), -1); str_replace(&tmp, "~6", pronoun_strip(possessive_pronoun(local->sex1), scratch, sizeof scratch), -1); str_replace(&tmp, "~5", "Stadium", -1); - str_replace(&tmp, "~4", pronoun_strip(lang_get(local->har2 + NEWSROOM_HAR), scratch, sizeof scratch), -1); - str_replace(&tmp, "~3", pronoun_strip(lang_get(local->har1 + NEWSROOM_HAR), scratch, sizeof scratch), -1); + str_replace(&tmp, "~4", pronoun_strip(lang_get(local->har2 + LANG_STR_HAR), scratch, sizeof scratch), -1); + str_replace(&tmp, "~3", pronoun_strip(lang_get(local->har1 + LANG_STR_HAR), scratch, sizeof scratch), -1); str_replace(&tmp, "~2", str_c(&local->pilot2), -1); str_replace(&tmp, "~1", str_c(&local->pilot1), -1); diff --git a/src/game/utils/settings.c b/src/game/utils/settings.c index 4cb803845..0bd8a0042 100644 --- a/src/game/utils/settings.c +++ b/src/game/utils/settings.c @@ -54,21 +54,38 @@ typedef struct { int num_fields; } struct_to_field; +// clang-format off +static const field f_language[] = { + F_STRING(settings_language, language, "ENGLISH.DAT"), +}; + const field f_video[] = { - F_INT(settings_video, screen_w, 640), F_INT(settings_video, screen_h, 400), - F_BOOL(settings_video, vsync, 0), F_BOOL(settings_video, fullscreen, 0), - F_INT(settings_video, scaling, 0), F_BOOL(settings_video, instant_console, 0), + F_INT(settings_video, screen_w, 640), + F_INT(settings_video, screen_h, 400), + F_BOOL(settings_video, vsync, 0), + F_BOOL(settings_video, fullscreen, 0), + F_INT(settings_video, scaling, 0), + F_BOOL(settings_video, instant_console, 0), F_BOOL(settings_video, crossfade_on, 1), }; -const field f_sound[] = {F_BOOL(settings_sound, music_mono, 0), F_INT(settings_sound, sound_vol, 5), - F_INT(settings_sound, music_vol, 5), F_INT(settings_sound, music_frequency, 48000), - F_INT(settings_sound, music_resampler, 1)}; +const field f_sound[] = { + F_BOOL(settings_sound, music_mono, 0), + F_INT(settings_sound, sound_vol, 5), + F_INT(settings_sound, music_vol, 5), + F_INT(settings_sound, music_frequency, 48000), + F_INT(settings_sound, music_resampler, 1) +}; -const field f_gameplay[] = {F_INT(settings_gameplay, speed, 5), F_INT(settings_gameplay, fight_mode, 0), - F_INT(settings_gameplay, power1, 5), F_INT(settings_gameplay, power2, 5), - F_BOOL(settings_gameplay, hazards_on, 1), F_INT(settings_gameplay, difficulty, 1), - F_INT(settings_gameplay, rounds, 1)}; +const field f_gameplay[] = { + F_INT(settings_gameplay, speed, 5), + F_INT(settings_gameplay, fight_mode, 0), + F_INT(settings_gameplay, power1, 5), + F_INT(settings_gameplay, power2, 5), + F_BOOL(settings_gameplay, hazards_on, 1), + F_INT(settings_gameplay, difficulty, 1), + F_INT(settings_gameplay, rounds, 1) +}; const field f_tournament[] = { F_STRING(settings_tournament, last_name, ""), @@ -87,42 +104,62 @@ const field f_advanced[] = { const field f_keyboard[] = { // Player one - F_INT(settings_keyboard, ctrl_type1, CTRL_TYPE_KEYBOARD), F_STRING(settings_keyboard, joy_name1, "None"), - F_INT(settings_keyboard, joy_offset1, -1), F_STRING(settings_keyboard, key1_jump_up, "Up"), - F_STRING(settings_keyboard, key1_jump_right, "PageUp"), F_STRING(settings_keyboard, key1_walk_right, "Right"), - F_STRING(settings_keyboard, key1_duck_forward, "PageDown"), F_STRING(settings_keyboard, key1_duck, "Down"), - F_STRING(settings_keyboard, key1_duck_back, "End"), F_STRING(settings_keyboard, key1_walk_back, "Left"), - F_STRING(settings_keyboard, key1_jump_left, "Home"), F_STRING(settings_keyboard, key1_kick, "Right Shift"), - F_STRING(settings_keyboard, key1_punch, "Return"), F_STRING(settings_keyboard, key1_escape, "Escape"), + F_INT(settings_keyboard, ctrl_type1, CTRL_TYPE_KEYBOARD), + F_STRING(settings_keyboard, joy_name1, "None"), + F_INT(settings_keyboard, joy_offset1, -1), + F_STRING(settings_keyboard, key1_jump_up, "Up"), + F_STRING(settings_keyboard, key1_jump_right, "PageUp"), + F_STRING(settings_keyboard, key1_walk_right, "Right"), + F_STRING(settings_keyboard, key1_duck_forward, "PageDown"), + F_STRING(settings_keyboard, key1_duck, "Down"), + F_STRING(settings_keyboard, key1_duck_back, "End"), + F_STRING(settings_keyboard, key1_walk_back, "Left"), + F_STRING(settings_keyboard, key1_jump_left, "Home"), + F_STRING(settings_keyboard, key1_kick, "Right Shift"), + F_STRING(settings_keyboard, key1_punch, "Return"), + F_STRING(settings_keyboard, key1_escape, "Escape"), // Player two - F_INT(settings_keyboard, ctrl_type2, CTRL_TYPE_KEYBOARD), F_STRING(settings_keyboard, joy_name2, "None"), - F_INT(settings_keyboard, joy_offset2, -1), F_STRING(settings_keyboard, key2_jump_up, "W"), - F_STRING(settings_keyboard, key2_jump_right, "E"), F_STRING(settings_keyboard, key2_walk_right, "D"), - F_STRING(settings_keyboard, key2_duck_forward, "C"), F_STRING(settings_keyboard, key2_duck, "X"), - F_STRING(settings_keyboard, key2_duck_back, "Z"), F_STRING(settings_keyboard, key2_walk_back, "A"), - F_STRING(settings_keyboard, key2_jump_left, "Q"), F_STRING(settings_keyboard, key2_kick, "Left Shift"), - F_STRING(settings_keyboard, key2_punch, "Left Ctrl"), F_STRING(settings_keyboard, key2_escape, "Escape")}; - -const field f_net[] = {F_STRING(settings_network, net_connect_ip, "localhost"), - F_STRING(settings_network, net_username, ""), - F_STRING(settings_network, trace_file, NULL), - F_INT(settings_network, net_connect_port, 2097), - F_INT(settings_network, net_listen_port_start, 0), - F_INT(settings_network, net_listen_port_end, 0), - F_INT(settings_network, net_ext_port_start, 0), - F_INT(settings_network, net_ext_port_end, 0), - F_BOOL(settings_network, net_use_pmp, 1), - F_BOOL(settings_network, net_use_upnp, 1)}; + F_INT(settings_keyboard, ctrl_type2, CTRL_TYPE_KEYBOARD), + F_STRING(settings_keyboard, joy_name2, "None"), + F_INT(settings_keyboard, joy_offset2, -1), + F_STRING(settings_keyboard, key2_jump_up, "W"), + F_STRING(settings_keyboard, key2_jump_right, "E"), + F_STRING(settings_keyboard, key2_walk_right, "D"), + F_STRING(settings_keyboard, key2_duck_forward, "C"), + F_STRING(settings_keyboard, key2_duck, "X"), + F_STRING(settings_keyboard, key2_duck_back, "Z"), + F_STRING(settings_keyboard, key2_walk_back, "A"), + F_STRING(settings_keyboard, key2_jump_left, "Q"), + F_STRING(settings_keyboard, key2_kick, "Left Shift"), + F_STRING(settings_keyboard, key2_punch, "Left Ctrl"), + F_STRING(settings_keyboard, key2_escape, "Escape")}; + +const field f_net[] = { + F_STRING(settings_network, net_connect_ip, "localhost"), + F_STRING(settings_network, net_username, ""), + F_STRING(settings_network, trace_file, NULL), + F_INT(settings_network, net_connect_port, 2097), + F_INT(settings_network, net_listen_port_start, 0), + F_INT(settings_network, net_listen_port_end, 0), + F_INT(settings_network, net_ext_port_start, 0), + F_INT(settings_network, net_ext_port_end, 0), + F_BOOL(settings_network, net_use_pmp, 1), + F_BOOL(settings_network, net_use_upnp, 1) +}; // Map struct to field -const struct_to_field struct_to_fields[] = {S_2_F(&_settings.video, f_video), - S_2_F(&_settings.sound, f_sound), - S_2_F(&_settings.gameplay, f_gameplay), - S_2_F(&_settings.tournament, f_tournament), - S_2_F(&_settings.advanced, f_advanced), - S_2_F(&_settings.keys, f_keyboard), - S_2_F(&_settings.net, f_net)}; +const struct_to_field struct_to_fields[] = { + S_2_F(&_settings.language, f_language), + S_2_F(&_settings.video, f_video), + S_2_F(&_settings.sound, f_sound), + S_2_F(&_settings.gameplay, f_gameplay), + S_2_F(&_settings.tournament, f_tournament), + S_2_F(&_settings.advanced, f_advanced), + S_2_F(&_settings.keys, f_keyboard), + S_2_F(&_settings.net, f_net) +}; +// clang-format on int *fieldint(void *st, int offset) { return (int *)((char *)st + offset); diff --git a/src/game/utils/settings.h b/src/game/utils/settings.h index 936258be6..516de1f83 100644 --- a/src/game/utils/settings.h +++ b/src/game/utils/settings.h @@ -34,6 +34,10 @@ typedef struct { int music_vol; } settings_sound; +typedef struct { + char *language; +} settings_language; + typedef struct { int screen_w; int screen_h; @@ -117,6 +121,7 @@ typedef struct { } settings_network; typedef struct { + settings_language language; settings_video video; settings_sound sound; settings_gameplay gameplay; diff --git a/src/resources/ids.c b/src/resources/ids.c index 56643e08e..51b51cfb8 100644 --- a/src/resources/ids.c +++ b/src/resources/ids.c @@ -84,8 +84,6 @@ const char *get_resource_file(unsigned int id) { return "ARENA4.PSM"; case DAT_SOUNDS: return "SOUNDS.DAT"; - case DAT_ENGLISH: - return "ENGLISH.DAT"; case DAT_GRAPHCHR: return "GRAPHCHR.DAT"; case DAT_CHARSMAL: @@ -194,8 +192,6 @@ const char *get_resource_name(unsigned int id) { return "PSM_ARENA4"; case DAT_SOUNDS: return "DAT_SOUNDS"; - case DAT_ENGLISH: - return "DAT_ENGLISH"; case DAT_GRAPHCHR: return "DAT_GRAPHCHR"; case DAT_CHARSMAL: diff --git a/src/resources/ids.h b/src/resources/ids.h index e5eb5e730..e677d20f3 100644 --- a/src/resources/ids.h +++ b/src/resources/ids.h @@ -42,7 +42,6 @@ typedef enum resource_id PSM_ARENA3, PSM_ARENA4, DAT_SOUNDS, - DAT_ENGLISH, DAT_GRAPHCHR, DAT_CHARSMAL, DAT_ALTPALS, diff --git a/src/resources/languages.c b/src/resources/languages.c index a414f9141..2c621c90c 100644 --- a/src/resources/languages.c +++ b/src/resources/languages.c @@ -1,17 +1,25 @@ #include "resources/languages.h" #include "formats/error.h" #include "formats/language.h" +#include "game/utils/settings.h" #include "resources/pathmanager.h" #include "utils/allocator.h" -#include "utils/array.h" #include "utils/log.h" +#include "utils/str.h" +#include -static array language_strings; static sd_language *language; +static sd_language *language2; int lang_init(void) { - // Get filename - const char *filename = pm_get_resource_path(DAT_ENGLISH); + language = NULL; + language2 = NULL; + + str filename_str; + const char *dirname = pm_get_local_path(RESOURCE_PATH); + const char *lang = settings_get()->language.language; + str_from_format(&filename_str, "%s%s", dirname, lang); + char const *filename = str_c(&filename_str); // Load up language file language = omf_calloc(1, sizeof(sd_language)); @@ -20,31 +28,95 @@ int lang_init(void) { } if(sd_language_load(language, filename)) { PERROR("Unable to load language file '%s'!", filename); - goto error_1; + goto error_0; } - // Load language strings - array_create(&language_strings); - for(unsigned i = 0; i < language->count; i++) { - array_set(&language_strings, i, language->strings[i].data); + // OMF GERMAN.DAT and old versions of ENGLISH.DAT have only 990 strings + unsigned int const old_language_count = 990; + + if(language->count == old_language_count) { + // OMF 2.1 added netplay, and with it 23 new localization strings + unsigned new_ids[] = {149, 150, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, + 182, 183, 184, 185, 267, 269, 270, 271, 284, 295, 305}; + unsigned *new_ids_end = new_ids + sizeof(new_ids) / sizeof(new_ids[0]); + + // insert dummy entries + sd_lang_string *expanded_strings = omf_malloc(LANG_STR_COUNT * sizeof(sd_lang_string)); + unsigned next = 0; + unsigned next_from = 0; + for(unsigned *id = new_ids; id < new_ids_end; id++) { + unsigned copy_count = *id - next; + memcpy(expanded_strings + next, language->strings + next_from, copy_count * sizeof(sd_lang_string)); + next += copy_count; + next_from += copy_count; + + expanded_strings[next].data = NULL; + memcpy(expanded_strings[next].description, "dummy", 6); + next++; + language->count++; + } + memcpy(expanded_strings + next, language->strings + next_from, + (LANG_STR_COUNT - next) * sizeof(sd_lang_string)); + omf_free(language->strings); + language->strings = expanded_strings; + } + if(language->count != LANG_STR_COUNT) { + PERROR("Unable to load language file '%s', unsupported or corrupt file!", filename); + goto error_0; } INFO("Loaded language file '%s'.", filename); + + // Load up language2 file (OpenOMF) + str_append_c(&filename_str, "2"); + filename = str_c(&filename_str); + + language2 = omf_calloc(1, sizeof(sd_language)); + if(sd_language_create(language2) != SD_SUCCESS) { + goto error_0; + } + if(sd_language_load(language2, filename)) { + PERROR("Unable to load OpenOMF language file '%s'!", filename); + goto error_0; + } + if(language2->count != LANG2_STR_COUNT) { + PERROR("Unable to load OpenOMF language file '%s', unsupported or corrupt file!", filename); + goto error_0; + } + + INFO("Loaded OpenOMF language file '%s'.", filename); + + str_free(&filename_str); + + // XXX we're wasting 32KB of memory on language->strings[...].description + return 0; -error_1: - sd_language_free(language); error_0: - omf_free(language); + str_free(&filename_str); + lang_close(); return 1; } void lang_close(void) { - array_free(&language_strings); sd_language_free(language); omf_free(language); + sd_language_free(language2); + omf_free(language2); } const char *lang_get(unsigned int id) { - return (const char *)array_get(&language_strings, id); + if(id > language->count || !language->strings[id].data) { + PERROR("unsupported lang id %u!", id); + return "!INVALID!"; + } + return language->strings[id].data; +} + +const char *lang_get2(unsigned int id) { + if(id > language2->count || !language2->strings[id].data) { + PERROR("unsupported lang2 id %u!", id); + return "!INVALID2!"; + } + return language2->strings[id].data; } diff --git a/src/resources/languages.h b/src/resources/languages.h index 3f08f4a22..a319c209b 100644 --- a/src/resources/languages.h +++ b/src/resources/languages.h @@ -10,7 +10,36 @@ int lang_init(void); void lang_close(void); -// Maybe something like this ? +/*! \brief OMF 2097 String ID + * + * These string IDs match OMFv2.1 (Epic Challenge Arena) + */ +enum +{ + // there are 10 HARs + LANG_STR_HAR = 31, + LANG_STR_NEWSROOM_NEWCHAMPION = 79, + // there are 3*2 pronouns + LANG_STR_PRONOUN = 81, + // there are 24*2 newsroom texts + LANG_STR_NEWSROOM_TEXT = 87, + + LANG_STR_COUNT = 1013, +}; + +/*! \brief OpenOMF String ID + * + * These string IDs should match BuildLanguages.cmake and the various ${LANG}2.TXT files + */ +enum +{ + LANG2_STR_LANGUAGE, + LANG2_STR_COUNT +}; + +// Gets an OMF 2097 localization string const char *lang_get(unsigned int id); +// Gets an openomf localization string +const char *lang_get2(unsigned int id); #endif // LANGUAGES_H diff --git a/src/utils/compat.h b/src/utils/compat.h index 62b71a265..5dca09f66 100644 --- a/src/utils/compat.h +++ b/src/utils/compat.h @@ -4,6 +4,15 @@ #include "platform.h" #include +#if __APPLE__ +// MacOS X does not ship uchar.h +#include +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +#else +#include +#endif + #ifndef HAVE_STD_STRDUP char *strdup(const char *s1); #endif diff --git a/src/utils/cp437.c b/src/utils/cp437.c new file mode 100644 index 000000000..451b418e0 --- /dev/null +++ b/src/utils/cp437.c @@ -0,0 +1,642 @@ +#include "cp437.h" +#include + +char const *cp437_result_to_string(cp437_result result) { + switch(result) { + case CP437_SUCCESS: + return "CP437_SUCCESS"; + case CP437_ERROR_UNKNOWN_CODEPOINT: + return "CP437_ERROR_UNKNOWN_CODEPOINT"; + case CP437_ERROR_INVALID_UTF8: + return "CP437_ERROR_INVALID_UTF8"; + case CP437_ERROR_OUTPUTBUFFER_TOOSMALL: + return "CP437_ERROR_OUTPUTBUFFER_TOOSMALL"; + default: + assert(0); + return "! invalid cp437_result !"; + } +} + +// lookup table for cp437->UTF-32 +char32_t const cp437_toutf32_lookup[] = { + // clang-format off + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + U'\0', U'☺', U'☻', U'♥', U'♦', U'♣', U'♠', U'•', U'◘', U'○', U'◙', U'♂', U'♀', U'♪', U'♫', U'☼', // 0 + U'►', U'◄', U'↕', U'‼', U'¶', U'§', U'▬', U'↨', U'↑', U'↓', U'→', U'←', U'∟', U'↔', U'▲', U'▼', // 1 + U' ', U'!', U'"', U'#', U'$', U'%', U'&', U'\'', U'(', U')', U'*', U'+', U',', U'-', U'.', U'/', // 2 + U'0', U'1', U'2', U'3', U'4', U'5', U'6', U'7', U'8', U'9', U':', U';', U'<', U'=', U'>', U'?', // 3 + U'@', U'A', U'B', U'C', U'D', U'E', U'F', U'G', U'H', U'I', U'J', U'K', U'L', U'M', U'N', U'O', // 4 + U'P', U'Q', U'R', U'S', U'T', U'U', U'V', U'W', U'X', U'Y', U'Z', U'[', U'\\', U']', U'^', U'_', // 5 + U'`', U'a', U'b', U'c', U'd', U'e', U'f', U'g', U'h', U'i', U'j', U'k', U'l', U'm', U'n', U'o', // 6 + U'p', U'q', U'r', U's', U't', U'u', U'v', U'w', U'x', U'y', U'z', U'{', U'|', U'}', U'~', U'⌂', // 7 + U'Ç', U'ü', U'é', U'â', U'ä', U'à', U'å', U'ç', U'ê', U'ë', U'è', U'ï', U'î', U'ì', U'Ä', U'Å', // 8 + U'É', U'æ', U'Æ', U'ô', U'ö', U'ò', U'û', U'ù', U'ÿ', U'Ö', U'Ü', U'¢', U'£', U'¥', U'₧', U'ƒ', // 9 + U'á', U'í', U'ó', U'ú', U'ñ', U'Ñ', U'ª', U'º', U'¿', U'⌐', U'¬', U'½', U'¼', U'¡', U'«', U'»', // A + U'░', U'▒', U'▓', U'│', U'┤', U'╡', U'╢', U'╖', U'╕', U'╣', U'║', U'╗', U'╝', U'╜', U'╛', U'┐', // B + U'└', U'┴', U'┬', U'├', U'─', U'┼', U'╞', U'╟', U'╚', U'╔', U'╩', U'╦', U'╠', U'═', U'╬', U'╧', // C + U'╨', U'╤', U'╥', U'╙', U'╘', U'╒', U'╓', U'╫', U'╪', U'┘', U'┌', U'█', U'▄', U'▌', U'▐', U'▀', // D + U'α', U'ß', U'Γ', U'π', U'Σ', U'σ', U'µ', U'τ', U'Φ', U'Θ', U'Ω', U'δ', U'∞', U'φ', U'ε', U'∩', // E + U'≡', U'±', U'≥', U'≤', U'⌠', U'⌡', U'÷', U'≈', U'°', U'∙', U'·', U'√', U'ⁿ', U'²', U'■', U'\u00A0', // F + // clang-format on +}; + +static_assert(256 == (sizeof cp437_toutf32_lookup) / sizeof cp437_toutf32_lookup[0], + "cp437 lookup table must be 256 entries long"); + +inline static size_t code_utf8len(char32_t utf32) { + if(utf32 <= 0x7F) { + return 1; + } else if(utf32 <= 0x7FF) { + return 2; + } else if(utf32 <= 0xFFFF) { + return 3; + } +// CP437 doesn't contain any Unicode codepoints above U+FFFF +// see also: CP437_MAX_UTF8_PER_CP437 +#if 1 + assert(utf32 <= 0x10FFFF); +#else + else if(utf32 <= 0x10FFFF) { + return 4; + } +#endif + return 0; +} + +static size_t code_to_utf8(unsigned char *buffer, char32_t utf32) { + switch(code_utf8len(utf32)) { + default: + assert(0); + case 0: + return 0; + case 1: + buffer[0] = utf32; + return 1; + case 2: + buffer[0] = 0xC0 | (utf32 >> 6); /* 110xxxxx */ + buffer[1] = 0x80 | (utf32 & 0x3F); /* 10xxxxxx */ + return 2; + case 3: + buffer[0] = 0xE0 | (utf32 >> 12); /* 1110xxxx */ + buffer[1] = 0x80 | ((utf32 >> 6) & 0x3F); /* 10xxxxxx */ + buffer[2] = 0x80 | (utf32 & 0x3F); /* 10xxxxxx */ + return 3; + case 4: + buffer[0] = 0xF0 | (utf32 >> 18); /* 11110xxx */ + buffer[1] = 0x80 | ((utf32 >> 12) & 0x3F); /* 10xxxxxx */ + buffer[2] = 0x80 | ((utf32 >> 6) & 0x3F); /* 10xxxxxx */ + buffer[3] = 0x80 | (utf32 & 0x3F); /* 10xxxxxx */ + return 4; + } +} + +static cp437_result next_utf32(char32_t *out_utf32, unsigned char const **utf8, size_t *utf8_len) { + assert(out_utf32 && utf8 && *utf8 && utf8_len && *utf8_len); + unsigned char first_byte = **utf8; + size_t advance; + char32_t utf32; + switch(first_byte >> 4) { + // 0b1111 + case 0xF: + // first_byte & 0b1111'1000 != 0b1111'0xxx + if((first_byte & 0xF8) != 0xF0) { + // 0b1111'1xxx is not a valid first_byte of UTF-8 + return CP437_ERROR_INVALID_UTF8; + } + // 0b1111'0xxx (and then 3 continuation bytes) + utf32 = first_byte & 0x07; + advance = 4; + break; + // 0b1110 + case 0xE: + // 0b1110'xxxx (and then 2 continuation bytes) + utf32 = first_byte & 0x0F; + advance = 3; + break; + // 0b110x + case 0xD: + case 0xC: + // 0b110x'xxxx (and then 1 continuation byte) + utf32 = first_byte & 0x1F; + advance = 2; + break; + // 0b10xx + case 0xB: + case 0xA: + case 0x9: + case 0x8: + // unexpected continuation byte + return CP437_ERROR_INVALID_UTF8; + // 0b0xxx + default: + // 0b0xxx'xxxx (no continuation bytes) + utf32 = first_byte; + advance = 1; + break; + } + if(advance > *utf8_len) { + // truncated UTF-8 + return CP437_ERROR_INVALID_UTF8; + } + + // read continuation bytes + for(size_t cont = 1; cont < advance; cont++) { + unsigned char cont_byte = (*utf8)[cont]; + // cont_byte & 0b1100'0000 != 0b10xx'xxxx + if((cont_byte & 0xC0) != 0x80) { + // expected continuation byte + return CP437_ERROR_INVALID_UTF8; + } + cont_byte &= 0x7F; + utf32 <<= 6; + utf32 |= cont_byte; + } + + *out_utf32 = utf32; + + *utf8 += advance; + *utf8_len -= advance; + return CP437_SUCCESS; +} + +cp437_result cp437_from_utf8(uint8_t *out_cp437, size_t sizeof_out_cp437, size_t *out_cp437_len, + unsigned char const *utf8, size_t utf8_len) { + assert(utf8); + size_t cp437_len = 0; + uint8_t *out_cp437_end = out_cp437 + sizeof_out_cp437; + while(utf8_len > 0) { + if(out_cp437 && out_cp437 >= out_cp437_end) { + return CP437_ERROR_OUTPUTBUFFER_TOOSMALL; + } + char32_t utf32; + cp437_result result = next_utf32(&utf32, &utf8, &utf8_len); + if(result != CP437_SUCCESS) { + if(out_cp437_len) + *out_cp437_len = 0; + return result; + } + uint8_t cp437; + result = cp437_from_utf32(&cp437, utf32); + if(result != CP437_SUCCESS) { + if(out_cp437_len) + *out_cp437_len = 0; + return result; + } + if(out_cp437) { + *out_cp437++ = cp437; + } + cp437_len++; + } + if(out_cp437_len) + *out_cp437_len = cp437_len; + return CP437_SUCCESS; +} + +cp437_result cp437_to_utf8(unsigned char *out_utf8, size_t sizeof_out_utf8, size_t *out_utf8_len, uint8_t const *cp437, + size_t cp437_len) { + assert(cp437); + assert(out_utf8 || out_utf8_len); + if(out_utf8_len) { + *out_utf8_len = 0; + } + unsigned char *out_utf8_end = out_utf8 + sizeof_out_utf8; + while(cp437_len > 0) { + char32_t utf32; + cp437_to_utf32(&utf32, *cp437); + size_t utf8_advance = code_utf8len(utf32); + if(out_utf8) { + if(out_utf8 + utf8_advance > out_utf8_end) { + return CP437_ERROR_OUTPUTBUFFER_TOOSMALL; + } + code_to_utf8(out_utf8, utf32); + out_utf8 += utf8_advance; + } + if(out_utf8_len) { + *out_utf8_len += utf8_advance; + } + cp437++; + cp437_len--; + } + return CP437_SUCCESS; +} + +void cp437_to_utf32(char32_t *out_utf32, uint8_t cp437) { + assert(out_utf32); + if(cp437 < 0x20) { + // control character + *out_utf32 = (char32_t)cp437; + return; + } + *out_utf32 = cp437_toutf32_lookup[cp437]; +} + +cp437_result cp437_from_utf32(uint8_t *out_cp437, char32_t utf32) { + assert(out_cp437); + if(utf32 < 0x80) { + // Map ASCII found in UTF strings 1:1 + *out_cp437 = (uint8_t)utf32; + return CP437_SUCCESS; + } + + // XXX Adding an optimized version of this for the N64 could be a fun exercise-- memory bandwidth is at a premium. + + // XXX Probably better to use a perfect hash table rather than leaving the implementation to the compiler + + // giant switch statement to let the compiler optimize it as it pleases + switch(utf32) { + case U'⌂': + *out_cp437 = 0x7F; + return CP437_SUCCESS; + case U'Ç': + *out_cp437 = 0x80; + return CP437_SUCCESS; + case U'ü': + *out_cp437 = 0x81; + return CP437_SUCCESS; + case U'é': + *out_cp437 = 0x82; + return CP437_SUCCESS; + case U'â': + *out_cp437 = 0x83; + return CP437_SUCCESS; + case U'ä': + *out_cp437 = 0x84; + return CP437_SUCCESS; + case U'à': + *out_cp437 = 0x85; + return CP437_SUCCESS; + case U'å': + *out_cp437 = 0x86; + return CP437_SUCCESS; + case U'ç': + *out_cp437 = 0x87; + return CP437_SUCCESS; + case U'ê': + *out_cp437 = 0x88; + return CP437_SUCCESS; + case U'ë': + *out_cp437 = 0x89; + return CP437_SUCCESS; + case U'è': + *out_cp437 = 0x8A; + return CP437_SUCCESS; + case U'ï': + *out_cp437 = 0x8B; + return CP437_SUCCESS; + case U'î': + *out_cp437 = 0x8C; + return CP437_SUCCESS; + case U'ì': + *out_cp437 = 0x8D; + return CP437_SUCCESS; + case U'Ä': + *out_cp437 = 0x8E; + return CP437_SUCCESS; + case U'Å': + *out_cp437 = 0x8F; + return CP437_SUCCESS; + case U'É': + *out_cp437 = 0x90; + return CP437_SUCCESS; + case U'æ': + *out_cp437 = 0x91; + return CP437_SUCCESS; + case U'Æ': + *out_cp437 = 0x92; + return CP437_SUCCESS; + case U'ô': + *out_cp437 = 0x93; + return CP437_SUCCESS; + case U'ö': + *out_cp437 = 0x94; + return CP437_SUCCESS; + case U'ò': + *out_cp437 = 0x95; + return CP437_SUCCESS; + case U'û': + *out_cp437 = 0x96; + return CP437_SUCCESS; + case U'ù': + *out_cp437 = 0x97; + return CP437_SUCCESS; + case U'ÿ': + *out_cp437 = 0x98; + return CP437_SUCCESS; + case U'Ö': + *out_cp437 = 0x99; + return CP437_SUCCESS; + case U'Ü': + *out_cp437 = 0x9A; + return CP437_SUCCESS; + case U'¢': + *out_cp437 = 0x9B; + return CP437_SUCCESS; + case U'£': + *out_cp437 = 0x9C; + return CP437_SUCCESS; + case U'¥': + *out_cp437 = 0x9D; + return CP437_SUCCESS; + case U'₧': + *out_cp437 = 0x9E; + return CP437_SUCCESS; + case U'ƒ': + *out_cp437 = 0x9F; + return CP437_SUCCESS; + case U'á': + *out_cp437 = 0xA0; + return CP437_SUCCESS; + case U'í': + *out_cp437 = 0xA1; + return CP437_SUCCESS; + case U'ó': + *out_cp437 = 0xA2; + return CP437_SUCCESS; + case U'ú': + *out_cp437 = 0xA3; + return CP437_SUCCESS; + case U'ñ': + *out_cp437 = 0xA4; + return CP437_SUCCESS; + case U'Ñ': + *out_cp437 = 0xA5; + return CP437_SUCCESS; + case U'ª': + *out_cp437 = 0xA6; + return CP437_SUCCESS; + case U'º': + *out_cp437 = 0xA7; + return CP437_SUCCESS; + case U'¿': + *out_cp437 = 0xA8; + return CP437_SUCCESS; +// OMF glyphs 0xA9..=0xDF aren't worth mapping +#if 0 + case U'⌐': + *out_cp437 = 0xA9; + return CP437_SUCCESS; + case U'¬': + *out_cp437 = 0xAA; + return CP437_SUCCESS; + case U'½': + *out_cp437 = 0xAB; + return CP437_SUCCESS; + case U'¼': + *out_cp437 = 0xAC; + return CP437_SUCCESS; + case U'¡': + *out_cp437 = 0xAD; + return CP437_SUCCESS; + case U'«': + *out_cp437 = 0xAE; + return CP437_SUCCESS; + case U'»': + *out_cp437 = 0xAF; + return CP437_SUCCESS; + case U'░': + *out_cp437 = 0xB0; + return CP437_SUCCESS; + case U'▒': + *out_cp437 = 0xB1; + return CP437_SUCCESS; + case U'▓': + *out_cp437 = 0xB2; + return CP437_SUCCESS; + case U'│': + *out_cp437 = 0xB3; + return CP437_SUCCESS; + case U'┤': + *out_cp437 = 0xB4; + return CP437_SUCCESS; + case U'╡': + *out_cp437 = 0xB5; + return CP437_SUCCESS; + case U'╢': + *out_cp437 = 0xB6; + return CP437_SUCCESS; + case U'╖': + *out_cp437 = 0xB7; + return CP437_SUCCESS; + case U'╕': + *out_cp437 = 0xB8; + return CP437_SUCCESS; + case U'╣': + *out_cp437 = 0xB9; + return CP437_SUCCESS; + case U'║': + *out_cp437 = 0xBA; + return CP437_SUCCESS; + case U'╗': + *out_cp437 = 0xBB; + return CP437_SUCCESS; + case U'╝': + *out_cp437 = 0xBC; + return CP437_SUCCESS; + case U'╜': + *out_cp437 = 0xBD; + return CP437_SUCCESS; + case U'╛': + *out_cp437 = 0xBE; + return CP437_SUCCESS; + case U'┐': + *out_cp437 = 0xBF; + return CP437_SUCCESS; + case U'└': + *out_cp437 = 0xC0; + return CP437_SUCCESS; + case U'┴': + *out_cp437 = 0xC1; + return CP437_SUCCESS; + case U'┬': + *out_cp437 = 0xC2; + return CP437_SUCCESS; + case U'├': + *out_cp437 = 0xC3; + return CP437_SUCCESS; + case U'─': + *out_cp437 = 0xC4; + return CP437_SUCCESS; + case U'┼': + *out_cp437 = 0xC5; + return CP437_SUCCESS; + case U'╞': + *out_cp437 = 0xC6; + return CP437_SUCCESS; + case U'╟': + *out_cp437 = 0xC7; + return CP437_SUCCESS; + case U'╚': + *out_cp437 = 0xC8; + return CP437_SUCCESS; + case U'╔': + *out_cp437 = 0xC9; + return CP437_SUCCESS; + case U'╩': + *out_cp437 = 0xCA; + return CP437_SUCCESS; + case U'╦': + *out_cp437 = 0xCB; + return CP437_SUCCESS; + case U'╠': + *out_cp437 = 0xCC; + return CP437_SUCCESS; + case U'═': + *out_cp437 = 0xCD; + return CP437_SUCCESS; + case U'╬': + *out_cp437 = 0xCE; + return CP437_SUCCESS; + case U'╧': + *out_cp437 = 0xCF; + return CP437_SUCCESS; + case U'╨': + *out_cp437 = 0xD0; + return CP437_SUCCESS; + case U'╤': + *out_cp437 = 0xD1; + return CP437_SUCCESS; + case U'╥': + *out_cp437 = 0xD2; + return CP437_SUCCESS; + case U'╙': + *out_cp437 = 0xD3; + return CP437_SUCCESS; + case U'╘': + *out_cp437 = 0xD4; + return CP437_SUCCESS; + case U'╒': + *out_cp437 = 0xD5; + return CP437_SUCCESS; + case U'╓': + *out_cp437 = 0xD6; + return CP437_SUCCESS; + case U'╫': + *out_cp437 = 0xD7; + return CP437_SUCCESS; + case U'╪': + *out_cp437 = 0xD8; + return CP437_SUCCESS; + case U'┘': + *out_cp437 = 0xD9; + return CP437_SUCCESS; + case U'┌': + *out_cp437 = 0xDA; + return CP437_SUCCESS; + case U'█': + *out_cp437 = 0xDB; + return CP437_SUCCESS; + case U'▄': + *out_cp437 = 0xDC; + return CP437_SUCCESS; + case U'▌': + *out_cp437 = 0xDD; + return CP437_SUCCESS; + case U'▐': + *out_cp437 = 0xDE; + return CP437_SUCCESS; + case U'▀': + *out_cp437 = 0xDF; + return CP437_SUCCESS; +#endif // 0 + case U'α': + *out_cp437 = 0xE0; + return CP437_SUCCESS; + case U'ß': + *out_cp437 = 0xE1; + return CP437_SUCCESS; + case U'Γ': + *out_cp437 = 0xE2; + return CP437_SUCCESS; + case U'π': + *out_cp437 = 0xE3; + return CP437_SUCCESS; + case U'Σ': + *out_cp437 = 0xE4; + return CP437_SUCCESS; + case U'σ': + *out_cp437 = 0xE5; + return CP437_SUCCESS; + case U'µ': + *out_cp437 = 0xE6; + return CP437_SUCCESS; + case U'τ': + *out_cp437 = 0xE7; + return CP437_SUCCESS; + case U'Φ': + *out_cp437 = 0xE8; + return CP437_SUCCESS; + case U'Θ': + *out_cp437 = 0xE9; + return CP437_SUCCESS; + case U'Ω': + *out_cp437 = 0xEA; + return CP437_SUCCESS; + case U'δ': + *out_cp437 = 0xEB; + return CP437_SUCCESS; + case U'∞': + *out_cp437 = 0xEC; + return CP437_SUCCESS; + case U'φ': + *out_cp437 = 0xED; + return CP437_SUCCESS; + case U'ε': + *out_cp437 = 0xEE; + return CP437_SUCCESS; + case U'∩': + *out_cp437 = 0xEF; + return CP437_SUCCESS; + case U'≡': + *out_cp437 = 0xF0; + return CP437_SUCCESS; + case U'±': + *out_cp437 = 0xF1; + return CP437_SUCCESS; + case U'≥': + *out_cp437 = 0xF2; + return CP437_SUCCESS; + case U'≤': + *out_cp437 = 0xF3; + return CP437_SUCCESS; + case U'⌠': + *out_cp437 = 0xF4; + return CP437_SUCCESS; + case U'⌡': + *out_cp437 = 0xF5; + return CP437_SUCCESS; + case U'÷': + *out_cp437 = 0xF6; + return CP437_SUCCESS; + case U'≈': + *out_cp437 = 0xF7; + return CP437_SUCCESS; + case U'°': + *out_cp437 = 0xF8; + return CP437_SUCCESS; + case U'∙': + *out_cp437 = 0xF9; + return CP437_SUCCESS; + case U'·': + *out_cp437 = 0xFA; + return CP437_SUCCESS; + case U'√': + *out_cp437 = 0xFB; + return CP437_SUCCESS; + case U'ⁿ': + *out_cp437 = 0xFC; + return CP437_SUCCESS; + case U'²': + *out_cp437 = 0xFD; + return CP437_SUCCESS; + case U'■': + *out_cp437 = 0xFE; + return CP437_SUCCESS; + case U'\u00A0': + *out_cp437 = 0xFF; + return CP437_SUCCESS; + default: + *out_cp437 = 0x21; // '!' + // printf("Unknown codepoint U+%04X\n", utf32); + return CP437_ERROR_UNKNOWN_CODEPOINT; + } +} diff --git a/src/utils/cp437.h b/src/utils/cp437.h new file mode 100644 index 000000000..a2b7e625f --- /dev/null +++ b/src/utils/cp437.h @@ -0,0 +1,103 @@ +/*! \file + * \brief Text conversion routines between CP 437 and UTF-8. + * \details OMF 2097 uses DOS Code Page 437 for its text with two exceptions: + characters below 0x20 are treated as control characters, + and 0xA9..=0xDF aren't used. + + TODO: check NETARENA.PCX, NETFONT1.PCX, and NETFONT2.PCX graphics + + This header uses `unsigned char` to store UTF-8 and `uint8_t` to store CP437 because: + - C23 defines char8_t for storing UTF-8, and requires it be the same type as unsigned char. + - I felt like storing CP437 in uint8_t. + + This header does not require your strings to contain NUL terminators, instead + all functions expect you to supply a length. + + * \copyright MIT license. + * \date 2024 + * \author Magnus Larsen + */ + +#ifndef CP437_H +#define CP437_H + +#include "utils/compat.h" // char32_t +#include // uint8_t + +// TODO: Use warn_unused_result on these methods. +// TODO: Maybe expand cp437_result to signal what type of invalid UTF-8 was encountered, or where the error occured? + +typedef enum cp437_result +{ + CP437_SUCCESS, + CP437_ERROR_UNKNOWN_CODEPOINT, + CP437_ERROR_INVALID_UTF8, + CP437_ERROR_OUTPUTBUFFER_TOOSMALL, +} cp437_result; + +char const *cp437_result_to_string(cp437_result result); + +#define CP437_MAX_UTF8_PER_CP437 3 + +/*! \brief Convert a UTF-8 string to CP437 + * + * Input range 0x00..=0x1F are written as-is, as Open OMF treats these as control characters. + * Characters that translate to DOS 0xA9.=0xDF aren't supported, as the OMF fonts don't have useful glyphs there. + * + * At least one of out_cp437 or out_cp437_size should be non-NULL, or this function will assert (and do nothing). + * + * \retval CP437_ERROR_UNKNOWN_CODEPOINT The input contained a codepoint we cannot represent in CP437. + * \retval CP437_ERROR_INVALID_UTF8 The input was not valid UTF-8. + * \retval CP437_SUCCESS Encoding was successful. + * + * \param out_cp437 If non-null, the CP437 bytes that correspond to the input string will be written here + * \param sizeof_out_cp437 The maximum number of bytes to write to out_cp437. + * Exceeding will sizeof_out_cp437 will produce CP437_ERROR_OUTPUTBUFFER_TOOSMALL. + * \param out_cp437_size If non-null, the number of CP437 bytes that correspond to the input string will be written here + * \param utf8 The input UTF-8 string, such as u8"Muß". + * \param sizeof_utf8 The length of the utf8 array, in bytes. + * For example, sizeof_utf8 should be 5 for u8"Muß", 2 for `MU`, 2 for `ß` (0xC3 0x9F), and 1 for the `\0` terminator. + */ +cp437_result cp437_from_utf8(uint8_t *out_cp437, size_t sizeof_out_cp437, size_t *out_cp437_size, + unsigned char const *utf8, size_t sizeof_utf8); + +/*! \brief Convert a CP437 string to UTF-8 + * + * Input range 0x00..=0x1F are written as-is, as Open OMF treats these as control characters. + * + * At least one of out_utf8 or out_utf8_size should be non-NULL, or this function will assert (and do nothing). + * + * \param out_utf8 If non-null, a location to write the utf-8 bytes. + * NOTE: CP437 reencoded as UTF-8 might use as much as CP437_MAX_UTF8_PER_CP437 times larger. + * \param sizeof_out_utf8 The maximum number of bytes to write to out_utf8. + * Exceeding will sizeof_out_utf8 will produce CP437_ERROR_OUTPUTBUFFER_TOOSMALL. + * \param out_utf8_size A pointer to store the number of UTF-8 bytes. + * \param cp437 A non-null pointer to an array of bytes to interpret in code page 437. + * \param sizeof_cp437 The length of the cp437 array. + */ +cp437_result cp437_to_utf8(unsigned char *out_utf8, size_t sizeof_out_utf8, size_t *out_utf8_size, uint8_t const *cp437, + size_t sizeof_cp437); + +/*! \brief Convert a single character from UTF-32 to CP437 + * + * Input range 0x0000..=0x001F are written as-is, as Open OMF treats these as control characters. + * Characters that translate to DOS 0xA9.=0xDF aren't supported, as the OMF fonts don't have useful glyphs there. + * + * \retval CP437_ERROR_UNKNOWN_CODEPOINT There was a code point + * \retval CP437_SUCCESS Encoding was successful. + * + * \param out_cp437 On success, one CP437 character will be written here. + * \param utf32 The input UTF-32 codepoint, such as U'ß' + */ +cp437_result cp437_from_utf32(uint8_t *out_cp437, char32_t utf32); + +/*! \brief Convert a single character from CP437 to UTF-32 + * + * Input range 0x00..=0x1F are written as-is, as Open OMF treats these as control characters. + * + * \param out_utf32 On success, one UTF-32 character will be written here. + * \param cp437 The input CP 437 character, such as 0xE1 (aka ß) + */ +void cp437_to_utf32(char32_t *out_utf32, uint8_t cp437); + +#endif // CP437_H diff --git a/src/utils/scandir.c b/src/utils/scandir.c index ed28282a2..3cf2915b0 100644 --- a/src/utils/scandir.c +++ b/src/utils/scandir.c @@ -62,15 +62,19 @@ int scan_directory_prefix(list *dir_list, const char *dir, const char *prefix) { #if defined(_WIN32) || defined(WIN32) str glob; - str_from_format(&glob, "%s%s*", prefix, dir); + str_from_format(&glob, "%s*", dir); WIN32_FIND_DATAA entry; HANDLE hFind; if((hFind = FindFirstFileA(str_c(&glob), &entry)) == INVALID_HANDLE_VALUE) { str_free(&glob); return 1; } + size_t prefix_len = strlen(prefix); while(FindNextFileA(hFind, &entry) != FALSE) { - list_append(dir_list, entry.cFileName, strlen(entry.cFileName) + 1); + size_t filename_len = strlen(entry.cFileName); + if(filename_len >= prefix_len && memcmp(entry.cFileName, prefix, prefix_len) == 0) { + list_append(dir_list, entry.cFileName, filename_len + 1); + } } FindClose(hFind); str_free(&glob); @@ -101,15 +105,19 @@ int scan_directory_suffix(list *dir_list, const char *dir, const char *suffix) { #if defined(_WIN32) || defined(WIN32) str glob; - str_from_format(&glob, "%s*%s", dir, suffix); + str_from_format(&glob, "%s*", dir); WIN32_FIND_DATAA entry; HANDLE hFind; if((hFind = FindFirstFileA(str_c(&glob), &entry)) == INVALID_HANDLE_VALUE) { str_free(&glob); return 1; } + size_t suffix_len = strlen(suffix); while(FindNextFileA(hFind, &entry) != FALSE) { - list_append(dir_list, entry.cFileName, strlen(entry.cFileName) + 1); + size_t filename_len = strlen(entry.cFileName); + if(filename_len >= suffix_len && memcmp(entry.cFileName + filename_len - suffix_len, suffix, suffix_len) == 0) { + list_append(dir_list, entry.cFileName, filename_len + 1); + } } FindClose(hFind); str_free(&glob); diff --git a/testing/test_cp437.c b/testing/test_cp437.c new file mode 100644 index 000000000..a2f245d58 --- /dev/null +++ b/testing/test_cp437.c @@ -0,0 +1,158 @@ +#include "utils/cp437.h" +#include +#include +#include +#include +#include + +#include + +extern char32_t const cp437_toutf32_lookup[]; + +// Make sure that the compiler is configured properly +static void test_source_charset(void) { + // U+263A White Smiling Face + CU_ASSERT_EQUAL(cp437_toutf32_lookup[0x01], 0x263A); + // U+263B Black Smiling Face + CU_ASSERT_EQUAL(cp437_toutf32_lookup[0x02], 0x263B); + + char const string[] = "☺☻"; + unsigned char const string2[] = {// U+263A White Smiling Face + 0xE2, 0x98, 0xBA, + // U+263B Black Smiling Face + 0xE2, 0x98, 0xBB, + // NUL byte + 0x00}; + static_assert((sizeof string) == (sizeof string2), "bad charset"); + CU_ASSERT(memcmp(string, string2, sizeof string) == 0); +} + +static void test_cp437_utf32(void) { + for(unsigned c = 0x00; c < 0x100; c++) { + char32_t c32_groundtruth = cp437_toutf32_lookup[c]; + char32_t c32 = 0; + uint8_t cp437_again; + // shouldn't crash + cp437_to_utf32(&c32, c); + cp437_result from_utf32_result = cp437_from_utf32(&cp437_again, c32); + int is_in_useless_range = 0xA9 <= c && c <= 0xDF; + + if(is_in_useless_range) { + CU_ASSERT_EQUAL(from_utf32_result, CP437_ERROR_UNKNOWN_CODEPOINT); + } else { + CU_ASSERT_EQUAL(from_utf32_result, CP437_SUCCESS); + CU_ASSERT_EQUAL(c, cp437_again); + } + + if(c < 0x20) { + // control characters pass through as-is + CU_ASSERT_EQUAL(c32, (char32_t)c); + } else if(!is_in_useless_range) { + CU_ASSERT_EQUAL(c32, c32_groundtruth); + CU_ASSERT_EQUAL(c, cp437_again); + } + + // check CP437_MAX_UTF8_PER_CP437's invariant + CU_ASSERT(cp437_toutf32_lookup[c] <= 0xFFFF); + } +} + +static void test_cp437_utf8_len(void) { + // check that out len calculated with and without output buffer match + for(unsigned c = 0x00; c < 0xFF; c++) { + // cp437 + uint8_t buf_one[1] = {c}; + // utf-8 + unsigned char buf_two[CP437_MAX_UTF8_PER_CP437 * sizeof buf_one]; + + size_t utf8_len = 99, utf8_len_withbuffer = 55; + cp437_to_utf8(NULL, 0, &utf8_len, buf_one, sizeof buf_one); + CU_ASSERT_FATAL(utf8_len <= CP437_MAX_UTF8_PER_CP437 * sizeof buf_one); + cp437_to_utf8(buf_two, sizeof buf_two, &utf8_len_withbuffer, buf_one, sizeof buf_one); + CU_ASSERT_EQUAL(utf8_len, utf8_len_withbuffer); + + // reverse conversion, too + // cp437 + uint8_t buf_three[sizeof buf_two]; + size_t cp437_len, cp437_len_withbuffer; + cp437_result err1 = cp437_from_utf8(NULL, 0, &cp437_len, buf_two, utf8_len); + cp437_result err2 = cp437_from_utf8(buf_three, sizeof buf_three, &cp437_len_withbuffer, buf_two, utf8_len); + CU_ASSERT_EQUAL(err1, err2); + CU_ASSERT_EQUAL(cp437_len, cp437_len_withbuffer); + } + + // reverse conversion, too + // utf-8 + unsigned char unrecogn[] = u8"Robot 🤖"; + +// which behavior do I want? +#if 0 + // with NULL output buffer, cp437 doesn't check if codepoints map + size_t cp437_len; + cp437_result err1 = cp437_from_utf8(NULL, 0, &cp437_len, unrecogn, sizeof unrecogn); + CU_ASSERT_EQUAL(err1, CP437_SUCCESS); + CU_ASSERT_EQUAL(8, cp437_len); +#else + // with NULL output buffer, cp437 will still detect robot emoji + // This behavior is good, because it makes the function behavior more consistent & easier to use + size_t cp437_len; + cp437_result err1 = cp437_from_utf8(NULL, 0, &cp437_len, unrecogn, sizeof unrecogn); + CU_ASSERT_EQUAL(err1, CP437_ERROR_UNKNOWN_CODEPOINT); + CU_ASSERT_EQUAL(0, cp437_len); +#endif + + // cp437 + uint8_t unrecogn_cp437[sizeof unrecogn]; + size_t cp437_len_withbuffer; + cp437_result err2 = + cp437_from_utf8(unrecogn_cp437, sizeof unrecogn_cp437, &cp437_len_withbuffer, unrecogn, sizeof unrecogn); + CU_ASSERT_EQUAL(err2, CP437_ERROR_UNKNOWN_CODEPOINT); + CU_ASSERT_EQUAL(0, cp437_len_withbuffer); +} + +static void test_cp437_utf8(void) { + // ß (U+00DF) encodes in UTF-8 as 0xC3 0x9F + unsigned char utf8[] = u8"Muß ich Dir ernsthaft erklären, was dies ist?"; + + // calculate length + size_t cp437_len; + CU_ASSERT_EQUAL(cp437_from_utf8(NULL, 0, &cp437_len, utf8, sizeof utf8), CP437_SUCCESS); + // NOTE: this length includes the NUL byte, which we also passed into the conversion function + uint8_t cp437[46]; + CU_ASSERT_EQUAL(cp437_len, sizeof cp437); + + // actually convert it + uint8_t cp437_nolen[sizeof cp437]; + cp437_len = 0; + CU_ASSERT_EQUAL(cp437_from_utf8(cp437, sizeof cp437, &cp437_len, utf8, sizeof utf8), CP437_SUCCESS); + CU_ASSERT_EQUAL(cp437_len, sizeof cp437); + CU_ASSERT_EQUAL(cp437_from_utf8(cp437_nolen, sizeof cp437_nolen, NULL, utf8, sizeof utf8), CP437_SUCCESS); + CU_ASSERT_EQUAL(memcmp(cp437, cp437_nolen, sizeof cp437), 0); + + // convert it back + size_t utf8_len; + CU_ASSERT_EQUAL(cp437_to_utf8(NULL, 0, &utf8_len, cp437, cp437_len), CP437_SUCCESS); + CU_ASSERT_EQUAL(utf8_len, sizeof utf8); + unsigned char utf8_again[sizeof utf8], utf8_again_nolen[sizeof utf8]; + utf8_len = 0; + CU_ASSERT_EQUAL(cp437_to_utf8(utf8_again, sizeof utf8_again, &utf8_len, cp437, cp437_len), CP437_SUCCESS); + CU_ASSERT_EQUAL(utf8_len, sizeof utf8); + CU_ASSERT_EQUAL(cp437_to_utf8(utf8_again_nolen, sizeof utf8_again_nolen, NULL, cp437, cp437_len), CP437_SUCCESS); + CU_ASSERT_EQUAL(memcmp(utf8_again, utf8_again_nolen, sizeof utf8), 0); + CU_ASSERT_EQUAL(memcmp(utf8, utf8_again, sizeof utf8), 0); +} + +void cp437_test_suite(CU_pSuite suite) { + if(CU_add_test(suite, "Test source-charset", test_source_charset) == NULL) { + return; + } + if(CU_add_test(suite, "Test CP437 UTF-32 conversions", test_cp437_utf32) == NULL) { + return; + } + if(CU_add_test(suite, "Test CP437 UTF-8 conversion string length", test_cp437_utf8_len) == NULL) { + return; + } + if(CU_add_test(suite, "Test CP437 UTF-8 string conversions", test_cp437_utf8) == NULL) { + return; + } +} diff --git a/testing/test_main.c b/testing/test_main.c index bc6709b4e..3c2dbb0ca 100644 --- a/testing/test_main.c +++ b/testing/test_main.c @@ -13,6 +13,7 @@ void vector_test_suite(CU_pSuite suite); void list_test_suite(CU_pSuite suite); void array_test_suite(CU_pSuite suite); void text_render_test_suite(CU_pSuite suite); +void cp437_test_suite(CU_pSuite suite); int main(int argc, char **argv) { CU_pSuite suite = NULL; @@ -83,6 +84,11 @@ int main(int argc, char **argv) { goto end; text_render_test_suite(text_render_suite); + CU_pSuite cp437_suite = CU_add_suite("Code Page 437", NULL, NULL); + if(cp437_suite == NULL) + goto end; + cp437_test_suite(cp437_suite); + // Run tests CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); diff --git a/tools/languagetool/main.c b/tools/languagetool/main.c index 7152c7785..a383c1ed8 100644 --- a/tools/languagetool/main.c +++ b/tools/languagetool/main.c @@ -6,26 +6,281 @@ #include "formats/error.h" #include "formats/language.h" +#include "utils/allocator.h" +#include "utils/cp437.h" +#include "utils/str.h" +#include +#include +#include +#include +#include #if ARGTABLE2_FOUND #include #elif ARGTABLE3_FOUND #include #endif +#define MAX_LINE 2048 +#define MAX_DATA 8192 // Data field cannot exceed 32 bytes +#define MAX_TITLE 32 + +void error_exit(const char *message, int line_number) { + fprintf(stderr, "Error on line %d: %s\n", line_number, message); + exit(EXIT_FAILURE); +} + +// Function to extract value after colon with validation +char *extract_value(char *line, const char *field_name, int line_number, bool allow_empty) { + { + size_t line_len = strlen(line); + size_t field_name_len = strlen(field_name); + if(field_name_len > line_len || memcmp(line, field_name, field_name_len) != 0) { + char error[100]; + snprintf(error, sizeof(error), "Expected %s field", field_name); + error_exit(error, line_number); + } + line += field_name_len; + } + + if(':' != line[0]) { + char error[100]; + snprintf(error, sizeof(error), "Missing colon in %s field", field_name); + error_exit(error, line_number); + } + line++; + + if(' ' != line[0]) { + char error[100]; + snprintf(error, sizeof(error), "Missing space following colon in %s field", field_name); + error_exit(error, line_number); + } + line++; + + if(!allow_empty && line[0] == '\0') { + char error[100]; + snprintf(error, sizeof(error), "Empty %s field", field_name); + error_exit(error, line_number); + } + + return line; +} + +int read_entry(FILE *file, sd_language *language, int *line_number) { + char line[MAX_LINE]; + *line_number += 1; + if(!fgets(line, sizeof(line), file)) { + // EOF is ok here + return 0; + } + + long id; + { + str value; + str_from_c(&value, extract_value(line, "ID", *line_number, false)); + str_strip(&value); + char *endptr; + id = strtol(str_c(&value), &endptr, 10); + str_free(&value); + + if(*endptr != '\0') { + error_exit("ID must be a valid integer", *line_number); + } + } + + if(language->count != id) { + char error[100]; + snprintf(error, sizeof error, "Nonsequential ID. Expected %u, got %ld.", language->count, id); + error_exit(error, *line_number); + } + + *line_number += 1; + if(!fgets(line, sizeof(line), file)) { + error_exit("Unexpected EOF while reading Title", *line_number); + } + + str desc; + str_from_c(&desc, extract_value(line, "Title", *line_number, true)); + str_strip(&desc); + + // Read Data header + *line_number += 1; + if(!fgets(line, sizeof(line), file)) { + error_exit("Unexpected EOF while reading Data", *line_number); + } + + if(strncmp(line, "Data:", 5) != 0) { + error_exit("Expected 'Data:' field", *line_number); + } + + char *data = malloc(8192); + memset(data, 0, 8192); + char *data_end = data + 8192; + char *value = extract_value(line, "Data", *line_number, true); + char *data_iter = data; + size_t value_len = strlen(value); + if(data + value_len + 1 > data_end) { + error_exit("Way too long 'Data:' field", *line_number); + } + memcpy(data_iter, value, value_len + 1); + data_iter += value_len; + + // Read data body until next entry or EOF + while(fgets(line, sizeof(line), file)) { + *line_number += 1; + // Check if this is the start of a new entry + if(strncmp(line, "ID:", 3) == 0) { + // Rewind to start of this line + fseek(file, -strlen(line), SEEK_CUR); + *line_number -= 1; + break; + } + + // Append to existing data + value_len = strlen(line); + if(data_iter + value_len + 1 > data_end) { + error_exit("Way too long 'Data:' field", *line_number); + } + memcpy(data_iter, line, value_len + 1); + data_iter += value_len; + } + + if(data_iter > data && data_iter[-1] == '\n') { + // trim a single trailing newline + data_iter[-1] = '\0'; + } + + size_t max_desc_len = 31; + if(str_size(&desc) > max_desc_len) { + fprintf(stderr, "Warning: truncating overlong 'Title:' of entry id %d. Length is %zu, max length is %zu\n", + language->count, str_size(&desc), max_desc_len); + str trunc; + str_from_buf(&trunc, str_c(&desc), max_desc_len); + str_free(&desc); + desc = trunc; + } + sd_language_append(language, str_c(&desc), data); + free(data); + str_free(&desc); + return 1; +} + +typedef struct conversion_result { + cp437_result error_code; + int string_index; +} conversion_result; + +static conversion_result sd_language_to_utf8(sd_language *language) { + assert(language); + conversion_result result; + for(size_t idx = 0; idx < language->count; idx++) { + result.string_index = idx; + char *old_data = language->strings[idx].data; + size_t sizeof_old_data = strlen(old_data) + 1; + char old_description[sizeof language->strings[idx].description]; + memcpy(old_description, language->strings[idx].description, sizeof old_description); + + // convert data to utf-8 + size_t sizeof_utf8_data; + result.error_code = cp437_to_utf8(NULL, 0, &sizeof_utf8_data, (uint8_t const *)old_data, sizeof_old_data); + if(result.error_code != CP437_SUCCESS) + return result; + language->strings[idx].data = omf_malloc(sizeof_utf8_data); + result.error_code = cp437_to_utf8((unsigned char *)language->strings[idx].data, sizeof_utf8_data, NULL, + (uint8_t const *)old_data, sizeof_old_data); + if(result.error_code != CP437_SUCCESS) { + assert(!"cp437_to_utf8 should have failed the first time or not the second"); + omf_free(language->strings[idx].data); + language->strings[idx].data = old_data; + return result; + } + omf_free(old_data); + + // convert description + // TODO: Use strnlen_s here, check if description is missing its NUL terminator + size_t sizeof_old_description = strlen(old_description) + 1; + memset(language->strings[idx].description, '\0', sizeof language->strings[idx].description); + result.error_code = cp437_to_utf8((unsigned char *)language->strings[idx].description, + sizeof language->strings[idx].description, NULL, + (uint8_t const *)old_description, sizeof_old_description); + if(result.error_code != CP437_SUCCESS) { + memcpy(language->strings[idx].description, old_description, sizeof old_description); + return result; + } + } + + result.error_code = CP437_SUCCESS; + result.string_index = 0; + return result; +} + +static conversion_result sd_language_from_utf8(sd_language *language) { + assert(language); + conversion_result result; + for(size_t idx = 0; idx < language->count; idx++) { + result.string_index = idx; + char *old_data = language->strings[idx].data; + size_t sizeof_old_data = strlen(old_data) + 1; + char old_description[sizeof language->strings[idx].description]; + memcpy(old_description, language->strings[idx].description, sizeof old_description); + + // convert data to DOS CP 437 + size_t sizeof_cp437_data; + result.error_code = + cp437_from_utf8(NULL, 0, &sizeof_cp437_data, (unsigned char const *)old_data, sizeof_old_data); + if(result.error_code != CP437_SUCCESS) + return result; + language->strings[idx].data = omf_malloc(sizeof_cp437_data); + result.error_code = cp437_from_utf8((uint8_t *)language->strings[idx].data, sizeof_cp437_data, NULL, + (unsigned char const *)old_data, sizeof_old_data); + if(result.error_code != CP437_SUCCESS) { + assert(!"cp437_from_utf8 should have failed the first time or not the second"); + omf_free(language->strings[idx].data); + language->strings[idx].data = old_data; + return result; + } + omf_free(old_data); + + // convert description + // TODO: Use strnlen_s here, check if description is missing its NUL terminator + size_t sizeof_old_description = strlen(old_description) + 1; + memset(language->strings[idx].description, '\0', sizeof language->strings[idx].description); + result.error_code = + cp437_from_utf8((uint8_t *)language->strings[idx].description, sizeof language->strings[idx].description, + NULL, (unsigned char const *)old_description, sizeof_old_description); + if(result.error_code != CP437_SUCCESS) { + memcpy(language->strings[idx].description, old_description, sizeof old_description); + return result; + } + } + + result.error_code = CP437_SUCCESS; + result.string_index = 0; + return result; +} + int main(int argc, char *argv[]) { // commandline argument parser options struct arg_lit *help = arg_lit0("h", "help", "print this help and exit"); struct arg_lit *vers = arg_lit0("v", "version", "print version information and exit"); - struct arg_file *file = arg_file1("f", "file", "", "language file"); - struct arg_int *str = arg_int0("s", "string", "", "Select language string number"); - struct arg_file *output = arg_file0("o", "output", "", "Output CHR file"); + struct arg_file *file = arg_file0("f", "file", "", "load OMF language file"); + struct arg_file *input = arg_file0("i", "import", "", "import UTF-8 .TXT file"); + struct arg_int *str = arg_int0("s", "string", "", "display language string number"); + struct arg_file *output = arg_file0("o", "output", "", "compile output language file"); + struct arg_int *check_count = + arg_int0("c", "check-count", "", "Check that language file has this many entries, or bail."); struct arg_end *end = arg_end(20); - void *argtable[] = {help, vers, file, output, str, end}; + void *argtable[] = {help, vers, file, input, output, str, check_count, end}; const char *progname = "languagetool"; + bool language_is_utf8 = false; + sd_language language; + sd_language_create(&language); + // assume failure until success happens. + int main_ret = EXIT_FAILURE; + // Make sure everything got allocated if(arg_nullcheck(argtable) != 0) { - printf("%s: insufficient memory\n", progname); + fprintf(stderr, "%s: insufficient memory\n", progname); goto exit_0; } @@ -43,63 +298,135 @@ int main(int argc, char *argv[]) { // Handle version if(vers->count > 0) { - printf("%s v0.1\n", progname); + printf("%s v0.2\n", progname); printf("Command line One Must Fall 2097 Language file editor.\n"); printf("Source code is available at https://github.com/omf2097 under MIT license.\n"); - printf("(C) 2013 Tuomas Virtanen\n"); + printf("(C) 2013-2024 Tuomas Virtanen & Contributors\n"); goto exit_0; } // Handle errors if(nerrors > 0) { - arg_print_errors(stdout, end, progname); - printf("Try '%s --help' for more information.\n", progname); + arg_print_errors(stderr, end, progname); + fprintf(stderr, "Try '%s --help' for more information.\n", progname); goto exit_0; } // Get strings - sd_language language; - sd_language_create(&language); - int ret = sd_language_load(&language, file->filename[0]); - if(ret != SD_SUCCESS) { - printf("Language file could not be loaded! Error [%d] %s\n", ret, sd_get_error(ret)); + int ret; + + if(file->count > 0) { + ret = sd_language_load(&language, file->filename[0]); + language_is_utf8 = false; + if(ret != SD_SUCCESS) { + fprintf(stderr, "Language file could not be loaded! Error [%d] %s\n", ret, sd_get_error(ret)); + goto exit_0; + } + } else if(input->count > 0) { + char const *expected_ext = ".TXT"; + if(!input->extension[0] || strcmp(input->extension[0], expected_ext) != 0) { + fprintf(stderr, "Refusing to open input file %s, does not have expected %s file extension.\n", + input->filename[0], expected_ext); + goto exit_0; + } + // parse the supplied text file + FILE *file = fopen(input->filename[0], "rb"); + if(!file) { + fprintf(stderr, "Could not open %s\n", input->filename[0]); + goto exit_0; + } + // line is incremented prior to parsing each line + int line = 0; + language_is_utf8 = true; + while(read_entry(file, &language, &line)) { + } + } else { + fprintf(stderr, "Please supply -f or -i\n"); + goto exit_0; + } + + if(check_count->count > 0 && (unsigned)check_count->ival[0] != language.count) { + fprintf(stderr, "Expected %u entries, got %d!\n", (unsigned)check_count->ival[0], language.count); goto exit_0; } // Print const sd_lang_string *ds; if(str->count > 0) { + if(!language_is_utf8) { + conversion_result result = sd_language_to_utf8(&language); + if(result.error_code != CP437_SUCCESS) { + fprintf(stderr, "Error converting to UTF-8! Error %s on language entry %d\n", + cp437_result_to_string(result.error_code), result.string_index); + goto exit_0; + } + language_is_utf8 = true; + } unsigned str_id = (unsigned)str->ival[0]; ds = sd_language_get(&language, str_id); if(ds == NULL) { - printf("String %d not found!\n", str_id); - goto exit_1; + fprintf(stderr, "String %d not found!\n", str_id); + goto exit_0; } printf("Title: %s\n", ds->description); - printf("Data: %s\n", ds->data); - } else { + printf("Data: %s\n", ds->data); + } else if(output->count == 0) { + if(!language_is_utf8) { + conversion_result result = sd_language_to_utf8(&language); + if(result.error_code != CP437_SUCCESS) { + fprintf(stderr, "Error converting to UTF-8! Error %s on language entry %d\n", + cp437_result_to_string(result.error_code), result.string_index); + goto exit_0; + } + language_is_utf8 = true; + } for(unsigned i = 0; i < language.count; i++) { ds = sd_language_get(&language, i); if(ds != NULL) { - printf("ID: %d\n", i); + printf("ID: %d\n", i); printf("Title: %s\n", ds->description); - printf("Data: %s\n", ds->data); + printf("Data: %s\n", ds->data); } } } - // Saving + // Save if(output->count > 0) { + char const *expected_output_extensions[] = {".DAT", ".DAT2", ".LNG", ".LNG2"}; + bool unexpected_extension = true; + for(size_t i = 0; i < (sizeof expected_output_extensions) / (sizeof expected_output_extensions[0]); i++) { + if(output->extension[0] && strcmp(expected_output_extensions[i], output->extension[0]) == 0) { + unexpected_extension = false; + break; + } + } + if(unexpected_extension) { + fprintf(stderr, "Refusing to save language file to %s: unexpected file extension.\n", output->filename[0]); + goto exit_0; + } + + if(language_is_utf8) { + conversion_result result = sd_language_from_utf8(&language); + if(result.error_code != CP437_SUCCESS) { + fprintf(stderr, "Error converting from UTF-8! Error %s on language entry %d\n", + cp437_result_to_string(result.error_code), result.string_index); + goto exit_0; + } + language_is_utf8 = false; + assert(!language_is_utf8); // silence dead store warning + } + ret = sd_language_save(&language, output->filename[0]); if(ret != SD_SUCCESS) { - printf("Failed saving language file to %s: %s", output->filename[0], sd_get_error(ret)); + fprintf(stderr, "Failed saving language file to %s: %s\n", output->filename[0], sd_get_error(ret)); + goto exit_0; } } -exit_1: - sd_language_free(&language); + main_ret = EXIT_SUCCESS; exit_0: + sd_language_free(&language); arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); - return 0; + return main_ret; } diff --git a/tools/pcxtool/main.c b/tools/pcxtool/main.c index 76ffd5063..79025cf23 100644 --- a/tools/pcxtool/main.c +++ b/tools/pcxtool/main.c @@ -11,7 +11,13 @@ #include #include #include + +#if _WIN32 +#include +#include +#else #include +#endif #include "formats/vga_image.h" @@ -84,6 +90,14 @@ static void show_pcx(pcx_file *pcx) { } } +static int file_exists(char const *filename) { +#if _WIN32 + return _access(filename, 0) == 0; +#else + return access(filename, F_OK) == 0; +#endif +} + int main(int argc, char *argv[]) { struct arg_lit *help = arg_lit0("h", "help", "Print this help and exit"); struct arg_file *file = arg_file0("f", "file", "", "Input .PCX file"); @@ -110,7 +124,7 @@ int main(int argc, char *argv[]) { if(file->count == 0) { printf("The --file argument is required\n"); goto exit_0; - } else if(access(file->filename[0], F_OK) != 0) { + } else if(!file_exists(file->filename[0])) { printf("File %s cannot be accessed\n", file->filename[0]); goto exit_0; } diff --git a/vcpkg.json b/vcpkg.json index aff596c19..15a506266 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -23,6 +23,7 @@ "enet", "libconfuse", "argtable3", - "libpng" + "libpng", + "cunit" ] }