Add translations (#153)

* Add translations from v1

* Add font configuration (to be able to use non-latin languages)

* translations: remove includes that are already in pch.h

* translations: rename enums and avoid macros

* Fix crash when the font file doesn't exist

* translations: avoid u8 to avoid reencoding by MSVC

MSVC will read the file as ASCII and reconvert characters as UTF-8, this will corrupt characters as the file is in fact already in UTF-8.

* translations: remove NUMBER in enums

* translations: handle non existing translations gracefuly (don't crash)

Fallback to english if available, else return empty string

* Testing pull collaboration.

* Rollback: remove NUMBER in enums.

* Get rid of namespace, use header instead.

* Collapsed translated text struct and array.

* Fixed build errors and warnings.

* Simplified language list.

* All new types, locals and globals should use CamelCase.

* Removed unnecessary ImGui patch.

* Rearranged TTextBox immediate mode draw.

* Final touches: removed unused declaration in gdrv.
Removed unused Msg entries and added new check.

* Remove placeholder english texts from missing translations

Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
This commit is contained in:
Alexis Murzeau 2022-08-31 06:58:03 +02:00 committed by GitHub
parent c1c74878df
commit 66a868083a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 7957 additions and 461 deletions

View File

@ -38,6 +38,8 @@ set(SOURCE_FILES
SpaceCadetPinball/control.h SpaceCadetPinball/control.h
SpaceCadetPinball/EmbeddedData.cpp SpaceCadetPinball/EmbeddedData.cpp
SpaceCadetPinball/EmbeddedData.h SpaceCadetPinball/EmbeddedData.h
SpaceCadetPinball/font_selection.cpp
SpaceCadetPinball/font_selection.h
SpaceCadetPinball/fullscrn.cpp SpaceCadetPinball/fullscrn.cpp
SpaceCadetPinball/fullscrn.h SpaceCadetPinball/fullscrn.h
SpaceCadetPinball/gdrv.cpp SpaceCadetPinball/gdrv.cpp
@ -131,6 +133,8 @@ set(SOURCE_FILES
SpaceCadetPinball/TPopupTarget.h SpaceCadetPinball/TPopupTarget.h
SpaceCadetPinball/TRamp.cpp SpaceCadetPinball/TRamp.cpp
SpaceCadetPinball/TRamp.h SpaceCadetPinball/TRamp.h
SpaceCadetPinball/translations.cpp
SpaceCadetPinball/translations.h
SpaceCadetPinball/TRollover.cpp SpaceCadetPinball/TRollover.cpp
SpaceCadetPinball/TRollover.h SpaceCadetPinball/TRollover.h
SpaceCadetPinball/TSink.cpp SpaceCadetPinball/TSink.cpp

View File

@ -290,7 +290,7 @@ void TPinballTable::tilt(float time)
{ {
pinball::InfoTextBox->Clear(); pinball::InfoTextBox->Clear();
pinball::MissTextBox->Clear(); pinball::MissTextBox->Clear();
pinball::InfoTextBox->Display(pinball::get_rc_string(35, 0), -1.0); pinball::InfoTextBox->Display(pinball::get_rc_string(Msg::STRING136), -1.0);
loader::play_sound(SoundIndex3, nullptr, "TPinballTable1"); loader::play_sound(SoundIndex3, nullptr, "TPinballTable1");
TiltTimeoutTimer = timer::set(30.0, this, tilt_timeout); TiltTimeoutTimer = timer::set(30.0, this, tilt_timeout);
@ -315,7 +315,7 @@ void TPinballTable::port_draw()
int TPinballTable::Message(int code, float value) int TPinballTable::Message(int code, float value)
{ {
LPSTR rc_text; const char* rc_text;
switch (code) switch (code)
{ {
case 1000: case 1000:
@ -369,9 +369,9 @@ int TPinballTable::Message(int code, float value)
LightGroup->Message(20, 0.0); LightGroup->Message(20, 0.0);
Plunger->Message(1016, 0.0); Plunger->Message(1016, 0.0);
if (Demo && Demo->ActiveFlag) if (Demo && Demo->ActiveFlag)
rc_text = pinball::get_rc_string(30, 0); rc_text = pinball::get_rc_string(Msg::STRING131);
else else
rc_text = pinball::get_rc_string(26, 0); rc_text = pinball::get_rc_string(Msg::STRING127);
pinball::InfoTextBox->Display(rc_text, -1.0); pinball::InfoTextBox->Display(rc_text, -1.0);
if (Demo) if (Demo)
Demo->Message(1014, 0.0); Demo->Message(1014, 0.0);
@ -466,11 +466,11 @@ int TPinballTable::Message(int code, float value)
{ {
if (PlayerCount <= 1) if (PlayerCount <= 1)
{ {
char* textboxText; const char* textboxText;
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(30, 0); textboxText = pinball::get_rc_string(Msg::STRING131);
else else
textboxText = pinball::get_rc_string(26, 0); textboxText = pinball::get_rc_string(Msg::STRING127);
pinball::InfoTextBox->Display(textboxText, -1.0); pinball::InfoTextBox->Display(textboxText, -1.0);
break; break;
} }
@ -507,32 +507,32 @@ int TPinballTable::Message(int code, float value)
component->Message(1020, static_cast<float>(nextPlayer)); component->Message(1020, static_cast<float>(nextPlayer));
} }
char* textboxText = nullptr; const char* textboxText = nullptr;
switch (nextPlayer) switch (nextPlayer)
{ {
case 0: case 0:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(30, 0); textboxText = pinball::get_rc_string(Msg::STRING131);
else else
textboxText = pinball::get_rc_string(26, 0); textboxText = pinball::get_rc_string(Msg::STRING127);
break; break;
case 1: case 1:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(31, 0); textboxText = pinball::get_rc_string(Msg::STRING132);
else else
textboxText = pinball::get_rc_string(27, 0); textboxText = pinball::get_rc_string(Msg::STRING128);
break; break;
case 2: case 2:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(32, 0); textboxText = pinball::get_rc_string(Msg::STRING133);
else else
textboxText = pinball::get_rc_string(28, 0); textboxText = pinball::get_rc_string(Msg::STRING129);
break; break;
case 3: case 3:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(33, 0); textboxText = pinball::get_rc_string(Msg::STRING134);
else else
textboxText = pinball::get_rc_string(29, 0); textboxText = pinball::get_rc_string(Msg::STRING130);
break; break;
default: default:
break; break;
@ -549,7 +549,7 @@ int TPinballTable::Message(int code, float value)
case 1022: case 1022:
loader::play_sound(SoundIndex2, nullptr, "TPinballTable3"); loader::play_sound(SoundIndex2, nullptr, "TPinballTable3");
pinball::MissTextBox->Clear(); pinball::MissTextBox->Clear();
pinball::InfoTextBox->Display(pinball::get_rc_string(34, 0), -1.0); pinball::InfoTextBox->Display(pinball::get_rc_string(Msg::STRING135), -1.0);
EndGameTimeoutTimer = timer::set(3.0, this, EndGame_timeout); EndGameTimeoutTimer = timer::set(3.0, this, EndGame_timeout);
break; break;
case 1024: case 1024:
@ -661,7 +661,7 @@ void TPinballTable::EndGame_timeout(int timerId, void* caller)
if (table->Demo) if (table->Demo)
table->Demo->Message(1022, 0.0); table->Demo->Message(1022, 0.0);
control::handler(67, pinball::MissTextBox); control::handler(67, pinball::MissTextBox);
pinball::InfoTextBox->Display(pinball::get_rc_string(24, 0), -1.0); pinball::InfoTextBox->Display(pinball::get_rc_string(Msg::STRING125), -1.0);
} }
void TPinballTable::LightShow_timeout(int timerId, void* caller) void TPinballTable::LightShow_timeout(int timerId, void* caller)

View File

@ -145,6 +145,43 @@ void TTextBox::Display(const char* text, float time)
} }
} }
void TTextBox::DrawImGui()
{
// Do nothing when using a font (the text will be rendered to VScreen in TTextBox::Draw)
if (Font || !Message1)
return;
char windowName[64];
SDL_Rect rect;
ImGuiWindowFlags window_flags =
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoInputs;
rect.x = OffsetX;
rect.y = OffsetY;
rect.w = Width;
rect.h = Height;
rect = fullscrn::GetScreenRectFromPinballRect(rect);
ImGui::SetNextWindowPos(ImVec2(rect.x, rect.y));
ImGui::SetNextWindowSize(ImVec2(rect.w, rect.h));
// Use the pointer to generate a window unique name per text box
snprintf(windowName, sizeof(windowName), "TTextBox_%p", this);
if (ImGui::Begin(windowName, nullptr, window_flags))
{
ImGui::SetWindowFontScale(fullscrn::GetScreenToPinballRatio());
// ToDo: centered text in FT
ImGui::TextWrapped("%s", Message1->Text);
}
ImGui::End();
}
void TTextBox::Draw() void TTextBox::Draw()
{ {
auto bmp = BgBmp; auto bmp = BgBmp;
@ -189,13 +226,7 @@ void TTextBox::Draw()
{ {
if (!Font) if (!Font)
{ {
gdrv::grtext_draw_ttext_in_box( // Immediate mode drawing using system font is handled by TTextBox::DrawImGui
Message1->Text,
render::vscreen->XPosition + OffsetX,
render::vscreen->YPosition + OffsetY,
Width,
Height,
255);
return; return;
} }

View File

@ -22,6 +22,7 @@ public:
int Message(int code, float value) override; int Message(int code, float value) override;
void Clear(); void Clear();
void Display(const char* text, float time); void Display(const char* text, float time);
void DrawImGui();
private: private:
struct LayoutResult struct LayoutResult

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "translations.h"
class TLight; class TLight;
class TSound; class TSound;
class TPinballTable; class TPinballTable;
@ -66,7 +68,8 @@ public:
static component_tag_base* simple_components[145]; static component_tag_base* simple_components[145];
static int waiting_deployment_flag; static int waiting_deployment_flag;
static bool table_unlimited_balls; static bool table_unlimited_balls;
static int RankRcArray[9], MissionRcArray[17], mission_select_scores[17]; static Msg RankRcArray[9], MissionRcArray[17];
static int mission_select_scores[17];
static component_tag_base *wormhole_tag_array1[3], *wormhole_tag_array2[3], *wormhole_tag_array3[3]; static component_tag_base *wormhole_tag_array1[3], *wormhole_tag_array2[3], *wormhole_tag_array3[3];
static void make_links(TPinballTable* table); static void make_links(TPinballTable* table);

View File

@ -0,0 +1,50 @@
#include "pch.h"
#include "font_selection.h"
#include "options.h"
#include "pinball.h"
#include "score.h"
#include "winmain.h"
static const char* popupName = "Font Selection";
bool font_selection::ShowDialogFlag = false;
char font_selection::DialogInputBuffer[512];
void font_selection::ShowDialog()
{
ShowDialogFlag = true;
}
void font_selection::RenderDialog()
{
if (ShowDialogFlag == true)
{
strncpy(DialogInputBuffer, options::Options.FontFileName.c_str(), sizeof(DialogInputBuffer));
ShowDialogFlag = false;
if (!ImGui::IsPopupOpen(popupName))
{
ImGui::OpenPopup(popupName);
}
}
bool unused_open = true;
if (ImGui::BeginPopupModal(popupName, &unused_open, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Font file to use: ");
ImGui::SameLine();
ImGui::InputText("", DialogInputBuffer, IM_ARRAYSIZE(DialogInputBuffer));
if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Ok)))
{
options::Options.FontFileName = DialogInputBuffer;
ImGui::CloseCurrentPopup();
winmain::Restart();
}
ImGui::SameLine();
if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Cancel)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}

View File

@ -0,0 +1,11 @@
#pragma once
class font_selection
{
public:
static void ShowDialog();
static void RenderDialog();
private:
static bool ShowDialogFlag;
static char DialogInputBuffer[512];
};

View File

@ -140,3 +140,21 @@ void fullscrn::window_size_changed()
width - offset2X, height - offset2Y width - offset2X, height - offset2Y
}; };
} }
SDL_Rect fullscrn::GetScreenRectFromPinballRect(SDL_Rect rect)
{
SDL_Rect converted_rect;
converted_rect.x = rect.x * render::DestinationRect.w / render::vscreen->Width + render::DestinationRect.x;
converted_rect.y = rect.y * render::DestinationRect.h / render::vscreen->Height + render::DestinationRect.y;
converted_rect.w = rect.w * render::DestinationRect.w / render::vscreen->Width;
converted_rect.h = rect.h * render::DestinationRect.h / render::vscreen->Height;
return converted_rect;
}
float fullscrn::GetScreenToPinballRatio()
{
return (float) render::DestinationRect.w / render::vscreen->Width;
}

View File

@ -28,6 +28,8 @@ public:
static void SetResolution(int value); static void SetResolution(int value);
static int GetMaxResolution(); static int GetMaxResolution();
static void window_size_changed(); static void window_size_changed();
static SDL_Rect GetScreenRectFromPinballRect(SDL_Rect rect);
static float GetScreenToPinballRatio();
private : private :
static int resolution; static int resolution;

View File

@ -6,6 +6,9 @@
#include "pb.h" #include "pb.h"
#include "score.h" #include "score.h"
#include "winmain.h" #include "winmain.h"
#include "TTextBox.h"
#include "fullscrn.h"
#include "pinball.h"
ColorRgba gdrv::current_palette[256]{}; ColorRgba gdrv::current_palette[256]{};
@ -269,8 +272,15 @@ void gdrv::ScrollBitmapHorizontal(gdrv_bitmap8* bmp, int xStart)
} }
void gdrv::grtext_draw_ttext_in_box(LPCSTR text, int xOff, int yOff, int width, int height, int a6) void gdrv::grtext_draw_ttext_in_box()
{ {
for (const auto textBox : { pinball::InfoTextBox, pinball::MissTextBox })
{
if (textBox)
{
textBox->DrawImGui();
}
}
} }
void gdrv::ApplyPalette(gdrv_bitmap8& bmp) void gdrv::ApplyPalette(gdrv_bitmap8& bmp)

View File

@ -80,7 +80,7 @@ public:
static void copy_bitmap_w_transparency(gdrv_bitmap8* dstBmp, int width, int height, int xOff, int yOff, static void copy_bitmap_w_transparency(gdrv_bitmap8* dstBmp, int width, int height, int xOff, int yOff,
gdrv_bitmap8* srcBmp, int srcXOff, int srcYOff); gdrv_bitmap8* srcBmp, int srcXOff, int srcYOff);
static void ScrollBitmapHorizontal(gdrv_bitmap8* bmp, int xStart); static void ScrollBitmapHorizontal(gdrv_bitmap8* bmp, int xStart);
static void grtext_draw_ttext_in_box(LPCSTR text, int xOff, int yOff, int width, int height, int a6); static void grtext_draw_ttext_in_box();
static void ApplyPalette(gdrv_bitmap8& bmp); static void ApplyPalette(gdrv_bitmap8& bmp);
static void CreatePreview(gdrv_bitmap8& bmp); static void CreatePreview(gdrv_bitmap8& bmp);
private: private:

View File

@ -121,7 +121,7 @@ void high_score::RenderHighScoreDialog()
if (ShowDialog == true) if (ShowDialog == true)
{ {
ShowDialog = false; ShowDialog = false;
if (!ImGui::IsPopupOpen("High Scores")) if (!ImGui::IsPopupOpen(pinball::get_rc_string(Msg::HIGHSCORES_Caption)))
{ {
dlg_enter_name = false; dlg_enter_name = false;
while (!ScoreQueue.empty()) while (!ScoreQueue.empty())
@ -140,19 +140,19 @@ void high_score::RenderHighScoreDialog()
} }
} }
ImGui::OpenPopup("High Scores"); ImGui::OpenPopup(pinball::get_rc_string(Msg::HIGHSCORES_Caption));
} }
} }
bool unused_open = true; bool unused_open = true;
if (ImGui::BeginPopupModal("High Scores", &unused_open, ImGuiWindowFlags_AlwaysAutoResize)) if (ImGui::BeginPopupModal(pinball::get_rc_string(Msg::HIGHSCORES_Caption), &unused_open, ImGuiWindowFlags_AlwaysAutoResize))
{ {
if (ImGui::BeginTable("table1", 3, ImGuiTableFlags_Borders)) if (ImGui::BeginTable("table1", 3, ImGuiTableFlags_Borders))
{ {
char buf[36]; char buf[36];
ImGui::TableSetupColumn("Rank"); ImGui::TableSetupColumn(pinball::get_rc_string(Msg::HIGHSCORES_Rank));
ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn(pinball::get_rc_string(Msg::HIGHSCORES_Name));
ImGui::TableSetupColumn("Score"); ImGui::TableSetupColumn(pinball::get_rc_string(Msg::HIGHSCORES_Score));
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (int offset = 0, row = 0; row < 5; row++) for (int offset = 0, row = 0; row < 5; row++)
@ -184,7 +184,7 @@ void high_score::RenderHighScoreDialog()
ImGui::EndTable(); ImGui::EndTable();
} }
if (ImGui::Button("Ok")) if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Ok)))
{ {
if (dlg_enter_name) if (dlg_enter_name)
{ {
@ -194,23 +194,23 @@ void high_score::RenderHighScoreDialog()
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel")) if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Cancel)))
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Clear")) if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Clear)))
ImGui::OpenPopup("Confirm"); ImGui::OpenPopup("Confirm");
if (ImGui::BeginPopupModal("Confirm", nullptr, ImGuiWindowFlags_MenuBar)) if (ImGui::BeginPopupModal("Confirm", nullptr, ImGuiWindowFlags_MenuBar))
{ {
ImGui::TextUnformatted(pinball::get_rc_string(40, 0)); ImGui::TextUnformatted(pinball::get_rc_string(Msg::STRING141));
if (ImGui::Button("OK", ImVec2(120, 0))) if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Ok), ImVec2(120, 0)))
{ {
clear_table(); clear_table();
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) if (ImGui::Button(pinball::get_rc_string(Msg::HIGHSCORES_Cancel), ImVec2(120, 0)))
{ {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
@ -220,7 +220,7 @@ void high_score::RenderHighScoreDialog()
ImGui::EndPopup(); ImGui::EndPopup();
// Reenter dialog for the next score in the queue // Reenter dialog for the next score in the queue
if (!ImGui::IsPopupOpen("High Scores") && !ScoreQueue.empty()) if (!ImGui::IsPopupOpen(pinball::get_rc_string(Msg::HIGHSCORES_Caption)) && !ScoreQueue.empty())
{ {
ShowDialog = true; ShowDialog = true;
} }

View File

@ -3,6 +3,7 @@
#include "fullscrn.h" #include "fullscrn.h"
#include "midi.h" #include "midi.h"
#include "pinball.h"
#include "render.h" #include "render.h"
#include "Sound.h" #include "Sound.h"
#include "winmain.h" #include "winmain.h"
@ -18,12 +19,12 @@ bool options::ShowDialog = false;
GameInput* options::ControlWaitingForInput = nullptr; GameInput* options::ControlWaitingForInput = nullptr;
const ControlRef options::Controls[6] const ControlRef options::Controls[6]
{ {
{"Left Flipper", RebindControls.LeftFlipper}, {Msg::KEYMAPPER_FlipperL, RebindControls.LeftFlipper},
{"Right Flipper", RebindControls.RightFlipper}, {Msg::KEYMAPPER_FlipperR, RebindControls.RightFlipper},
{"Left Table Bump", RebindControls.LeftTableBump}, {Msg::KEYMAPPER_BumpLeft, RebindControls.LeftTableBump},
{"Right Table Bump", RebindControls.RightTableBump}, {Msg::KEYMAPPER_BumpRight, RebindControls.RightTableBump},
{"Bottom Table Bump", RebindControls.BottomTableBump}, {Msg::KEYMAPPER_BumpBottom, RebindControls.BottomTableBump},
{"Plunger", RebindControls.Plunger}, {Msg::KEYMAPPER_Plunger, RebindControls.Plunger},
}; };
@ -113,6 +114,8 @@ void options::InitPrimary()
Options.DebugOverlayCollisionMask = get_int("Debug Overlay Collision Mask", true); Options.DebugOverlayCollisionMask = get_int("Debug Overlay Collision Mask", true);
Options.DebugOverlaySprites = get_int("Debug Overlay Sprites", true); Options.DebugOverlaySprites = get_int("Debug Overlay Sprites", true);
Options.DebugOverlaySounds = get_int("Debug Overlay Sounds", true); Options.DebugOverlaySounds = get_int("Debug Overlay Sounds", true);
translations::SetCurrentLanguage(get_string("Language", translations::GetCurrentLanguage()->ShortName).c_str());
Options.FontFileName = get_string("FontFileName", "");
} }
void options::InitSecondary() void options::InitSecondary()
@ -160,7 +163,9 @@ void options::uninit()
set_int("Debug Overlay Ball Edges", Options.DebugOverlayBallEdges); set_int("Debug Overlay Ball Edges", Options.DebugOverlayBallEdges);
set_int("Debug Overlay Collision Mask", Options.DebugOverlayCollisionMask); set_int("Debug Overlay Collision Mask", Options.DebugOverlayCollisionMask);
set_int("Debug Overlay Sprites", Options.DebugOverlaySprites); set_int("Debug Overlay Sprites", Options.DebugOverlaySprites);
get_int("Debug Overlay Sounds", Options.DebugOverlaySounds); set_int("Debug Overlay Sounds", Options.DebugOverlaySounds);
set_string("Language", translations::GetCurrentLanguage()->ShortName);
set_string("FontFileName", Options.FontFileName.c_str());
} }
@ -339,18 +344,16 @@ void options::RenderControlDialog()
return; return;
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{550, 450}); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{550, 450});
if (ImGui::Begin("3D Pinball: Player Controls", &ShowDialog)) if (ImGui::Begin(pinball::get_rc_string(Msg::KEYMAPPER_Caption), &ShowDialog))
{ {
ImGui::TextUnformatted("Instructions"); ImGui::TextUnformatted(pinball::get_rc_string(Msg::KEYMAPPER_Groupbox2));
ImGui::Separator(); ImGui::Separator();
ImGui::TextWrapped( ImGui::TextWrapped("%s", pinball::get_rc_string(Msg::KEYMAPPER_Help1));
"To change game controls, click the control button, press the new key, and then choose OK."); ImGui::TextWrapped("%s", pinball::get_rc_string(Msg::KEYMAPPER_Help2));
ImGui::TextWrapped(
"To restore 3D Pinball to its original settings, choose Default, and then choose OK.");
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextUnformatted("Control Options"); ImGui::TextUnformatted(pinball::get_rc_string(Msg::KEYMAPPER_Groupbox1));
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{5, 10}); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{5, 10});
if (ImGui::BeginTable("Controls", 4, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) if (ImGui::BeginTable("Controls", 4, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))
@ -366,7 +369,7 @@ void options::RenderControlDialog()
{ {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5, 0, 0, 1}); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5, 0, 0, 1});
if (ImGui::Button(row.Name)) if (ImGui::Button(pinball::get_rc_string(row.NameStringId)))
{ {
for (auto i = 0u; i <= 2; i++) for (auto i = 0u; i <= 2; i++)
row.Option[i] = {}; row.Option[i] = {};
@ -419,20 +422,20 @@ void options::RenderControlDialog()
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ImGui::Spacing(); ImGui::Spacing();
if (ImGui::Button("OK")) if (ImGui::Button(pinball::get_rc_string(Msg::KEYMAPPER_Ok)))
{ {
Options.Key = RebindControls; Options.Key = RebindControls;
ShowDialog = false; ShowDialog = false;
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel")) if (ImGui::Button(pinball::get_rc_string(Msg::KEYMAPPER_Cancel)))
{ {
ShowDialog = false; ShowDialog = false;
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Default")) if (ImGui::Button(pinball::get_rc_string(Msg::KEYMAPPER_Default)))
{ {
RebindControls = Options.KeyDft; RebindControls = Options.KeyDft;
ControlWaitingForInput = nullptr; ControlWaitingForInput = nullptr;

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "translations.h"
enum class Menu1:int enum class Menu1:int
{ {
New_Game = 101, New_Game = 101,
@ -90,11 +92,12 @@ struct optionsStruct
bool DebugOverlayCollisionMask; bool DebugOverlayCollisionMask;
bool DebugOverlaySprites; bool DebugOverlaySprites;
bool DebugOverlaySounds; bool DebugOverlaySounds;
std::string FontFileName;
}; };
struct ControlRef struct ControlRef
{ {
const char* Name; Msg NameStringId;
GameInput (&Option)[3]; GameInput (&Option)[3];
}; };

View File

@ -47,7 +47,7 @@ int pb::init()
record_table = partman::load_records(dataFilePath.c_str(), FullTiltMode); record_table = partman::load_records(dataFilePath.c_str(), FullTiltMode);
auto useBmpFont = 0; auto useBmpFont = 0;
pinball::get_rc_int(158, &useBmpFont); pinball::get_rc_int(Msg::STRING258, &useBmpFont);
if (useBmpFont) if (useBmpFont)
score::load_msg_font("pbmsg_ft"); score::load_msg_font("pbmsg_ft");
@ -124,7 +124,7 @@ void pb::SelectDatFile(const std::vector<const char*>& dataSearchPaths)
std::string datFileNames[3] std::string datFileNames[3]
{ {
"CADET.DAT", "CADET.DAT",
options::get_string("Pinball Data", pinball::get_rc_string(168, 0)), options::get_string("Pinball Data", pinball::get_rc_string(Msg::STRING268)),
"DEMO.DAT", "DEMO.DAT",
}; };
@ -219,7 +219,7 @@ void pb::toggle_demo()
MainTable->Message(1024, 0.0); MainTable->Message(1024, 0.0);
mode_change(GameModes::GameOver); mode_change(GameModes::GameOver);
pinball::MissTextBox->Clear(); pinball::MissTextBox->Clear();
auto text = pinball::get_rc_string(24, 0); auto text = pinball::get_rc_string(Msg::STRING125);
pinball::InfoTextBox->Display(text, -1.0); pinball::InfoTextBox->Display(text, -1.0);
} }
else else
@ -288,7 +288,7 @@ void pb::frame(float dtMilliSec)
{ {
if (nudge::nudge_count > 0.5f) if (nudge::nudge_count > 0.5f)
{ {
pinball::InfoTextBox->Display(pinball::get_rc_string(25, 0), 2.0); pinball::InfoTextBox->Display(pinball::get_rc_string(Msg::STRING126), 2.0);
} }
if (nudge::nudge_count > 1.0f) if (nudge::nudge_count > 1.0f)
MainTable->tilt(time_now); MainTable->tilt(time_now);
@ -366,7 +366,7 @@ void pb::pause_continue()
{ {
if (MainTable) if (MainTable)
MainTable->Message(1008, time_now); MainTable->Message(1008, time_now);
pinball::InfoTextBox->Display(pinball::get_rc_string(22, 0), -1.0); pinball::InfoTextBox->Display(pinball::get_rc_string(Msg::STRING123), -1.0);
midi::music_stop(); midi::music_stop();
Sound::Deactivate(); Sound::Deactivate();
} }
@ -376,17 +376,17 @@ void pb::pause_continue()
MainTable->Message(1009, 0.0); MainTable->Message(1009, 0.0);
if (!demo_mode) if (!demo_mode)
{ {
char* text; const char* text;
float textTime; float textTime;
if (game_mode == GameModes::GameOver) if (game_mode == GameModes::GameOver)
{ {
textTime = -1.0; textTime = -1.0;
text = pinball::get_rc_string(24, 0); text = pinball::get_rc_string(Msg::STRING125);
} }
else else
{ {
textTime = 5.0; textTime = 5.0;
text = pinball::get_rc_string(23, 0); text = pinball::get_rc_string(Msg::STRING124);
} }
pinball::InfoTextBox->Display(text, textTime); pinball::InfoTextBox->Display(text, textTime);
} }
@ -481,7 +481,7 @@ void pb::InputDown(GameInput input)
case 'h': case 'h':
{ {
high_score_struct entry{ {0}, 1000000000 }; high_score_struct entry{ {0}, 1000000000 };
strncpy(entry.Name, pinball::get_rc_string(26, 0), sizeof entry.Name - 1); strncpy(entry.Name, pinball::get_rc_string(Msg::STRING127), sizeof entry.Name - 1);
high_score::show_and_set_high_score_dialog({ entry, 1 }); high_score::show_and_set_high_score_dialog({ entry, 1 });
break; break;
} }
@ -550,7 +550,16 @@ void pb::end_game()
if (position >= 0) if (position >= 0)
{ {
high_score_struct entry{ {0}, scores[i] }; high_score_struct entry{ {0}, scores[i] };
strncpy(entry.Name, pinball::get_rc_string(scoreIndex[i] + 26, 0), sizeof entry.Name - 1); const char* playerName = "Player";
switch(scoreIndex[i]) {
case 0: playerName = pinball::get_rc_string(Msg::STRING127); break;
case 1: playerName = pinball::get_rc_string(Msg::STRING128); break;
case 2: playerName = pinball::get_rc_string(Msg::STRING129); break;
case 3: playerName = pinball::get_rc_string(Msg::STRING130); break;
}
strncpy(entry.Name, playerName, sizeof entry.Name - 1);
high_score::show_and_set_high_score_dialog({ entry, -1 }); high_score::show_and_set_high_score_dialog({ entry, -1 });
} }
} }

View File

@ -29,6 +29,7 @@
#include <string> #include <string>
#include <thread> #include <thread>
#include <map> #include <map>
#include <initializer_list>
//#include <array> //#include <array>
#define SDL_MAIN_HANDLED #define SDL_MAIN_HANDLED

View File

@ -1,219 +1,13 @@
#include "pch.h" #include "pch.h"
#include "pinball.h" #include "pinball.h"
#include "translations.h"
#include "winmain.h" #include "winmain.h"
// Todo: load translations from file int LoadStringAlt(Msg uID, LPSTR lpBuffer, int cchBufferMax)
std::map<uint32_t, LPCSTR> rc_strings
{ {
{0, "Replay Awarded"}, const char* text = translations::GetTranslation(uID);
{1, "Ball Locked"},
{2, "Center Post\n%ld"},
{3, "Bonus Awarded\n%ld"},
{4, "Bonus Activated"},
{5, "Weapons Upgraded"},
{6, "Engine Upgraded"},
{7, "Bonus up 1 Million"},
{8, "Extra Ball Available\n%ld"},
{9, "Extra Ball"},
{10, "Reflex Shot Award\n%ld"},
{11, "Final Battle Won"},
{12, "Hyperspace Bonus\n%ld"},
{13, "Hyperspace Bonus Available"},
{14, "Jackpot Awarded\n%ld"},
{15, "Jackpot Activated"},
{16, "Multiball"},
{17, "Ramp Bonus Awarded"},
{18, "Light Added"},
{19, "Ramp Bonus On"},
{20, "Light Reset Off"},
{21, "Skill Shot\n%ld"},
{22, "Game Paused\nF3 to Resume"},
{23, "Continue Play"},
{24, "F2 Starts New Game"},
{25, "Careful..."},
{26, "Player 1"},
{27, "Player 2"},
{28, "Player 3"},
{29, "Player 4"},
{30, "Demo\nPlayer 1"},
{31, "Demo\nPlayer 2"},
{32, "Demo\nPlayer 3"},
{33, "Demo\nPlayer 4"},
{34, "Game Over"},
{35, "TILT!"},
{36, "This program requires an 80386 or later CPU."},
{37, "80386 Required"},
{38, "3D Pinball for Windows - Space Cadet"},
{
39,
"One or more of the player controls is set to the same key,\nfor best performance use unique keys for each control."
},
{40, "Clear High Scores?"},
{41, "Confirm"},
{42, "WAVEMIX.INF is missing - it must be in the pinball directory!"},
{43, "Warning:"},
{44, "Ship Re-Fueled"},
{45, "Gravity Well"},
{46, "Time Warp Forward"},
{47, "Time Warp Backward"},
{48, "Maelstrom!"},
{49, "Wormhole"},
{50, "Awaiting Deployment"},
{51, "Flags Upgraded"},
{52, "Bonus Hold"},
{53, "Level One Commendation"},
{54, "Level Two Commendation"},
{55, "Level Three Commendation"},
{56, "Field Multiplier 2x"},
{57, "Field Multiplier 3x"},
{58, "Field Multiplier 5x"},
{59, "Field Multiplier 10x"},
{60, "Target Practice"},
{61, "Launch Training"},
{62, "Re-Entry Training"},
{63, "Science"},
{64, "Stray Comet"},
{65, "Black Hole"},
{66, "Space Radiation"},
{67, "Bug Hunt"},
{68, "Alien Menace"},
{69, "Rescue"},
{70, "Satellite Retrieval"},
{71, "Recon"},
{72, "Doomsday Machine"},
{73, "Cosmic Plague"},
{74, "Secret"},
{75, "Time Warp"},
{76, "Maelstrom"},
{77, "Mission Accepted\n%ld"},
{78, "Mission Completed\n%ld"},
{79, "%s Mission Selected"},
{80, "Black Hole\n%ld"},
{81, "Gravity Normalized\n%ld"},
{82, "Gravity Well\n%ld"},
{83, "Promotion to %s"},
{84, "Cadet"},
{85, "Ensign"},
{86, "Lieutenant"},
{87, "Captain"},
{88, "Lt Commander"},
{89, "Commander"},
{90, "Commodore"},
{91, "Admiral"},
{92, "Fleet Admiral"},
{93, "Wormhole Opened"},
{94, "Crash Bonus\n%ld"},
{95, "Replay Ball"},
{96, "Re-Deploy"},
{97, "Player 1 Shoot Again"},
{98, "Player 2 Shoot Again"},
{99, "Player 3 Shoot Again"},
{100, "Player 4 Shoot Again"},
{
101,
"This 3D Pinball Table was created for Microsoft by Maxis.\nFor more information call (800)-336-2947\n(US and Canadian customers only).\nCopyright (c) 1995 Maxis."
},
{102, "3D Pinball Table created for Microsoft by Maxis. Copyright (c) 1995 Maxis."},
{103, "About 3D Pinball"},
{104, "Hit Mission Targets To Select Mission"},
{105, "Re-Fuel Ship"},
{106, "Launch Ramp To Accept %s Mission"},
{107, "Attack Bumpers Hits\nLeft: %d"},
{108, "Target Training Passed"},
{109, "Mission Aborted"},
{110, "Launches Left: %d"},
{111, "Launch Training Passed"},
{112, "Re-Entries Left: %d"},
{113, "Re-Entry Training Passed"},
{114, "Drop Targets\nLeft: %d"},
{115, "Science Mission Completed"},
{116, "Warning -- Low Fuel"},
{117, "Fill Right Hazard Banks"},
{118, "Hyperspace Launch"},
{119, "Comet Destroyed"},
{120, "Enter Wormhole"},
{121, "Radiation Eliminated"},
{122, "Upgrade Launch Bumpers"},
{123, "Enter Black Hole"},
{124, "Black Hole Eliminated"},
{125, "Targets\nLeft: %d"},
{126, "Xenomorphs Destroyed"},
{127, "Upgrade Flags"},
{128, "Hyperspace Launch"},
{129, "Survivors Rescued"},
{130, "Aliens Repelled"},
{131, "Hit Fuel Targets"},
{132, "Remote Attack Bumper Hits\nLeft: %d"},
{133, "Satellite Repaired"},
{134, "Lane Passes\nLeft: %d"},
{135, "Shoot Ball Up Fuel Chute"},
{136, "Survey Complete"},
{137, "Out Lane Passes\nLeft: %d"},
{138, "Doomsday Machine Destroyed"},
{139, "Roll Flags: %d"},
{140, "Hit Space Warp Rollover"},
{141, "Plague Eliminated"},
{142, "Hit Yellow Wormhole"},
{143, "Hit Red Wormhole"},
{144, "Hit Green Wormhole"},
{145, "Plans Recovered"},
{146, "Rebound Hits\nLeft: %d"},
{147, "Hit Hyperspace Chute or Launch Ramp"},
{148, "Drop Target Hits\nLeft: %d"},
{149, "Spot Target Hits\nLeft: %d"},
{150, "Lanes Passes\nLeft: %d"},
{151, "Shoot Ball Up Fuel Chute"},
{152, "Hit Launch Ramp"},
{153, "Hit Flags"},
{154, "Hit Worm Hole"},
{155, "Hyperspace Chute to Maelstrom"},
{156, "pinball.mid"},
{158, "1 UseBitmapFont"},
{159, "90 Left Flipper Key"},
{160, "191 Right Flipper Key"},
{161, "32 Plunger Key"},
{162, "88 Bump Left Key"},
{163, "190 Bump Right Key"},
{164, "38 Bump Bottom Key"},
{165, "Software\\Microsoft\\Plus!\\Pinball"},
{166, "SpaceCadet"},
{167, "1c7c22a0-9576-11ce-bf80-444553540000"},
{168, "PINBALL.DAT"},
{169, "Space Cadet"},
{170, "Error:"},
{171, "Unable to find other tables."},
{172, "3D Pinball\nSpace Cadet"},
{173, "Promotion to %s"},
{174, "Demotion to %s"},
{175, "Upgrade Attack Bumpers"},
{176, "Fill Left Hazard Banks"},
{177, "HIGH SCORE"},
{178, "pinball.chm"},
{179, "Not enough memory to run 3D Pinball."},
{180, "Player 1's Score\n%ld"},
{181, "Player 2's Score\n%ld"},
{182, "Player 3's Score\n%ld"},
{183, "Player 4's Score\n%ld"},
{184, "High Score 1\n%ld"},
{185, "High Score 2\n%ld"},
{186, "High Score 3\n%ld"},
{187, "High Score 4\n%ld"},
{188, "High Score 5\n%ld"},
{189, "255 255 255 (R G B default font color)"},
{2030, "Use Maximum Resolution (640 x 480)"},
{2031, "Use Maximum Resolution (800 x 600)"},
{2032, "Use Maximum Resolution (1024 x 768)"}
};
int LoadStringAlt(uint32_t uID, LPSTR lpBuffer, int cchBufferMax) strncpy(lpBuffer, text, cchBufferMax);
{
auto str = rc_strings.find(uID);
if (str == rc_strings.end())
{
return 0;
}
strncpy(lpBuffer, str->second, cchBufferMax);
return 1; return 1;
} }
@ -226,17 +20,18 @@ int pinball::LeftShift = -1;
int pinball::RightShift = -1; int pinball::RightShift = -1;
std::string pinball::BasePath; std::string pinball::BasePath;
char* pinball::get_rc_string(int uID, int a2) char* pinball::get_rc_string(Msg uID)
{ {
char* result = &getRcBuffer[256 * rc_string_slot]; char* result = &getRcBuffer[256 * rc_string_slot];
if (!LoadStringAlt(uID, &getRcBuffer[256 * rc_string_slot], 255)) if (!LoadStringAlt(uID, &getRcBuffer[256 * rc_string_slot], 255))
*result = 0; *result = 0;
if (++rc_string_slot >= 6) if (++rc_string_slot >= 6)
rc_string_slot = 0; rc_string_slot = 0;
return result; return result;
} }
int pinball::get_rc_int(int uID, int* dst) int pinball::get_rc_int(Msg uID, int* dst)
{ {
char buffer[255]; char buffer[255];
int result = LoadStringAlt(uID, buffer, 255); int result = LoadStringAlt(uID, buffer, 255);

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "translations.h"
class TTextBox; class TTextBox;
class pinball class pinball
@ -12,8 +14,8 @@ public:
static int LeftShift; static int LeftShift;
static std::string BasePath; static std::string BasePath;
static char* get_rc_string(int uID, int a2); static char* get_rc_string(Msg uID);
static int get_rc_int(int uID, int* dst); static int get_rc_int(Msg uID, int* dst);
static std::string make_path_name(const std::string& fileName); static std::string make_path_name(const std::string& fileName);
private: private:
static char getRcBuffer[256 * 6]; static char getRcBuffer[256 * 6];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,361 @@
#pragma once
enum class Msg : int
{
Min = 0,
STRING101 = 0,
STRING102,
STRING103,
STRING104,
STRING105,
STRING106,
STRING107,
STRING108,
STRING109,
STRING110,
STRING111,
STRING112,
STRING113,
STRING114,
STRING115,
STRING116,
STRING117,
STRING118,
STRING119,
STRING120,
STRING121,
STRING122,
STRING123,
STRING124,
STRING125,
STRING126,
STRING127,
STRING128,
STRING129,
STRING130,
STRING131,
STRING132,
STRING133,
STRING134,
STRING135,
STRING136,
STRING137,
STRING138,
STRING139,
STRING140,
STRING141,
STRING142,
STRING143,
STRING144,
STRING145,
STRING146,
STRING147,
STRING148,
STRING149,
STRING150,
STRING151,
STRING152,
STRING153,
STRING154,
STRING155,
STRING156,
STRING157,
STRING158,
STRING159,
STRING160,
STRING161,
STRING162,
STRING163,
STRING164,
STRING165,
STRING166,
STRING167,
STRING168,
STRING169,
STRING170,
STRING171,
STRING172,
STRING173,
STRING174,
STRING175,
STRING176,
STRING177,
STRING178,
STRING179,
STRING180,
STRING181,
STRING182,
STRING183,
STRING184,
STRING185,
STRING186,
STRING187,
STRING188,
STRING189,
STRING190,
STRING191,
STRING192,
STRING193,
STRING194,
STRING195,
STRING196,
STRING197,
STRING198,
STRING199,
STRING200,
STRING201,
STRING202,
STRING203,
STRING204,
STRING205,
STRING206,
STRING207,
STRING208,
STRING209,
STRING210,
STRING211,
STRING212,
STRING213,
STRING214,
STRING215,
STRING216,
STRING217,
STRING218,
STRING219,
STRING220,
STRING221,
STRING222,
STRING223,
STRING224,
STRING225,
STRING226,
STRING227,
STRING228,
STRING229,
STRING230,
STRING231,
STRING232,
STRING233,
STRING234,
STRING235,
STRING236,
STRING237,
STRING238,
STRING239,
STRING240,
STRING241,
STRING242,
STRING243,
STRING244,
STRING245,
STRING246,
STRING247,
STRING248,
STRING249,
STRING250,
STRING251,
STRING252,
STRING253,
STRING254,
STRING255,
STRING256,
STRING257,
STRING258,
STRING259,
STRING260,
STRING261,
STRING262,
STRING263,
STRING264,
STRING265,
STRING266,
STRING267,
STRING268,
STRING269,
STRING270,
STRING271,
STRING272,
STRING273,
STRING274,
STRING275,
STRING276,
STRING277,
STRING278,
STRING279,
STRING280,
STRING281,
STRING282,
STRING283,
STRING284,
STRING285,
STRING286,
STRING287,
STRING288,
STRING289,
HIGHSCORES_Caption,
HIGHSCORES_Ok,
HIGHSCORES_Cancel,
HIGHSCORES_Clear,
HIGHSCORES_Name,
HIGHSCORES_Score,
HIGHSCORES_Rank,
HIGHSCORES_Rank1,
HIGHSCORES_Rank2,
HIGHSCORES_Rank3,
HIGHSCORES_Rank4,
HIGHSCORES_Rank5,
KEYMAPPER_Caption,
KEYMAPPER_Ok,
KEYMAPPER_Cancel,
KEYMAPPER_FlipperL,
KEYMAPPER_FlipperR,
KEYMAPPER_Plunger,
KEYMAPPER_BumpLeft,
KEYMAPPER_BumpRight,
KEYMAPPER_BumpBottom,
KEYMAPPER_Default,
KEYMAPPER_Help1,
KEYMAPPER_Help2,
KEYMAPPER_Groupbox1,
KEYMAPPER_Groupbox2,
Menu1_New_Game,
Menu1_About_Pinball,
Menu1_High_Scores,
Menu1_Exit,
Menu1_Sounds,
Menu1_Music,
Menu1_Help_Topics,
Menu1_Launch_Ball,
Menu1_Pause_Resume_Game,
Menu1_Full_Screen,
Menu1_Demo,
Menu1_Select_Table,
Menu1_Player_Controls,
Menu1_1Player,
Menu1_2Players,
Menu1_3Players,
Menu1_4Players,
Menu1_MaximumResolution,
Menu1_640x480,
Menu1_800x600,
Menu1_1024x768,
Menu1_WindowUniformScale,
Menu1_AlternativeRender,
Menu1_Game,
Menu1_Options,
Menu1_Select_Players,
Menu1_Table_Resolution,
Menu1_Window,
Menu1_Help,
Menu1_UseMaxResolution_640x480,
Menu1_UseMaxResolution_800x600,
Menu1_UseMaxResolution_1024x768,
Max,
};
enum class Lang : int
{
Min = 0,
Arabic = 0,
Czech,
Danish,
German,
Greek,
English,
Spanish,
Finnish,
French,
Hebrew,
Hungarian,
Italian,
Japanese,
Korean,
Norwegian,
Dutch,
Polish,
BrazilianPortuguese,
Portuguese,
Russian,
Swedish,
Turkish,
SimplifiedChinese,
TraditionalChinese,
Max
};
struct TextArray
{
TextArray(const std::initializer_list<std::pair<Msg, std::initializer_list<std::pair<Lang, LPCSTR>>>>& iList)
{
for (const auto& msgPair : iList)
{
for (const auto& languagePair : msgPair.second)
{
assertm(!contains(msgPair.first, languagePair.first), "Key redefinition");
Set(msgPair.first, languagePair.first, languagePair.second);
}
}
for (auto i = Msg::Min; i < Msg::Max; i = static_cast<Msg>(static_cast<int>(i) + 1))
{
assertm(contains(i, Lang::English), "English text is mandatory for all keys");
}
}
LPCSTR Get(Msg msgId, Lang langId) const
{
assertm(TextArray::contains(msgId), "Message Id out of bounds");
assertm(TextArray::contains(langId), "Language Id out of bounds");
return Store[static_cast<int>(msgId)][static_cast<int>(langId)];
}
bool contains(Msg msgId, Lang langId) const
{
return contains(msgId) && Get(msgId, langId) != nullptr;
}
static bool contains(Msg msgId)
{
return msgId >= Msg::Min && msgId < Msg::Max;
}
static bool contains(Lang langId)
{
return langId >= Lang::Min && langId < Lang::Max;
}
private:
LPCSTR Store[static_cast<int>(Msg::Max )][static_cast<int>(Lang::Max)]{ nullptr };
void Set(Msg msgId, Lang langId, LPCSTR value)
{
assertm(TextArray::contains(msgId), "Message Id out of bounds");
assertm(TextArray::contains(langId), "Language Id out of bounds");
Store[static_cast<int>(msgId)][static_cast<int>(langId)] = value;
}
};
struct LanguageInfo
{
const Lang Language;
const char* ShortName;
const char* DisplayName;
};
class translations
{
public:
static const LanguageInfo Languages[static_cast<int>(Lang::Max)];
static const char* GetTranslation(Msg id);
static void SetCurrentLanguage(const char* short_name);
static const LanguageInfo* GetCurrentLanguage();
static void GetGlyphRange(ImVector<ImWchar>* ranges);
private:
static const TextArray Translations;
static Lang CurrentLanguage;
};

View File

@ -9,6 +9,8 @@
#include "pb.h" #include "pb.h"
#include "render.h" #include "render.h"
#include "Sound.h" #include "Sound.h"
#include "translations.h"
#include "font_selection.h"
SDL_Window* winmain::MainWindow = nullptr; SDL_Window* winmain::MainWindow = nullptr;
SDL_Renderer* winmain::Renderer = nullptr; SDL_Renderer* winmain::Renderer = nullptr;
@ -64,7 +66,7 @@ int winmain::WinMain(LPCSTR lpCmdLine)
// SDL window // SDL window
SDL_Window* window = SDL_CreateWindow SDL_Window* window = SDL_CreateWindow
( (
pinball::get_rc_string(38, 0), pinball::get_rc_string(Msg::STRING139),
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 556, 800, 556,
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE
@ -106,9 +108,6 @@ int winmain::WinMain(LPCSTR lpCmdLine)
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
ImIO = &io; ImIO = &io;
// ImGui_ImplSDL2_Init is private, we are not actually using ImGui OpenGl backend
ImGui_ImplSDL2_InitForOpenGL(window, nullptr);
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
auto prefPath = SDL_GetPrefPath(nullptr, "SpaceCadetPinball"); auto prefPath = SDL_GetPrefPath(nullptr, "SpaceCadetPinball");
auto iniPath = std::string(prefPath) + "imgui_pb.ini"; auto iniPath = std::string(prefPath) + "imgui_pb.ini";
@ -117,6 +116,40 @@ int winmain::WinMain(LPCSTR lpCmdLine)
// First step: just load the options // First step: just load the options
options::InitPrimary(); options::InitPrimary();
if(!Options.FontFileName.empty())
{
ImGuiSDL::Deinitialize();
io.Fonts->Clear();
ImVector<ImWchar> ranges;
translations::GetGlyphRange(&ranges);
ImFontConfig fontConfig{};
// ToDo: further tweak font options, maybe try imgui_freetype
fontConfig.OversampleV = 2;
fontConfig.OversampleH = 4;
// ToDo: improve font file test, checking if file exists is not enough
auto fileName = Options.FontFileName.c_str();
auto fileHandle = fopenu(fileName, "rb");
if (fileHandle)
{
fclose(fileHandle);
// ToDo: Bind font size to UI scale
if (!io.Fonts->AddFontFromFileTTF(fileName, 13.f, &fontConfig, ranges.Data))
io.Fonts->AddFontDefault();
}
else
io.Fonts->AddFontDefault();
io.Fonts->Build();
ImGuiSDL::Initialize(renderer, 0, 0);
}
// ImGui_ImplSDL2_Init is private, we are not actually using ImGui OpenGl backend
ImGui_ImplSDL2_InitForOpenGL(window, nullptr);
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
// Data search order: WD, executable path, user pref path, platform specific paths. // Data search order: WD, executable path, user pref path, platform specific paths.
auto basePath = SDL_GetBasePath(); auto basePath = SDL_GetBasePath();
std::vector<const char*> searchPaths std::vector<const char*> searchPaths
@ -372,34 +405,34 @@ void winmain::RenderUi()
fullscrn::window_size_changed(); fullscrn::window_size_changed();
} }
if (ImGui::BeginMenu("Game")) if (ImGui::BeginMenu(pinball::get_rc_string(Msg::Menu1_Game)))
{ {
if (ImGui::MenuItem("New Game", "F2")) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_New_Game), "F2"))
{ {
new_game(); new_game();
} }
if (ImGui::MenuItem("Launch Ball", nullptr, false, LaunchBallEnabled)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Launch_Ball), nullptr, false, LaunchBallEnabled))
{ {
end_pause(); end_pause();
pb::launch_ball(); pb::launch_ball();
} }
if (ImGui::MenuItem("Pause/Resume Game", "F3")) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Pause_Resume_Game), "F3"))
{ {
pause(); pause();
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("High Scores...", nullptr, false, HighScoresEnabled)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_High_Scores), nullptr, false, HighScoresEnabled))
{ {
pause(false); pause(false);
pb::high_scores(); pb::high_scores();
} }
if (ImGui::MenuItem("Demo", nullptr, DemoActive)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Demo), nullptr, DemoActive))
{ {
end_pause(); end_pause();
pb::toggle_demo(); pb::toggle_demo();
} }
if (ImGui::MenuItem("Exit")) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Exit)))
{ {
SDL_Event event{SDL_QUIT}; SDL_Event event{SDL_QUIT};
SDL_PushEvent(&event); SDL_PushEvent(&event);
@ -407,45 +440,58 @@ void winmain::RenderUi()
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Options")) if (ImGui::BeginMenu(pinball::get_rc_string(Msg::Menu1_Options)))
{ {
if (ImGui::MenuItem("Show Menu", "F9", Options.ShowMenu)) if (ImGui::MenuItem("Show Menu", "F9", Options.ShowMenu))
{ {
options::toggle(Menu1::Show_Menu); options::toggle(Menu1::Show_Menu);
} }
if (ImGui::MenuItem("Full Screen", "F4", Options.FullScreen)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Full_Screen), "F4", Options.FullScreen))
{ {
options::toggle(Menu1::Full_Screen); options::toggle(Menu1::Full_Screen);
} }
if (ImGui::BeginMenu("Select Players")) if (ImGui::BeginMenu(pinball::get_rc_string(Msg::Menu1_Select_Players)))
{ {
if (ImGui::MenuItem("1 Player", nullptr, Options.Players == 1)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_1Player), nullptr, Options.Players == 1))
{ {
options::toggle(Menu1::OnePlayer); options::toggle(Menu1::OnePlayer);
new_game(); new_game();
} }
if (ImGui::MenuItem("2 Players", nullptr, Options.Players == 2)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_2Players), nullptr, Options.Players == 2))
{ {
options::toggle(Menu1::TwoPlayers); options::toggle(Menu1::TwoPlayers);
new_game(); new_game();
} }
if (ImGui::MenuItem("3 Players", nullptr, Options.Players == 3)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_3Players), nullptr, Options.Players == 3))
{ {
options::toggle(Menu1::ThreePlayers); options::toggle(Menu1::ThreePlayers);
new_game(); new_game();
} }
if (ImGui::MenuItem("4 Players", nullptr, Options.Players == 4)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_4Players), nullptr, Options.Players == 4))
{ {
options::toggle(Menu1::FourPlayers); options::toggle(Menu1::FourPlayers);
new_game(); new_game();
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::MenuItem("Player Controls...", "F8")) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Player_Controls), "F8"))
{ {
pause(false); pause(false);
options::ShowControlDialog(); options::ShowControlDialog();
} }
if (ImGui::BeginMenu("Language"))
{
auto currentLanguage = translations::GetCurrentLanguage();
for (auto &item : translations::Languages)
{
if (ImGui::MenuItem(item.DisplayName, nullptr, currentLanguage->Language == item.Language))
{
translations::SetCurrentLanguage(item.ShortName);
winmain::Restart();
}
}
ImGui::EndMenu();
}
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginMenu("Audio")) if (ImGui::BeginMenu("Audio"))
@ -472,7 +518,7 @@ void winmain::RenderUi()
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("Music", "F6", Options.Music)) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_Music), "F6", Options.Music))
{ {
options::toggle(Menu1::Music); options::toggle(Menu1::Music);
} }
@ -487,6 +533,10 @@ void winmain::RenderUi()
if (ImGui::BeginMenu("Graphics")) if (ImGui::BeginMenu("Graphics"))
{ {
if (ImGui::MenuItem("Change Font"))
{
font_selection::ShowDialog();
}
if (ImGui::MenuItem("Uniform Scaling", nullptr, Options.UniformScaling)) if (ImGui::MenuItem("Uniform Scaling", nullptr, Options.UniformScaling))
{ {
options::toggle(Menu1::WindowUniformScale); options::toggle(Menu1::WindowUniformScale);
@ -543,10 +593,18 @@ void winmain::RenderUi()
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Table Resolution")) if (ImGui::BeginMenu(pinball::get_rc_string(Msg::Menu1_Table_Resolution)))
{ {
char buffer[20]{}; char buffer[20]{};
auto maxResText = pinball::get_rc_string(fullscrn::GetMaxResolution() + 2030, 0); Msg resolutionStringId = Msg::Menu1_UseMaxResolution_640x480;
switch(fullscrn::GetMaxResolution()) {
case 0: resolutionStringId = Msg::Menu1_UseMaxResolution_640x480; break;
case 1: resolutionStringId = Msg::Menu1_UseMaxResolution_800x600; break;
case 2: resolutionStringId = Msg::Menu1_UseMaxResolution_1024x768; break;
}
auto maxResText = pinball::get_rc_string(resolutionStringId);
if (ImGui::MenuItem(maxResText, nullptr, Options.Resolution == -1)) if (ImGui::MenuItem(maxResText, nullptr, Options.Resolution == -1))
{ {
options::toggle(Menu1::MaximumResolution); options::toggle(Menu1::MaximumResolution);
@ -574,7 +632,7 @@ void winmain::RenderUi()
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Help")) if (ImGui::BeginMenu(pinball::get_rc_string(Msg::Menu1_Help)))
{ {
#ifndef NDEBUG #ifndef NDEBUG
if (ImGui::MenuItem("ImGui Demo", nullptr, ShowImGuiDemo)) if (ImGui::MenuItem("ImGui Demo", nullptr, ShowImGuiDemo))
@ -633,7 +691,7 @@ void winmain::RenderUi()
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("About Pinball")) if (ImGui::MenuItem(pinball::get_rc_string(Msg::Menu1_About_Pinball)))
{ {
pause(false); pause(false);
ShowAboutDialog = true; ShowAboutDialog = true;
@ -648,11 +706,15 @@ void winmain::RenderUi()
a_dialog(); a_dialog();
high_score::RenderHighScoreDialog(); high_score::RenderHighScoreDialog();
font_selection::RenderDialog();
if (ShowSpriteViewer) if (ShowSpriteViewer)
render::SpriteViewer(&ShowSpriteViewer); render::SpriteViewer(&ShowSpriteViewer);
options::RenderControlDialog(); options::RenderControlDialog();
if (DispGRhistory) if (DispGRhistory)
RenderFrameTimeDialog(); RenderFrameTimeDialog();
// Print game texts on the sidebar
gdrv::grtext_draw_ttext_in_box();
} }
int winmain::event_handler(const SDL_Event* event) int winmain::event_handler(const SDL_Event* event)
@ -924,8 +986,8 @@ void winmain::memalloc_failure()
{ {
midi::music_stop(); midi::music_stop();
Sound::Close(); Sound::Close();
char* caption = pinball::get_rc_string(170, 0); const char* caption = pinball::get_rc_string(Msg::STRING270);
char* text = pinball::get_rc_string(179, 0); const char* text = pinball::get_rc_string(Msg::STRING279);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption, text, MainWindow); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption, text, MainWindow);
std::exit(1); std::exit(1);
} }
@ -935,13 +997,13 @@ void winmain::a_dialog()
if (ShowAboutDialog == true) if (ShowAboutDialog == true)
{ {
ShowAboutDialog = false; ShowAboutDialog = false;
ImGui::OpenPopup("About"); ImGui::OpenPopup(pinball::get_rc_string(Msg::STRING204));
} }
bool unused_open = true; bool unused_open = true;
if (ImGui::BeginPopupModal("About", &unused_open, ImGuiWindowFlags_AlwaysAutoResize)) if (ImGui::BeginPopupModal(pinball::get_rc_string(Msg::STRING204), &unused_open, ImGuiWindowFlags_AlwaysAutoResize))
{ {
ImGui::TextUnformatted("3D Pinball for Windows - Space Cadet"); ImGui::TextUnformatted(pinball::get_rc_string(Msg::STRING139));
ImGui::TextUnformatted("Original game by Cinematronics, Microsoft"); ImGui::TextUnformatted("Original game by Cinematronics, Microsoft");
ImGui::Separator(); ImGui::Separator();