From b2f693113dde5bc90e3109c324ecf4f216dea6ce Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Wed, 17 Jun 2026 22:00:01 +0200 Subject: [PATCH] Starter improvements - scenery Description - scenery mini - extracted translations outside - autoselect first consist on scenery selection - autoexpand on group click --- StarterNG.sln.DotSettings.user | 2 + StarterNG/App.axaml | 5 +- StarterNG/App.axaml.cs | 38 ++--- StarterNG/AppInfo.cs | 2 +- StarterNG/Assets/Langs/English.axaml | 188 ---------------------- StarterNG/Assets/Langs/Polski.axaml | 188 ---------------------- StarterNG/Assets/Logo/maszyna_black.png | Bin 0 -> 22396 bytes StarterNG/Assets/Logo/maszyna_black.svg | 69 -------- StarterNG/Assets/Logo/maszyna_white.png | Bin 0 -> 32481 bytes StarterNG/Assets/Logo/maszyna_white.svg | 41 ----- StarterNG/Classes/Scenery.cs | 88 ++++++++-- StarterNG/Classes/VehicleDatabase.cs | 117 ++++++++------ StarterNG/MainWindow.axaml | 16 +- StarterNG/Services/LocalizationService.cs | 159 ++++++++++++++++-- StarterNG/StarterNG.csproj | 45 ++++-- StarterNG/Views/Depot.axaml | 1 + StarterNG/Views/Scenarios.axaml | 27 +++- StarterNG/Views/Scenarios.axaml.cs | 30 +++- StarterNG/Views/Settings.axaml | 3 +- StarterNG/Views/Settings.axaml.cs | 17 +- StarterNG/starter/lang/en.xml | 187 +++++++++++++++++++++ StarterNG/starter/lang/pl.xml | 187 +++++++++++++++++++++ 22 files changed, 776 insertions(+), 634 deletions(-) delete mode 100644 StarterNG/Assets/Langs/English.axaml delete mode 100644 StarterNG/Assets/Langs/Polski.axaml create mode 100644 StarterNG/Assets/Logo/maszyna_black.png delete mode 100644 StarterNG/Assets/Logo/maszyna_black.svg create mode 100644 StarterNG/Assets/Logo/maszyna_white.png delete mode 100644 StarterNG/Assets/Logo/maszyna_white.svg create mode 100644 StarterNG/starter/lang/en.xml create mode 100644 StarterNG/starter/lang/pl.xml diff --git a/StarterNG.sln.DotSettings.user b/StarterNG.sln.DotSettings.user index e68f177..79d3883 100644 --- a/StarterNG.sln.DotSettings.user +++ b/StarterNG.sln.DotSettings.user @@ -1,4 +1,5 @@  + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -7,6 +8,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/StarterNG/App.axaml b/StarterNG/App.axaml index 6b10b8e..ba0d66c 100644 --- a/StarterNG/App.axaml +++ b/StarterNG/App.axaml @@ -3,14 +3,15 @@ xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" x:Class="StarterNG.App" + x:CompileBindings="True" RequestedThemeVariant="Default"> - + - \ No newline at end of file + diff --git a/StarterNG/App.axaml.cs b/StarterNG/App.axaml.cs index 53bdb39..9df1cc6 100644 --- a/StarterNG/App.axaml.cs +++ b/StarterNG/App.axaml.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Styling; using Avalonia.Threading; using StarterNG.Classes; using StarterNG.Services; @@ -36,29 +36,23 @@ public partial class App : Application _ => "English" }; Settings.Instance.Language = langName; - - var langDict = new ResourceInclude(new Uri("avares://StarterNG/")) - { - Source = new Uri($"Assets/Langs/{langName}.axaml", UriKind.Relative) - }; - var resources = Current!.Resources; + ApplyLanguage(langName); + } - var languageDictionaries = resources.MergedDictionaries - .OfType() - .Where(d => d.Source != null && d.Source.OriginalString.Contains("Langs/")) - .ToList(); - - foreach (var dict in languageDictionaries) - { - resources.MergedDictionaries.Remove(dict); - } - - resources.MergedDictionaries.Add(langDict); - - - - Loc.SetLanguage(langDict, langName); + /// + /// Swaps the active language by loading the matching XML file from the + /// lang/ folder next to the executable (see ). + /// The translations are no longer compiled into the assembly, so they can be + /// edited or added without rebuilding the launcher. Accepts either a display + /// name ("English", "Polski") or a short code ("en", "pl"). + /// + public static void ApplyLanguage(string langNameOrCode) + { + // Loc.Load resolves the file and returns the canonical display name, + // which we mirror back into the settings so the stored value stays valid. + var resolvedName = Loc.Load(langNameOrCode); + Settings.Instance.Language = resolvedName; } public override void OnFrameworkInitializationCompleted() diff --git a/StarterNG/AppInfo.cs b/StarterNG/AppInfo.cs index 71fffbe..1fe417b 100644 --- a/StarterNG/AppInfo.cs +++ b/StarterNG/AppInfo.cs @@ -18,6 +18,6 @@ public static class AppInfo #if DEBUG text += " (DEBUG)"; #endif - return text; + return text + " public beta"; } } diff --git a/StarterNG/Assets/Langs/English.axaml b/StarterNG/Assets/Langs/English.axaml deleted file mode 100644 index e7cbe9f..0000000 --- a/StarterNG/Assets/Langs/English.axaml +++ /dev/null @@ -1,188 +0,0 @@ - - - - Scenarios - Depot - Settings - Controls - - - Sceneries - Vehicles - Scenario description - Consist - Start - Open latest simulation log - - - Vehicle database - Search... - Load consist from scenery - New consist - Selected vehicle - Vehicle - with crew - reversed - No vehicles found in databases/vehicles/. - Add vehicles from the database on the left, or load an existing consist from a scenery file. - Move left - Move right - Reverse vehicle - Remove from consist - Change crew (cab 1 / cab 2 / passenger / none) - Scenery - Category - Class - All - Diesel - Electric - EMU - DMU - Carriages A - Carriages B - Electric locomotive - Diesel locomotive - Steam locomotive - Railbus - Draisine - Work vehicles - Prototype - Tram - Car - Bus - Truck - People - Animals - Wagons - Other - Brakes - Loads - Coupling - Select a vehicle in the consist to edit it. - Editing options coming soon. - Split - Split the unit into individual cars - Add - Add this vehicle to the consist - Add the whole unit to the consist - - - Loading vehicle database - Loading sceneries - Starting… - - - General - - Language - Fullscreen - Pause simulation when window is inactive - Pause on start - Cursor sensitivity: - Invert mouse horizontally - Invert mouse vertically - - Communication - - - Ignore gamepad input - - Feedback - Off - Caps: CA/SHP; ScrollLock: starting resistances - Caps: CA; ScrollLock: SHP - LPT - PoKeys55 - Serial port (COM) - - - Other - Select exe: - Select automatically - Debug mode (test mode) - Virtual shunting - - - Graphics - Render engine - Full - Old - Simplified shader - Simplified - Disable visual layer - Experimental - - Resolution: - Buffer resolution: - Maximum texture resolution: - Maximum cabin texture resolution: - Texture filtering quality: - Multisampling: - Rendering range: - VSync - Smoke display - Smoke particles multiplier - - Post-processing - Reinhard - ACES - - Chromatic aberration - Motion blur - Additional shader effects - Reflections as cubemap - Render VBO - Render shadows - Reflections refresh rate - Shadows resolution - Reflections refresh rate - Cabin shadow sources range - - Display shadows - Only important - Limited - All - - Reflections detail level - Terrain geometry only - Terrain geometry and static models - Terrain geometry, static models and vehicles - - Cabin shadow sources range - Render onboard computer screens - Render screens in a separate thread - Onboard screens refresh rate - - Physics - - Track curves quality - Increased physics accuracy - Possibility of pantograph breaking - Powered only under overhead line - Save speedometer tapes - Save simulation log - Keep previous logs - Display simulation - Collision damage - - Sound - Enable sound - Volume - Radio volume - Vehicles volume - Positional sounds volume - Ambient volume - Volume during pause - - Starter - Automatically close starter - Large thumbnails - Automatically expand scenery tree - - - Save - Reset - Settings saved - - diff --git a/StarterNG/Assets/Langs/Polski.axaml b/StarterNG/Assets/Langs/Polski.axaml deleted file mode 100644 index 418b153..0000000 --- a/StarterNG/Assets/Langs/Polski.axaml +++ /dev/null @@ -1,188 +0,0 @@ - - - - Scenariusze - Zajezdnia - Ustawienia - Sterowanie - - - Scenerie - Pojazdy - Opis scenariusza - Zestawienie - Uruchom - Otwórz ostatni log symulatora - - - Baza pojazdów - Szukaj... - Wczytaj skład ze scenerii - Nowy skład - Wybrany pojazd - Pojazd - z obsadą - obrócony - Nie znaleziono pojazdów w databases/vehicles/. - Dodaj pojazdy z bazy po lewej stronie lub wczytaj istniejący skład z pliku scenerii. - Przesuń w lewo - Przesuń w prawo - Obróć pojazd - Usuń ze składu - Zmień obsadę (kabina 1 / kabina 2 / pasażer / brak) - Sceneria - Kategoria - Klasa - Wszystkie - Spalinowe - Elektryczne - EZT - SZT - Wagony A - Wagony B - Lokomotywa elektryczna - Lokomotywa spalinowa - Parowóz - Szynobus - Drezyna - Pojazdy robocze - Prototyp - Tramwaj - Samochód osobowy - Autobus - Samochód ciężarowy - Ludzie - Zwierzęta - Wagony - Inne - Hamulce - Ładunki - Sprzęg - Wybierz pojazd w składzie, aby go edytować. - Opcje edycji wkrótce. - Rozdziel - Rozdziel zespół na pojedyncze człony - Dodaj - Dodaj ten pojazd do składu - Dodaj cały zespół do składu - - - Wczytywanie bazy pojazdów - Wczytywanie scenerii - Uruchamianie… - - - Ogólne - - Język - Tryb pełnoekranowy - Wstrzymaj symulację, gdy okno nie jest aktywne - Pauza po uruchomieniu - Czułość kursora: - Inwersja myszy w poziomie - Inwersja myszy w pionie - - Komunikacja - - - Ignoruj sygnały z gamepada - - Informacja zwrotna - Wyłączona - Caps: CA/SHP; ScrollLock: opory rozr. - Caps: CA; Scrollock: SHP - LPT - PoKeys55 - Serialport (COM) - - - Inne - Wybór exe: - Wybierz automatycznie - Tryb testowy (debugmode) - Wirtualny manewrowy - - - Grafika - Silnik renderowania - Pełny - Stary - Uproszczony shaderowy - Uproszczony - Wyłącz warstwę wizualną - Eksperymentalny - - Rozdzielczość: - Rozdzielczość bufora: - Maksymalna rozdzielczość tekstur: - Maksymalna rozdzielczość tekstur kabiny: - Jakość filtrowania tekstur: - Multisampling: - Zasięg renderowania: - VSync - Wyświetlanie dymu - Mnożnik ilości cząsteczek dymu - - Postprocessing - Reinhard - ACES - - Aberracja chromatyczna - Rozmycie podczas ruchu - Dodatkowe efekty shaderów - Odbicia jako cubemapa - Renderowanie VBO - Renderowanie cieni - Odświeżanie odbić - Rozdzielczość cieni - Odświeżanie odbić - Zasięg źródeł cieni w kabinie - - Wyświetlanie cieni - Tylko ważne - Ograniczone - Wszystkie - - Szczegółowość odbić - Tylko geometria terenu - Geometria terenu i modele statyczne - Geometria terenu, modele statyczne i pojazdy - - Zasięg źródeł cieni w kabinie - Renderowanie ekranów komputerów pokładowych - Renderowanie ekranów w osobnym wątku - Częstotliwość odświeżania ekranów komputerów pokładowych - - Fizyka - - Jakość łuków torów - Zwiększona dokładność fizyki - Możliwość złamania pantografu - Zasilanie tylko pod siecią trakcyjna - Zapisuj taśmy z prędkościomierza - Zapisuj log z symulacji - Zachowuj poprzednie logi - Wyświetlaj symulację - Uszkodzenia przy zderzeniu - - Dżwięk - Włącz dźwięk - Głośność - Głośność radiotelefonu - Głośność pojazdów - Głośność dżwięków pozycjonowanych - Głośność otoczenia - Głośność podczas pauzy - - Starter - Zamknij starter automatycznie - Duże miniaturki - Automatycznie rozwijanie drzewka scenerii - - - Zapisz - Przywróć - Zapisano ustawienia - - diff --git a/StarterNG/Assets/Logo/maszyna_black.png b/StarterNG/Assets/Logo/maszyna_black.png new file mode 100644 index 0000000000000000000000000000000000000000..f05c575b2df9daf036885ee10ce320afbc817a5b GIT binary patch literal 22396 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 - - - - - - - - - - - - - - - diff --git a/StarterNG/Assets/Logo/maszyna_white.png b/StarterNG/Assets/Logo/maszyna_white.png new file mode 100644 index 0000000000000000000000000000000000000000..77c09f2979442f1bddde5f4b56efffc16549640a GIT binary patch literal 32481 zcmXtg2Q=0H|Nku_nHSlku9fV)iLSk=BrCc6L%mu9b0ZWsmH= z|1aOq|K}X1>m}KGy3-=;^4E-eR}~fj~&r)!+sY2;MRTa*ggL0eGdabaDv% zbIr{_Rq5JT7jF>w0pCVZTM+^&k0m~{ya9f`^HR+~8v^m=gg{=ug+NZgORvEdJOm+- z)#nh1bTR}&`zozoUl#la(JM7$Hwc8E?dtoQcaDN5c#+UuU0a!Ol>ka5M*m~z4!HIm zh&o*Hk@xt&DIb5c$8{20OJjplQuMwlRd?B|2sDT}3^L!6*%f?LY>K)4#kYwp->Atq zefqTd&|XngX*g@TNYeB+)2oqNe5hL}+hVNBv-g4?cd9tF2dm_IUbQ#jb}w3?CMn`v zUGxXzr-$eMXZi2b{+$!dlZQ{j1(gVMRkQqUeZ9tgx%6mxzK^g zOF!QW951X;?%&Ho^cax?iLzCRC7n_9q1%f4NrD=Cg&cNY8&1Y@5F`Rmz+P_Z!=&Nx zh_fJ8Sk_w<6D%tLR-wvW&4bq3i0POM-BLzkPeKzqc~D#27Zvj*LexcAqe@{TyrwJ*h`;Zc)| zNC@uw@pk(@;NJhbGet{!O%JKfg3Vh-8i= zoqyD0%7`IsyTy{17h3mQv?qgby?La}0%_{Il7qBfqcFICz;IQnm>;=MQjfHm~HSnm7CI+l#3% z*QcS&Zf(sSbF~Rho3#sV8w`pD{X0B)PS@V;ejlDQL>saEfx=T@qc>fKwyLHE;r}s{ zY9IpZuc3B&X857YN|TqyueXoZj;{R|Mbg0EkmxFBoFn+-ho+vil=_*J?l=ztVUoE> ztiQMc=j(UutJUy5*7B$S&NT|k$Vo{{B0J_HC^_*MnL3O+u+qJfXNMRJ#zVSvPJ(1k z!DChCqk#oIk5peO&AiKN6qbdV+04+)tf^MZ>A}aDIc}JA4Q(0ens;?h&VnhdzR$?% z`KZjP8oTQo0=y5_bcuFFc!#Aeru?;P-21QXQoQa;5pF5BJng}Mg6W$p&jl-P=%^x1yg`)DC8Do}ig zqzl_bd*mj`1Xj;76if_GF(jua%B z>*je<{FN)dE5WKO!eEt=fn0VbXF@DB_n1}MtPJH}P**2)duM5D3V*z?wA8$Ch4<5o zVGH(UdH3=R0seHIKFdW;>iEFLo>VV!kq)KFKQ6p9gW?4}h)SED428?PZGt0z^F<*u zwGx_+6M>_t-f`Euv`xZ4m581b8-%=KcE7pNFDiV|VjEJfbobv56huw#?(S)51!lH? z`?N&$fkCUssZ|YEazZ=5eQ5)L9By}w6yu^;o84{|K`UkZGytlGr{A;6v+oR zHP*&KomRqb;FhIpfsf(vj;G1F$NZ$f)@D2+@=#GRF)@Lpx?_BssuaccOQRHF803i}1R0TJk#u;15jnqJBe)~rvc zs;DsJofyZ=?NJP5h>`v@IXyiMXt1;6Pcmu6t~31HU8}CH4)OjTvtyqw&VkR{A&WKs zIoW_uG5GFt*PNN=3AC3}z_fqQVaQV&)pBba4q5 zw3p7&-;bJ}@*Xf@a_2;}cU|GFmn3c9_72!Q+&w*oDcvt04-p5r^CeH0BIUkIe30M7 z2aA;+zL#;2{&hdc3KtHCvk>~&mtRmoeS zpDN2L-|*q-h2ySM?y;SQhlx3iVE5aq?|g>*g2a>oMf^)Xl8Q*#kCPN7Ro>_pf%v8U@kiDyV`H zH#MO#EUYIl8sFictJ#o~llMJP!ZWPGzbpC6$E4QTf_sXR>lsg*HiSw=Tqx(~WM)QekRL z!wN^U9E{*uh<~DfzPNpfkV@BSd5hLnidg1K*U7+QxnPv4awY>KBTpJ9GjyodM#{Bo^Ejx;AZVs$Jyx)iUm!wUGG>>~ zcUyh9vOD4?TQgSpx=sQX4=0qjtO`6|E^ho+{S(mh6uBee$4D4L+T^MRQeRxq{=FSL3Qd6QmXsj#8Zp6&VKHF$%3c9>_2J;Q)FW-?RNA_`9^!0WwxJH8B zb#q`V)=GE`i>;)Hco}m$>4~&r2lb)QXpLF9-Nx~*vBf)se%(O1x$E%RWM&V|`yUHO zr*D~=nQ=m!#J_ds%$SEr;yJflc7!xvv znPXo*PLc+bu$Z;KhBIOp4;)n^=A8QT>7V~3cXBR$=NKu>^-v6Fx~q_HrguM1%_+`6p^N9}){L_=>N;rPBF^ zUcLz4Ufo`EG*eaW@CS{VCz-zJNlD?*SX#1Y@H|zzlHD(^_nNAvxbwc=ULD2ZU;|<8 z-8@*u4Wg3)X&D)192@Re0B609YLO47mPR*l;AEy7dLw9geLb3DU<~!+Yk;|O%Z~@a zf&YTJPn|c8SEwT?0?w}r9F$Lmz1>6SB431$a5j()HWf4Hs8Ss)JY2g;An0Oyuidx@ zz!cE+KU?0sC@{5oY*RTV4)d+!^f;HFsFCoGNZdz^Bb)z~UI`NJe|0A`J?ubp7?$(0?U+TkcSPOq%(`)DAXH zo_S&hfr0o(jLtPf2Jg&&XERbmgnWsim~B!$k^$Q)BRBz`w}Y}BBN)DVlx^+gTl7qP z#*HV!;bhDyI&kFhT^86*rnRxx-v|L;IX|z0HG1ktWNQ7yB1muVuP+3q-)FKmT<-uK zh9>D4`Qls(bmT((osi(w%%8t^8@cqdGqD(w=c$hp*jku6e9}<$qK%h6M&>>X9}F&{ zQ&Lh6MclN)z3=6GIcRj>C(*pdLq0eBeU-=P@ngP%p&8ZSK&D)~M86anN_SCjOTB|J z0@mYBReQA5t93K+RP9V@MKm+XAGzD>TFz#%rXt#vR;fQdyu3`XXNt_TcVH%0ON~)> zk)!CULVt|c$62PY(@geB zBvaJWDEV1H$FZMy%bA+dx5jDC`uC4JiiZWt$MZ$=fDcY%)}$+nicA=O)K+qAu>U%t znG%e$eK3dG_t$fNQ{;vWP+QV;!;1k!s9amjsRQ4plm7~mN6Ha}ffRRDBxb%s@(=X4j zQMw0lTF}mZx|U!za92^_`b13}FL*JdprBw(72Yzm_1hw`%SwlDk?>jKevqhoZ!PG8 zK63U*W79N^9K>Cc1u;QEJ#9EKMMOlw5RnO}Qbs!-%K-rakKyh#N+lVfdN9JPZpI~l z5$Ue>XYV9wdq8px09PMO-W0+Gi>+~@U0+}9>{MfeHmS$spI3sa$#3s$yn40NC!i5K zg=J+5v1mT{i|_l@FoA^knX#KuR>H==naLR$8EHi-L-f{ch6^7%VP#3f z{t0zB_H`)+Zw%h*iBp^ZPI*aMtbluzliQqG2{Z-0yGKeDTR(I=YM1%ClLRt$u&pH~ zwbJmtK3fj>kfNG+W4cASGvPs&FXrxKLJoYvmXQ$R6H5ts-yEn(x*Z+6;rOtd2dRaV02 zL5=M(BU6{3^jOo_duukJ47@=t!YJbdOJ_k{mS&E75KQKqEcB9Kbpqd77>$fTJxPrf zs-%kiIW0Uk12mNGLXns8@dd*{?hJ;EfBrDJ-?LFb;Rno~|ImSrXIf4Ouh?zm+68Kb z`%%YTI@{Z8WF4mXW;9_Ux^J0h8*91nim{N zdFDrFXJ;oxNK~)T)c902w(;h4W_ByFh`(YeKJ)ub!j%8v#*limT5Dsfz(|&86$P-I z35ZUN7#JAB^ksN3M|1#dRKys?WztTgekAdkcsdIyxLH|! zjzjAzyYCMIGy~O_feBgX0g1wroPd4#^$CqFZ09klz^MhNa7iU-(C<(ek3%Zx80hJb zrR{T&?YPZ$^fHHI)!J&JU?rH%I!xkz4in{G1b(;RyLvJ?hUnS#y4+rAKo^qh2;!1m zLpsWzlH}3dvAiyaF#<#4yVJ!L#0_f@dnizQ{49LE7b<4IBd zTVns;JWpAps}xMXV)90%TqGW{KVTAN`gA^=V9*cnbUn{ySs;6@GE#poDdFRSjhFaf zI>81%+YD@Bh0<$t#^XMzoJ88CjPjPc3%1|aF&RzM$+Ngx&(b@FU*Fw8_U{Mi4g9&i zU8i*Oz2LP~lm2KxMK$3nVh#U{at#WM=g!}+)zb>?UMJmHO1+Ys^cVw{dO|`%l0QDR zI?ln1q==aP;?&^y@~UEL{>qAuj#fs8#arO2DfEYFDEDTK(<}zb)snx>1l)<)PyN;Q z%*g9CDlp3!4KQM3z+~%ZOaGzGLl|tuUHaU52yQV_nX)pN2Q~|=VfG!*c(RW)Oux$a z9sX3=yK1tgoSd9=G#e^E@I%*p1a`8p^*K8SEK4!OsOfoYu!w#or!|8TBp-S5%IvU6 zx~f~iK6}QaQM>W?${rKUx0;~V^&v0K+C#oQC%FbD{d@d_qSd2ws_+jfZ>tm8Krp|cs;{4PhV%e3{`R^%^S&WC| zEn@M$zF(jgy!&jP0p^;==`jSp51m+jRt;-9Bna9>ZBszC$H-FEpc4@_Gw{~~0A`){nSl1`cf>^s!DhYa|r>DbU7K1A(=sIbZ*p?OL!&U$#Nck^20D@(- z$I$E-@5g7H&EJB*SS>6p1Y3&6!V_pB&Us5V0jqkd-8&!1z-_hcvL(AKwrcbY`S|#V z(Ncx_!KK}d?toz>KNha*z3y<3Ilr^EA!w4VcCbEihzGctv5DIc(L>$9o%k}##X1OAnF zc#@KlInzQzd)fQV%N=l~_r60on0e{i*C;|~HSz{6sN&KPFEhL-pyQk;a0{^x;l!$`<{8NRrpf+N|1(0lR}3^ zD78x~Ynz&;oSdC?paX6xrn^4JVi$s~t=I@I%n<|px(3JP4GPLVSme(afR7UN@SpB% zY&dsU6vx_rY2K&laJdLe$+d3rl*$tH0ic^WxlSby-z<+lW5L@v96w-(KMl+9c6D{t zy54adq6*%>fF;6Ad|ukWlGIH6U1U{NRa$h4ctqHwVCj$ETD|*##$NuyMx)@{0HzGb zIbbKw74b$ix7OiTjZ?b#p^Pjy&W>4HTrbVk&%KvR;aI&`IXr6(2p8z%%e#~uYepNi zafD(H$mSmGusrMDjPt~SgJU_ZeZ4Q(2X?MwPK0Dk z`459P40G*t5E5SO%O~RA;{j~w%b8?x>g!>artubD1bU)*8G$E+$;Y4vt4}ygO=lUM zo-@exwT-)-H@Tgk>~|V+s-~KqO~_27(Z=rnrJ4B z>u=We>*7c<-4TYrc%cGRK@TY)JASY@`c8X7eyS?nq<4QMN|$}vaJS+LPlaA=fosqR zyOCH9>RMP>+=iy-bnoBKsbM{6sUD+`N2eMV4E@WBZFZ}Rdrx9p+6A=C?H@`)KwWS` zke#`x!h}t`T4tuFH{Nwh=+C_yxhzqp4!&lSrE4n6Fl<-w=K!!ssehMiqUL-nnlj+N zJZY-{GySBpwH0hKGXz~DGI?yo>I*j*S?|R!0<<-D?n4YxF@}38$j#{{OZEOD$B7Gb zFE5GSqA>1l+0An-Q(^#V7*D`8Ha0e(vFQ&ravugAnj{W~s^PciNKyp^N2FUWS3P=l z8i}z;NBHw|FHn+z8(XCyS_dc5^hEW|5wSWmY8!R{)?A!lUilq>tErUp`z{H4ZA=!U z_QF4x@>_g;^*3pI=)2W|Yh4c3-|Xpz5gb#pC#IDuzJ3RKPFIbzPcX<(q`p3!@fe5% z5_c)7vW6M=Xysa@wOwM%o>kk+14%EXQ^jQzqB*0Zlvly%@Qxy;?2hk!`2PMw%u*Ap zF2B|zfQiJlcR3L;Zx=9TxPWnTTpTFHys+SBl2WjpEgOe9)1eiZV2PM5jJX7>btN`i3WS{)!J^aBi4Vu#~z+L0_Y-91niBfQ&+X zS6Y}_)D;I=TCTzkM^fzpk{JMVRm-lAz@O>}eK|ZlTtc_Ge9VlI?oMD<>y+0Ay?D7mGo+EMFWF zDwy?waowbd@`v2^m6^OTs!-HhPbS(EFMvP z(CrGcCOpgEHLZiIj9qQ9Pfum~c5A}q76CTGgYP}KxrUsqD4q~frfB~SkmZ$8lZQR4 z7o2Wct8eyQ8y_3vhV8WEqp28{65RVWpX$hB~@ka1dD^ECJosWW>ZTc|Guvt5IM&{${%bzTr z=YpR@QmMdRc`2SMM;t41W`;x}t^R%wZF#=ft&}n<_uk@god+l^D(NGZCz(tNwZIw# zTPD?#4zSc1zW>p7-e`WG2_ejqQ_67nhxwaes6~zzfYH`keh~nQ6d(=uF=0DP$QnCQ zJ@ujCVP@}bg+A8m8qI7Q`}PzuhehgBH(*5s-i39&uQB8TB$f;3VG+i~NEz{?3OFM( zt=OculI0BpvAE&>enBm`L{=irqp%4HyPow-mA!uZ4|E_ahE`69w-ja``k@yc_ zfu({LbkT|Bd6+i3zd6-_s}-?#t((Lt<0C^m1UmX^Yik=&@Ovj^h2!JT_RNsSEBybL z1?Y(BGw8ug2~q92OP81gH-Bwtn7E}K%n$Puo9JKhUh7Xf5p6&1{~SH>hi~BD2QPdM zMS;gQKo4nQhoLEOD>p?hvAd1rvqa<>TAXga0Gl;onO39ml;Qe*p=1cVs1{W4!Z1pV*3A1f=fD?{jzl~7Gt9^g6f>Iy z##j85Hrp+4j>BHcOy!a`iN>jE@%$ttCn51yf%^)L{tZf12)bZZ1@p7@gD@3%&gz#3Hs^ka8%@x-o7OG+p76hF0RJrsxxw=sSQr?5 z7bP#H_eA6a(lZ-M8x$+AOWQNoTi`MxQK?;47Y|-({YR^1Jys-r-?Kf`Nhv$sEsUae^A$xq^31oVipiXntHm zFS3KT!@=X|!wrq!HhhZIF~7tTZ4b^4`T#X&zegnf@p*e*8Py&LWN5=#hVx(ZFN1Po zG4%e(d&Se>|4KN&4<63zMshFpU`wp=0)W4VJWG5<$ z{bc+6XtSV9V4|I0zMN~qp3%TV)ZiFq7M8#a9bB3kd*Ev}T+IEBHvy*yX0d9JguvoQ zu!#e40+f_X$~fntgC(y{FcE$l1&qKte2GF$%l4T#O?XO(WJKFiM5y)dHrCGTy7sGF zbXIR>U1U1T5jHF+K+-X z;IlyiC~SQjFBW6(FsSbNWPohnF&C_?2?6(FAO5>nj=WSh3WCHD%`ZR<77kVNsDxGCCwN)8T8ppYg$U zkauzE+cg(}(H)($sRLhrPDZ0(?Q%NzO;BCwX*-}DH^ud&3wurr?NV^~f8?DX82{?; z?7VqE${ojNQgfyOKYbI#$x}P5(e?LqNx9cVOo!&VDy-Kblq5Ho*4`;Yu>&(ME$S?i4ou zDxmB2fwyom))#ni4LVk$@9OpU&!6X{(DaqIYmq=BYv{+^C~M_mS3HzOGX9$Vs(uo1 z%+B1v$vU>3W@$Wfj#(;E5%m}A^y&H@O1x>g80#Q8mp8QjmzK2cC^mCaH!!|0h!@Ci6vt(hFl$7f}>bC3Bb_W)+Sn`L45NQL(2 z+%^Z+@px zx@}N(UoapodK%{xhSLE;?*mKbB*{f~U%=;3tr@6J8 z9_YWWOnIts7A5vQJqwGL&b+$zZR(u^Sy1LYj6-xZxYIq|6gmW>a3_1?8xP_={r^Ax zs#Gkf1v`0qJFpbUL8>Rr)$Kszv({DuE&(qPrg3&5zl0MzCQ?&UHh^XCMiIT!t!uX_ zdR2K#RpBv?gv#^hAi71ceF2N>jEB2h-0T)h2mLIH!Mw(M?r94J=i5Nd{*MOIy+@`j z_4wxnd6<)Uewh=YN_F_={#-S8PGC6a`Vk;#F7Y{L$K3Bj`}tQyNwWVj@`H@mw3V=r ze6KodWwgn%q)zQxJrXYZi|eCCdey7H-}w<2q9Tl_PN=%F0eAH;(H_R@ff<&Qyc-1A zGoFmUfB$Ag-0VfG2AdDy%b~Saj|iHiK5E=v@OE;NJ~~TnnGn7IIhkDMU|_1jYbJ!# zX|lp3_pdcF<6GYgX5TmV_Vxq>goH-#I#p!pRpq!`21Oom$Q*0q7P6Bj5C-xJNRD|+9cL<@dX600*@*z*Ig~9P>@N~X8d`8oQU6?G zhB6&M1u{|paO)RL9o)Z&IZB$};2cvLSUsu%rJx`P;tW{;T-+4*5FT0I(7;pCTUcK1 zvbS1P*8B)APc^%r(V;msBVp5<<~O*q$Bj}UVpuQb7g5ML=SRq2gGv?3`GJH9Z@JT{ zfaTYj#l=PHP=~zrO;zA!>JN6yPq&MDWtjY@D}S2spiRkKvXhvrd|Js+rO|)*Z_JIW zmmBl@;hVYPb|H{%>%`{SKCPir@2~a?>BU~*iu_TX9Pl_V=v4Ahru?v@4{OUic3wQ=TAz$9jW*du5Y+yI`BNlT#Ihc0eF^NC-Uk3Phnxx!y!#W#y2rrZZt zLFMT$`B?3A+WMYdS0O?ryDzgQ$>L(~GwWT-<_$LwSu)ApKOyAu5^pkaLW&A`z;620 zokmJZOglIUOoD0P@-i26_}zKu9cC$|ZB^Uda=wmiVqmFWOzrKgSw9s)Yc-%8+o{|h z(F=(LomF)6H;Bcl{eZ@G0sgt3Xg$@2EkU&p;K}9;$$%2GygFR;81>M_@LTs-_kydp zpSdDhYq0?~3*Jr69OK?JaEEn1>#Tg$Pn+uRf91SPxd(^2q@q4=o9fWT81Y0U5}g1? zrWHv)J~3eiTUE9Z^#LQ@K+YujMG5ZQ#wLOw+#d%KHp0p=N5(9nH*uOsB%Ip=lotgn zM4DKT`|p|=P?>=d-u9*AyT_CT{uvEU6&9DLx5hI86dYEN*pPbl=K(Xc2eMfRY~rpkgYjcX+*C*($TSInXw^C3mj&0~g4$AzDoP zfSax&?liuq0#HaxG{rTM4A5BsB8!&u^Q09C!XKckO!ot2r)Hw2iD=-C zE0awnN*3B*L30(3FgQ>)eqJfb3YweA2Zi5K{X{3r%lU~!zCs9p`#K#8FhxuPURGA~ zv8W&0J*g7NPr1kv&Pp2nZk0Y4CJScn3fBdVKEWv6dpXkrFk@L|RSyHPFw0^ijeydk zqHZ|69nj1tz-Vj{-lYVKM*bg;keuZoN+r7q)ea4$?>`-1mh7F;Yy1fEG_^un9VaVU zbVysCp+sHVMuyl9@`z4(QlwZKk*_eiQ5kM3n{}f>AgGiB|4VDCR&eP`J7e<{8_003 z$@MinfFJx(m%guVgD|fITV*B|6*_VB!F|tF+I(bWP`4!7bN|@QR{I>mR&K^AX^|K@& zGSC(X5GFc#qP;m>Ymqft6Mz&(%WA>}O|1VdIamW-yLZ8L=`I7j!E^oH{hVebU3)DW z(2E%R?63rbBoHr+c#kCT(gF-$9b|W(FI+HA)HLYVU;Oo9$*nt8yGTe-P8rGRVqbpd z{K{$KAfdfzoFz2GZX>kqWH)%_BzpdBi;*aF`Z2Wchb24%qZh)*KBU@Z&5*oezm8JH@=EPSQwwMrx zpl|+^A{n3U*uH$J(NGVupQn|9rDF@C2o7&I#k28Shq>pHRgMJCZZui>mi3AyO zS9_lxtg~Wdyqxn#HX2G#b;B&r-lFre#iPb-&R@KGwIv5|Pd2LkD8axBY@(p+1_xea zHPK^n7rd#PBD)?a_yRh&V9l}bk(=ADsvPnK*CFslv^OQdA_3e^CuhhaBA+>S&E=eI z5D!E|dWEB@Fo$6)euZb!d#C4t>0mL4g2*($Dm_?#VN6B#>iQ`#oJEkfw#&c4#>TV! zY6CKt0wO_g(4<4Jvne4)aCys=#UM6@P)0hsBR4}s!$fpCM3&V-KkFhvi!;c;%2$xW84%(`czj~ zJ|^2F7;J*PGxIU`IOLDp@p)9>TeU|zf#fXx9lyI`Y^qrCeK6tp=X9?c85kNNTyEou z#y}?U-TKo?2Cbv#kV1?F&VycLGBYz%w!U!)Hr$kGkAkopo5o)LynoV?50BPhBg+7! zUlX8~Qq)bDe%)L0yZ!dER6%Ww(}D#YMB`k`$Gh|yiei%X6r%NjTAxBAo^$yvf8HAc zT>u2Stu+b`Bh_er`V5Kq*j< z3|rCq5Y$DKT7t#@?1!bhl)mM>4hACfAr-0r`0}r-9zO&rIReXjos@4M$k<#3JeC8g za_!7FeWr!|yCe}G0zE+>?+!qzLw|v-?#GsKGL#qi1Dny#(3(x@rVE#M1NGC)6a>O) zq1XCPeA*E%xBCYM%3q^FKzF@eINtXYH42}3aI(YkflyNuWX0W=Oa$n$<6_=VWLXp> zZXUjv@H|{zUWP&Im^u6&N!31090nxdMqqO<90tvh^E;Tro(bHm&OXTs%o+L#aPT7T z%k|go2b(jOnoxQr_FtgDk--%;|7!P3I8S<)q4u)L&O+Aefvv6?)Bk5Bwed#oKYIrU z!m{R*RUD8pIAKx=8_)$)a6ABMhHJvc;v}S{eU(6%gca>WudxQ^%TPL99#>JU0Aql( z_K%KAYQWc^5fz7_Ph$%5j*sWIzb9obwS2t1z50zfB+CY%a&h=|QkGvjtjF1-rG^@2 zyf8_Y?aqoA5IA+a%4_T5KI-I|6crYN+AEp6-6^jIpD)st-Tt9m{zU! z)0v?8u-n=9@~4h2_o&=qupH{#x=Xy=Us;AefHVC<<97{#v5|^S)xWRGz7zx+!;*30J|i6O&Q{+kIYsCW=n_I|s>BFCjN#jX6~F(#iTntrt97KTQonaUIkYm49dhmUtgL;^qn< z2Kp%-he1C#bclMvVD+`l%`6QLG~)fLA>qP%a@`dhy;j0Y5uJGVwgH}%NZQ?u0kA*? z76-N}kH+87AO4-YjS`?tT-3QOA)wILuR2-L-h?oH8oIJoIYfc+m*u_o8}}8FK)s=|P}d zIS1!NE=hpxhFBiZM5uJCb<)9*};E)5yRbq7YAPbzs2tP8|M9rTyoS&bs zgOq2CE#&QBBA)11@R`SosA&|BFJ{lZS=_lV8&#k4+ z2S|}f#Nl`$0TuPGvq2+a(jWLv?opZ-NAEhmo0c(m_{gHQ_#;)ay@Yo)AEte_7{uWY z=L(#k1HDDQ3%gChwM3sYH1X;!10*UBFcLLx4-se+^T0j(pxkx4hcMS#lz#(0s~k=+ zs?VMw@cj!e3-#ea`J+h4h66a!!Lw&)Z~27vj7 zfD-+TS%e9n?G5VA#CjG#mo8J7$y%{VVi4Ewx;i2C=FJ-*R3N`I;2|zr42>hr0m!pN zl#{u(Ll*=Ls!qYCMggx^w%88X7c!c<~sK0pocx>`DvBkbwuC0>i> zv&ZqrQ~ob5FWz%5T~Ry0hn;1WA%f8VWjPe!?C)Lzc z{OhLc0$I27hNOAPt1D<9k6v?cx-j4zC|z3xCzwPT%z_y%$ghHXi}8DU{6XOSTL#yz zAISE#=;4~^az|rHd@zUiJ6u?P4&p)L<9ZM+Trd`xeuBK#1o{nZ$I;1Y{em(u@Q;l@ zf%KIU_>hie)|5XSzv;no&kuK}t;X}y!z4$xDvz}hIW}OPY+zrxPt&QN0B(^3+gYNR zz9<9(ysAEwT^lASrR$41l%A-$49+8faDuLBF9Ds3R?{;3gnSlI_?f7xmq=Qq4|Ksf zmUP->{3(~T4)}b*Yx3fnt0RlHzmx|4$PEt-J*s%A1ya$WDEC0WF#G_LtKkHI6-U^_ z$24bLR$5kOA=PU0I$~Muu4b=X&QPl3#Pr*|8<2PXxqr0wX8V1fJXfb$1}E2ychB4> zw+o@cw*oc4ZGw<|Tl^0vqRyFnAC<2Pcj3{%?6x;7sk_M>PH9<>Y!mFw=M5+?^><51|*Zt6KJlAZ~|yVmnJyovfMbN!TtER+&2)f@mw+OxaDI^cZ5H+ z+xVdDp%IYc1^Pj1kpz~Kz5U=MPWX&8J&*J!ICoYH5eon&+`L`Rkcm=_{FXJ&u z5N8|ZTB>e9GLwlIa_e;8^mYp=TiH(4do)=e8A`_*(Jsag5F>*ztp4~ODW`HG2rQkV z;geT8x%&(YTAYSENaBougRu@}_7EMuuPlK_f2(0Tuha;n;&3e&`<0wqJb7X4Qbqtn z*CzNqgFNt-irM|LDC1<5()$wKvv4$P1aR^JplE%h9>F^B&k48YMZK8HXfOo2@Dm_i z944M@&gbRI#R!v?f*yqPm^wtM@sk`n7>~d|Y}DN?Ag}^1f%#1uO2Bf`wePMGbkt%@N;~u`J70X0W=pMk`dA!SmbK3NWgFoi22->E7P~6E_J-s8;mf+dfDQfNU~HIq%XWCc0rX-!ZX~!kE2A1fD z)x3K|()xGu>SiK(0&eAdZGZxwYY+TiNbU z#NCF&MP+4yGUI-+Lnu>63l86$)h=3_byiyRW9bJ)iOVUnz5_e8xC$`6TiaQeTXh$UAz-FYEo);diJ&lo~Yv zHe$QUK4I;(dLwU*59u>H{CsTR7R*Mf>E+fbq-9`k-B9C<@4?EN%$azz&vf0WiQ{t( zp#^5zGgOMjs|IhzEQ)uo(;$vLhr)*>7y>Tm+;3xlJJQ~&%vopUosl)4e`WNs7;U46te_a(DgJECDnqFxXDBQjE>9s4t>k80h`zQuD z;V=B_1v+>SN6Ud4oq(zVm$?CAonoJK`)#nvLQ3!!+_^l!>B|g_s`s}fYBRBIt-G?t zwZ>>m(51HGczzu<8Iz=*D%{3)vO_ji z;aS;a&>=Lv`~0auf>Kf@&>l^ce+(bdY&bhObbsRJ(fZGr9{Ae23mBTkfg1~uh~2<) zmnX#fD@mt2|D+OZJ|jp0X7-i}^g1N}?b)Mwi#wDZPmGLe6VM*L-S>re4d7LQpWfoj z30$tn)HqnaY8?Loa%oi}Zg4OypYk;*|1S$r0YZMppIS=xYd?U~2Vcd&VRhCmS#lr- zutEHm9EBf^tr?sIg1}~f)TdVB))|a&@k2rgWD6Wq-K34< z0_U852S_25%~ywphDcCqE$uIdSFVzQq`>0`0q+!ALs&;tlFKYYwe(wf$p85#2%WV(BX9sru<#=1PA+_|-Ci&OL3h zG(jm?zC7lS6+&Kr*DrsRaqo1q<j-n%an)N!_DI>J;Zwv;+3qr@tfRI(5K4esCx&j@T zagVt>@(B+DISMzpmHP7rve;mNdl6JepZ2T6h-`{nS^xRzgUcT-WnZ>OK}BEMZYg<_ zs`X!kK(0UUPULGD@i8m|+Fz)F^33xY-?fqnIDCRB0$m5*oe00Z5K4RyN8$@TmjNce3iFXF`^IZZq?gnm0Xz4rEJrZ2ipB z36%zq;gzlOk(KBK|Nk3?>6@PnRG&01Gq}%qXIozF_ANNL;?YB3<&kA=HG(degDqZ< z#D{hP_9?d!qB(F8N=dmV+u>4!>hvVNe~AGl*ew=HT@3InRK!Gj=3!0?fYRf&$eC=| z>est{;mD@_5$2E0F;`v@R4%$6Tm+)bwDi34>(a^vWh-q&xq-%^T)aIvt~~xebIAv@ zbZWQpG>GNb=YMf$DGZ{`hT-_k5Xd)GGG^&LxAa(7zAzB{j6{ulG!wQkxs1%rTuMN@ zR+uygMPI|0``5IQzD_pS`&_*wfCX!%>XM=5)dFC$}DGdGBQCn972dys;%GVqBM zsclRlH-PcPocjg>d(?Fg~uJ_(9I%wiy+hV6^s zFWku)Bfo(?AMO!r0};0bYvyJGNI-k8d@*(_z<=KI*#->g;pwt3%Rr+>n)DSSCi0y& z)Ri0NfDxW~J4u@JC6Hn!W@a{1%}%d=HL}w!#fbRdJQ)P?hwVQ+eADHodyvncB8d^j zaRBk&Gz(CU1j+gpHTOv_JN;6VBsYQhX?&(RX^J2E=|LMMS;lQETjS zV-OqSYz7)1C(48$@l&Y3t^bzlV0Q5E3CdBQh{h^Y1yK&WWNr-DFkpY>2o6)t&+bKxb zOP~fbi*?0{h!iA-FR`HHob|dqG4ki7v@VvsfbA?STknsfGGKl z8zRgC6^%nG&8N28*SeSZd@ILTVoO9e^QDXtPE1Lr4w*a^3FS=?sJ~ z2>Tl9vwHqvcW>J^(R>3_AG#$8ma=Ep5f&+XXVz#kK{l^o;Pqt{)M4od4jpSq0*-UN zNCXxGnsPS4PJP~=6k#>>-eQ}4q&|Qg6Hvo+0O}HhgDf~2uyuiUqJXIW^85rNCWx89 z4lbjRbZPgRdK^YaXd4^_L_Go7M}yPTrcQT3l$`CjltezwenF!|C65K&-4Q~fNq=h6 zbhtSXww3@NiU&1+n?Ah3s z^7&dNf^!Gdei|(KV#1h=|K1W?VB-I&kXYcAJrH>975w~PR{&OE;v=q6GD&c+A> z?l2G)O|!3lI9MHC6(rZ$2sPKZE&Re$EF>WV4l|)D|5mQ!7L&eoMyC$Y9e#HdYOV(q zOg7kz93@y1a3QE*E`fEj-|@frt*hisT(_6?8lpim|+ zrwc$9lFjP2YXc7^eBT640p9O^%KYEEY%3#u=?{3M|MUa8w%MHoEjZ=%yAsKd!obNr z0;)o(lYdEy2C}AL5*oo}qDnwWl%t&^0%LyzCgB5mG(G^+s?g8z14PvjUHF!w=k*yH z-;xxK!T~?ey5b1DY(9?WWc1paIT-vkLykR4zWzkp0`kT*aN`Nx$}$)MT8v7rc00hf zdXET%w162-s89=~4+ITJN`wT0*4MjhG$3`&74+Ps$xfv5iNIMFF^n^Ti-1g$Fqgo~ zU=5&coCh;?r+_s<9e7Ixds|9yyCqS@8cbN>L?WSsH% z(F4Fc8F>|n+*K339FBYio`RJD3qP3qCb%*))75{Oh)&Q4PS&oZN|5Hfo;;j? zfOSyJ=J~;pD3YK5tx$efwI?jApmI2u@;hec%{@xU349EZ?@b8H?&0G zvI)>i%?b(%IA|lP#;ZTg;H_R>Gi?A$=?z>1KKN(XsKB=<$=)P_kqosWiu=!@$soy{ z?BwHm-7I-nZ0-W+hq~9t1dV+zSo56(UI#F5AUdTgFzsxc zpsMS^Vg)31|0nP*PZJ+2&^030@-qd_4kLg6Hbo%K9>aOwpTOZ@oS1$u6muk8+d>IG zOtpad$0N3MRi*$XJL7As-XL93uVtRQUltx&56~xfOe=ETO4#R~2_fn3j zLodz%AB=88iN_OCtuO>`)C)1PP0EBR_*WyYZ#`a9StK&YvmQKp75k*+L;o?0Rw_@f z8q9(AlbJjUeQf|H+;6*-gjivPrK`a?$h9&N^JpiQXmB_DiCTf*PYvmyD(P(~v_;a_ zH~2B!MTSg+Vi2LtotLZv=yGkKkwOeJ{xJZY8OqAaVvJI$?3ToZM+7imTZ66dl3fIS z8KogpBn72RX!KpV>y_*O_e-P=6sBP5(ggv^f#z4V)kOekO1}fiGwgbbU%%K2ll`B> zKH@r-ik+lF}8i4_>oyZP6T8+d72s+5}1u}I7$0m(Is{Qv$4v86s` zH%itX6HLTB1HTgnelnEN;D0|1SnCGr@DTC@mdjf0wG6;Z4gK$p|9!RPeXJjP9P}S= z0VSUeTqF*;OsaL8OqW9I+M=zSe2|k25I*Q()qgMzYQVt%BI>_l#Bkb@VKB4+DyKy`t69msz-m`t&U15|h*X6%c8*#TSouZyER80c|VZMV$D` zEdWwri&;&pZzVN&$oD)3y#SD{g7s7gVF3SRY2)FJ5 zX~0$nSXuqa`>#}NO{K;J$Oq#Q8$dZnPe(U7ettD^ws$NU&2sfI8|kyU`uVfXt17bh zJ)@*A-}YK~+{d~j|B}#+IJSkKl@xnyV+rD))fHvON}M8^V`nzTBDc69Iu~L7nI2!< zJTSb9jn@bT9HtKXkfo;=b5*kPPqw}KGmiFI)q-SBj|ZM=_~?3h>G~Yh5Q{SjQogw&)cwA>A)OE{HDv@pG5I zBAcbJg2zBE^W4<*@`7%B8#mMiHvSo)U0wh+60F1<7#`N4B97F!8OkDbd|$dxgGJcY za)|`!VNSgPbgZI9%_rB!;<1YZ3|8_<=byG)5SO%;K3mf?8R1XwlIiYVw z^Xn;G3SITkihjasKhcUB+Hl|K!v%D@*YbI|X&C=lOnIPIS+PBd&HITo@w)Vsr^CiH z1aCx@$KSeQ%RSG$X>0mn{nX>)0=SJa7TJ%VeALkz{#uVW`z=`m*;UyK2lGLmiS+#& zM5Nt7mU;)SljTw`e1Cdxu}9B(&jfFFdr|#t?6_WKw-a1Si)=-Xz7(O?L|Aq}0J5P*Q zlm7DbR)+ruVo8qbgA@eA935xE6T0_ZUC%c#SwW|VMkz<2$4}E~ssHTM-4!eI%yp4z zhOIU*1L5t#5QW%En^tykAOP$K6WA#SP`@@)bL88p4NEB|kkt5#gSxxXX5x@&*Y_Oa zv;+bIGpW=gZ%qo~cYsFiTK^Hdr#Y3KB% zr#Jo14XZaJ2%RT4^D7x`^!tNh!%7-}q-Xgh640~H#!x}RWI>7HrsZFp&prYMW&Hf4 zKYPE&dN^-zClP`3Mb3sT6&V|w^YDr3*%Q$5F2VA>2oc%_(l)p1J>YCc2QEtrb}EiX zyUCs(2?Uvxl?M?B)2@MEJbb68BZQk%926PWz9CgkvOt8CURdc7`|o?=u5VySN-I6g zKNVrgX9F6i9f0aeO=*2O!#%UIwZ$;CKIQ+W$$3+H;0xkKWF&5Bmq>3?BXMyQ=h&Y= zx?E(wCcI;&*B30#h*f7*SgeA7&LS(vmU1~r-?_hfbt4|DSk5of z!5D@!RoOHt&HMVHe240C%xy6U@xMIbF$g?|^h0;atiE*Ol|qAat?DUrW#z6Bw(PT4 z)*m~!_mJde_T#H#-tVwx1$OK|zfdBUWRPAhOe!W^{9%;pm0fJ_$~5oGKcU!$DHvRt z2sf~zA@hPah|m#N0*A8Wgu>qlPSV|OVC^<9_v6vbtjGS!Ao#{2!;zINa~o^|)hzwn z50og2_MI%IGaBlPVWl_*X-$U<&nWi4caqKl-NOKXmlUQXojf-fVbL%RR?P>LOL8K2 zoo|MIVCxV)9JPv*emE?wn#wy!y34K1czg&XTz##d`BHEBy8q6)B9)hlLf9~@5yR$5 z*q^@NF-GCg*qU;TZG6An=mLF>m*TMJ-&dAiUyge?btfA;$pPoV9(03pHKUzfhLyVL zOf9+p3t-pi7&Y6#O&-+{iCFgC;O}i-$&Yb}>Ec6xq?nA1Q4ZeEK@Lk@sSA!ne*rTl z*p|J^o4;r1uN%TQ|FyiQkB`qxmfbRoRVV{k^X1_CQ(u23eNpgtioGYG@OL$~-O}m} zX&iHYHpi4~!>3Oh**_RKV6j6Yn{O+wy&tJKuumTb( zk0mre%lsp9nVk=yVD%N!IL{FPY;~QzcK%_J56ttg_DjZMxM z$GVFCmt$hQJUkhbacW3zE1ma#a`yy$wu~*8&e+Z@UboJ<+WNDT;v3(mHT#Pf{^Q*k z15omlbgiIN>jc3(GUXM0bOZa+M} zw!uwCDiby)f~csd=>3*DC<9@%%Rqo7j=aDei`SYBh1@tT-0ii0z$k*cfL6FN(P^6pSz&?CT#@i^^-4Wk zY7AWv$ls+mkzRa}u`w4`@UT)40W6-Vu(ls{X*%dvoT*VuqT5Odcy_%$+3HCB-(!tEC2ZL&eb zn{uH+_6w;tS>;eHOS@)Ib)Rl$|CM5hGia>ECSno3Y{9R_C9a)*WM?a?=Andp*_`fp zc1iE)V?A-$?Dx{g#+l*tKqo~8uu>F^`!2+&mM+hymC_LDRMU+jsW9+`^%o)p{hbtgr0+3v z>NWCfz;CmTT$WLJVgfF{fn1U}4NH|ZKTBvN$Nk38=1NT3>>qkhn1jwh*6o0sISbEP zSV7ctGyNnB_f(bLuIPMKb2Xw7pwZdD6W*f$kK^8Xu?N+8Aphx`CS)?oKA+X76Vor` z{YywBTBdCUtDe+ifp>vV_+ zd6WqCi?rkYS9dt$>xGwe$g&X_y8Y^-${72mT6CZ*~ zI+5gqZ+Urhz^|I&j=V7%eO0{Y0)ohQLd>k(n_@%I><-8=eL#p}pbWFD(*I_C`jXj6 z{W-x6?2(ef5Md8JU?f*?vZ4yULY(3kjW86Tk@~4%GHzE-H|4ymX*Z&Z8aAUHGxp4r zjnwZ>1I|B+ZehXGe#^RDwx0z(EPsUEtUNre`Fh3mdS~B3$KHOGiu-60IR?71N6NTY zal#&H|D;xr{tXGd-R?2YuwHt2Ho6yhtiK3i@&O&IXGbhO8GKUX5$>6j9H7hM26Vf0 zt!aqf``q`A=w-~K!I^qH#-nEHOyY zgG!spvh*j$4Lw@3d8-aIlNWTO>H>PR0|g=WJF*Sp0AKH^TT|gtVMELFFjMI zYMZm^Fg_!_m4Mj2KEKHpCzvOBGuoCm?UP5azUw*Ga?|k-3D3>kX(p{ci}qb|_fga5 z&;ObddZ>(evC%F;)=RX%G)zpg;)x{vwSvUyrnxb>!gyD#0KE)ylg_LSseR3`56H{) z1R{}A8nk>sGkZTdTgB;Dr^`?fZlwHS(|4!PEl9n`T1Uls0Pqk+g?nrpO(v<$+JE53 zj|uo{j2w)_blTm&`&59mT*X(qQC*HLdUG^rK;>bw`z-i9yo5q}ttpv#A?YGyaDaK)lp~ZvDugd|^Zx_c& zk&)Vc5-{9rifRy+A17ruRX^0z-F^56nGNLNd5Ij+juG?c3T-b%pIyig0R4WSVw-~Q zbd|FaTMxj?>Wpv$Ny!9392vLUGUJURxRk>ZbGUqPC+6K73zf0Y!Ty7Jz)9tSe>qp} zc<9>n>hRo4-#XUW{kw0frg+BE+yISRsz4)<<;{r#yylc40uQT>)bc9Orv$WtiCBBo zpq3H;nLIN2K(i&b36v}U&OAA=X_IKmxjO3EA|&s^UVG9#l2)Jr)FOLrrsA|>!+QG=#Xahl-!nyR5bEe$pQPs@xcul6g=n_9VGtz1qNdJ%YOuBwLr^Q+Uj6UeP}$ z@FFZYEpkA}^MXD<+uKzQFgt*^IkKK6s~2fE-_N}&iCyL(76S;CNS77sdv>@@%K(i^ycskrp2wVujd@-k-K8=+rL>!-k8FJ z^WFnI87I5EPgtQCR?7JYtpd*p!z{MJS7iO@8{Yiw$sH#d2->rMpBPRxKlUIs&}9Wc zmy$4s@)h&xMb$C-l+;upbiJco%ecCj{tBUEEWYd*$z4e$%Dg8J>eMIDaiV4DmV=8R zCMZ$zW#>DwX>ke&h-H#UwThiK7Uo`lFO6?dXj6?WBhuup89ie-^&~l#G?do}ykMDdzX^+AJKb9cBw6y~-yxYWBleEGbs#I?#!PXw3Zx2SBDpDK!SJ); zM6=vhipRq_KtfYo32udzVc|;J87$~YX66Y&mxSLz+?)n7w*$3{fKHc@E&LD6if4~L zw_bei%oi*^{3qIdQPz&k8rbp8{Ctm+bh9o3pV7OiqCZ*3k-3>}uhKErLJ$MR^&SC~ zbMi?RG$Sg-q}%jnMyjeYWp@{VLMhnLd*Si$SJf<88}&jhhEek$g75O1&_V2y{qHyLhPCK;A+vDNZD$Z$1GSnGBe;IKp)a$Vpc-;sW$? z%&Jp{_BwrgVEDmZrJ#V064hI&yBufQJ#CwgzXC{1gwusaP6OK*gUop{B)Q25s!9bTGfj5|3Vg83ZUW#|vq7 zTWMhRqezkK11y|MZmc52{e}vakM{K%KR`kE@j`nj&CXh}@aW=SVj5eCAMz4GBq}m8 zBgivqWvO~j8o~-jLO*KVAp31)@1(6Kp_77~r{kf(G%y^YOD>xG;O@JvXD6Wr{H`jo z+tt9DE|VpEyA9o3URtVr($mpWh|}T$F$LKXk@aVayIGp>-l-=iE7G0@45$K!4)5*h z3ttp(lGg+j*Xw)znMF8cg!KVOdV=oW3_>47K|$aQax9~KK-((u6C!sS0(&HrlXV`sPA z+Vt9k1yl-tpCxZWaw7+{46BQm*X1(^m*Lh!%2?(`ii(PMRXgQ%b&~4vN}QH40;?u> z(A|mx#?CW&noqi~fC>0H=SUMUDK)Lo-tFWn_jcu*0fZ(JFvHis^xDID!T&V6l0Sh$ z9QBCugbhILR}Y@{w)jKhs(*2PUp<-|u(rb}prrNgd7=aPVcG1vE2Z)Xp{PS|FhG-L zPp||rEYMDRn+H*Q{=zPP1sR;PDaZpcS-(6Lr~{ z84+v@7^AZ9(cW8m{BD=4EZ<4UxiS$C1TgH%%RSAJrse>`Cm?!u8Dj-F?D=(^jTd*X=7692%zPW`iT@G`fQ>R8B&% z%wHOuvOu#j@1@26l^O9zeLcK6ir~ zS0HQ{>u~B(2C)p$UwLLnIx&ht_Aa|PX8=H*PWH;`K6^I#1Rg>s=35PI;H&1EmbIgx9soZDWY*|8dgt#29A~R~`5nR=f zTRTney@*`~VY61WN)s(CTTe@Cx8GAT$f@Z}9Mn-Ey7$YK;Mq5D;Qx>$QtxRC8^a1M zZryndug>r+8ZisGcs0ws@&cf6yc5|*@ts{s92tN{6lYoGE5*=Ep@g53oP0ij+FS?q zG6R(SlI8{!TCrXUQrdjH^LheT|<~6Z^cJ{O;Cq@~KyrdA^@?mjM2$PiXsI)Ji%qXXpnN+C*UANukQ( z;^MINFldJ#+d{|i>QxGJN@ft5EvJ{fo`U_Nxye92o`f*y~Y_6~u24q;-(MSJvY zh5a}qVg+U!%M*J6z|0xox*Tw_k#l>tUAL~Hd9}R;#40n}B_)!;SrSi^kXrnTgN`2yL(Vd_88Pg_vY?uw1;Vw=68rJS zzOC)guvUR*Ww^(3=3964#?F@Oxw*L|kp_|rzj!|D_Ef7Yz>9MG%P3@W4hQ{}gHE5L zJY8&KxdM-FyxjVx(ix~$B?Pi9JEsX5lRLEy)Upclh_k4bDijJ;GI9Yj;K!kpQ}fe% z7$+A%gGhTXv7Dr8+f+{TZ-ee>*278mX3N@kSJh6Q;+A2s=?Uc>4OqfU`rL1I%alkoOP{^=n`ef`R7y)-4k31@D>mxN~T zcQI{w!jo%5F4HTsr**)>D+2$`MxTz0H|MK4^gPIY5u$-!){@TGP@DSBHGo|_1Y7L` zuYnkFJe^??whz6iO>eV@d(^q%9y!LMa*x|I)zsc`tk&|WAIkt5P{?3f#Rt}md2WxP zgSTP9>M312=C)gFYldcMLSRQ=vK+q>M2*=`1a-0xD4ML}&I%0;%YpslqFbEUEe7rT z$|F8a{3dmt7EOyC=m|C(uXtA+sc%@r){8pNpQk1PRHl~U{iOsY!GqJDH!%=Bl>6z^ zry$^iv`jYG`*D>J@x7UJ{=o#Q^5A25U*n+h(C}?+@F>KvGD_344&6hJkqqFC%mFN2 z>|scXG!+JCe?DFiHVgjBzc-#McNpAXgyv3St z7NA=>bgkLuhW}{62TcLqlNFIpY{@n>5y7mr%p$X#-BR0T%}kRC8yGnSe}Dgvfqctp zh?NiMz}o}~#2T*ZeWcf_nTP}S~ zLvd2na+d!EKC2h;{3iW4p8c5^U#UwH=y-I2uY#7lflf9!P#fRv0h!59v~;v+H5|VR ztY?h>#^d((_IM~&%fv2h)`WNGQxLp4*V&iS0hs}gJR;)` zpRq5Uuq=;#TrjW~<6?QIhjtoAUGSJW8kaxh;N0;OhC)5G|8Slds>`{7^R? z?&#d#n3FHI_g~C(^%&BW-Txl59&uOWfm*<%Nbl;$S0pe8c|<*8#k(dFu5SO}4^xO6 zC*`RKV}pLJ`(jzO-0PtI)!5yI4vyPw!nVP`Nd`QM|EM!H{93s5!zQ-}?2E|;yNN?* z7A<~2&n!qUmd>>w$H~<<)U^&^0xmQmR~JY9)=OrBU&seYVZlK^U*8HjTw%Sqsc1t- zC=jN|pvEfOC{2?5gq0ZH=9U=ey2VDa>V(V4S@H58v?9fG)=+Fe@>U>{xg9+tOlb9{ z^8Q+(`_Eo z{*j*7IbtsGy{Ww1SyEVb$@22j6O2Q%H)iTOAq+=EmNq15%qnu22N-BA(F1PX(H(75 zAe&ZxpiaYAsO_Tl3pcbQ!?Tv@`%$qR)8`0e4d>dt5GVs)X{Z$Tq?UYk4yYovWP_yM zA}nsQNSrNYxHi?HP>0iWDeE`YhJ%ZDx^>>*vRr?xVjbgQ{Dg}k?&>}z0sUOrhZ<{| z2$dZvBw{VIIgO`b8E@rgtU}OOU|$3))~tO`^`#goeuM0@(=3&u*xz0zOEO+H#r8*( zxvE2_u-C_2WSb_cMVjBp$Xe%i2qE9;McgoM)KAAB<}Vvvg&IBpWI7udUiruK`lY^l zzQ`hX)Gcw8C@Cq~t*WZp%tw2C0h(yK2;X}!f;SR-Rz20GLtZAU1FbiA=I???+4;pJ z1dFV>bf^0QK;|ZIlI_8Y!IWPE&HhfX#ZggF@g@Zx#?O~HmxD4q365WnjsMf_Z9Rd%h zb{@SasM*fn*(hOV@D&rkM=l6G!GW;k?m-*ap(f~tR|uI&z~=|7#gC(! z_i`%MAKzwnNtsokfc`y@GpqM5$AWaIHrfqbnifV`7on-@PdeaPQhdDbPBd_o4Yuwo zO#vnxJoS&Ry9jmfkT1oJ+15kW>Vm%&+c#G^*WZG&HE#{QAr%Xk*QNXLb$l#Zs_$&F z+Bu+{RK7?fJj|*M9S55)Wf{dqql>U$hMVWL+KST#FEY~fGT!@@cQ0X)5lM<-!Av>A zbNkV*SaMo7R=S782(2W@f)v=^-6ZP>Iw&)fcKP1_bh&SDo-Si|cmng7gR%ubxgRvZ z6Z_Y~5>)Q5{?lYw0WQw50CJ_2r(=2jp8fTEzSrV<_->`7^N)dFs~yd+2?arMeo@Ku zoz^(5E=r!SYUJ|gnuB@cAVI%s;2&$iWtW(F^vyZo0VJoatQ_Ms#(m47q@p601dqWn z{gB%BvTELu!^hiO#kH@Fvm6z$+?N%yXV=28wy|N{iQXlXJU@^aCk2dbV;pGU&z}Qk z;Q+6JFMmhSd5OX(?WfF_rBWjxyaEx z;lM@9AC58TyQatSF(`M6yu^W(8w6@e$>3o7Z|8U`wUW!vL2GVXv_ZwgOf2*GJjU#N zp4c{6@5j;apL07mkai}i&(9WKjzPO&nO{#8S$^aGlN`AVx;;}k&}_2e5C)ta+r@(in5&K0`re{Z0MD7LoL+xS?srO-*0p z*#woyOEVpWJ*b6Uw=iGS<~%r#>G)|%lil_;Bm(yWiG@9MWDn)*g1=O(523n1y;n4^ z&rnuUva+L?Zf4lv>t+>O+E{kFlCqsiBE{Z+p#dJwZ=il+BzSW-aPx{6SaWUY+PW~< zFNmrM>wSuB7!9?&5w*`1hgGLROC6`kH-iCB8a$dUDR^P>VCpF83G1_=1&*3;-xPx? zsNogdTw6_z7%mO^ssRY$Pux3B3o24LK<<2~NY z7SNqX^k;e+r7lK!xBy{7rmALD%JX~Op5yi(#xz;wjNY(*n6ymO6K#osvXHf9WsX^u zDPX85Akfv4?MHVDY@nQ**F5-L3*BLdwWmqMFEnBbTgyT1^b})Y>Pfk8Fsma9iUwgy zx%|7QJT|{`h<1O9GS8DxMPmG!NOy3){!Cb>5V5^Pua4y_#>i-OwbA6N0p;cq~ztxeh;bpg1_y_=R-SLE>F zwcblt=BVMrb>9{eH~#UYf?-x~NWCV6q*37C@AK56&;5cqtaM%}+6EWX+uxjx+EX>` zf#sYb>bH&kw>*D;8CI-K_u6nkXteX}l1MPivPu)Bf7ya{E9ALdF>u`!hcb&e*XuJw z(I0|2N%22{tU+mZV5Igdeb=8dy^vzMYrlqH=!bX(vNBY!`13XS%3THGqPs^V5^2_9 zy%z_8xf>w0zvGDoJh&8V%)!1&tJPjQ$lCS4My}3uvm-<5N2Ip~{AdCnv&oU1u!oGU zy-EwxMAl!pjF%gJTVomPWr6>G{O)eb0l_=cdCB$%B^G1VHX-+MZz{Brw>Q;LhVzxC zO1R(474|rS*3~OGUbJUQ=fM;<3N*4$bNE!syf&sA&jdqOJG(zT#{70+9!U%vg&=)d z=W!9o*JDsl^^K|W#QyB9$=+}<^1@Z3YotRGyO-wt8_EDbV%$( zV*SU;yL)1|LZdNHzxEcGb5_@gATb|QcGBD$fxt`=ikAVpk6`Dpg%`>X9mcxrB#Qp< zX&UQnxow3JbKf_phAXu7Jj(WdsL6imF(rJ@dGvE$ z7Sj3V#h4uqNPB;U${$d}w5zcsZmi&(q%ki2EVIBRAN#oTX>fFO^vtk;m+#r2wd-mu zh2V4fA8|lh^$_vm9+b>7&q;+B1UATE;SPETY7p{4?y+R}un6WBX^o-Muh>kW2quR{ zZ$Obu^Y#QYejN+JIdbqCm6Z`&(0|$Clru~DadN7OUu(pS_#OgFcuz_kSwmTsUV#%@ zz}kVCfr|{gy#=jT-#0h68Uu;sZkjNXsa~Hi=t5-8>bRbK(ivQjqTmRYgKuNEa6;mb z=X7|gJFB&-NzP0>2NkbghAkA3Rdw?$+XH!WYBa>-vDr<`_o%3d84BevEm7h-h_t9stg}B z@g(0F|5GNWmL>V19N)Co=iQbD1CPz}lBP!=4-Zd0;sp~k#WCa9L z`q`_*E`EL|irhti=8C$dDH(-q;2I4!KmWzaHz zc<}^ku_vHS>4L-Zo)r&Ne>zAnu0@tP;vQe6MBicwa_;FdU)7>NUqX_AX22 za4tkh8Z#Hk7%c9!_WJ-KuAEOY44J1}G7gnK<3&9?-c4g{HBw>gNoGcWP>zrUv`t@e6zi;fnqpQ@tIpD-Nc z3nHJIX7&q$of<_-3I}d6v!5~hHKW!ZqBA=S_|EGA5@dVZ1-Mtj%zW9<=L^@H=JsUj zcA*TCxZW5qsu03rRawdLVy)?|ml$BD#ZNUGz?Ks2=<{W%!^I>xY}YxhveIL>F*wTu zZ+4R<&FXV{Muse(EfNIa3&X1USMH=A>Q>HZ&zkhpD3-k9EZ1DoM z=>>8vfH*Z-C=u*#xHW<+?#u2nvh)j#!tu+&BJhtmnMr9hQ9cvqp_d7Nx`Ux5aH#kS zSc9%Y7z#g{jaE=w9lHxohTkKsTTC^&>_U%eAA}M96VbgY;p63HqtL5c`%!CO&dmtrR2z-l-n;Ah21yO{lu1A)iV8dgb|T!kP!A<` z9)GyTMQ@T%)QS{sKv4-^Ib1|<`0I@F;Q}LQ`2R$2`;A7jl3i#5il9{X7h2~>umt@g z&TulLlUK;J^kNB<5wB9UO;G7f11N(E9caejq!$DhF9U7u>Q)(}&P=FkgnqdXaHsyB9s@ukpklg_e z@?`7q@X*5@-(`d+*h7z%eD$cJJ}Ic-myK)$ypaG{}t;I`X6 zs3rJpbL9T(?|?1mfjs$Rd`HR__1>=v04%Rb_1^v?XCz+f@h~{HIojWE&_Z0kNcVos zd*|U>gp1}4srAa4E7trnKv9n!^166qneu8rN?!rxpAcPtp+hhCEx!mfZIVG0oGkImzw5QeWwhZaDEhJIN00>nQT=vWsheVnf) z3iO-5h!|_P=4w*o3+t+`X?-UrH&K2Ieya`<kq z`7ui9I5dBu-Feou*pI$Kf9`AZ_&nqGSP9hXV3#St19MKH=4gnp;Jq1I1bk+xK+iFi zbl$qdn?Cu6_cuw@x)scDd~k3#Rj*{S*uDb)1~zw~VMe=pLtgdLe&XU6h4wk`rLzTS z$M=5}Y++KjIx8Os$Xpf@$dt%gE7B-${F_??bb)E53yyybL#hF9y1^6Eg^z-&8#GTvB+{XJ0f23CTIHF}fE(_*)K%NVUY^qCu_ubWT zi&@=mE0gg?z(niOzX7%?`m;XOj1#)S ctbcZeXM-TPdK2<^@0#YXtf&sJkT(zeKbOKZ1ONa4 literal 0 HcmV?d00001 diff --git a/StarterNG/Assets/Logo/maszyna_white.svg b/StarterNG/Assets/Logo/maszyna_white.svg deleted file mode 100644 index 24e611b..0000000 --- a/StarterNG/Assets/Logo/maszyna_white.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - diff --git a/StarterNG/Classes/Scenery.cs b/StarterNG/Classes/Scenery.cs index 177bd8a..e148f88 100644 --- a/StarterNG/Classes/Scenery.cs +++ b/StarterNG/Classes/Scenery.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; @@ -12,10 +12,20 @@ public class Scenery public string Group; public string Path; + // Starter directives (comment-syntax metadata, see wiki "Plik scenerii"). + // They do not affect the simulation, only how a starter presents the scenery. + public string Name; // //$n - scenery name + public string Description; // //$d - scenery description + public string ImageName; // //$i - main-window image (scenery thumbnail) + // The file content with each trainset block replaced by a {{i}} placeholder, // used to rebuild the .scn on export. private readonly string _template; + // Lazily resolved, cached path to the //$i image on disk (null if not found). + private string _imagePath; + private bool _imagePathResolved; + public Scenery(string path) { this.Path = path; @@ -25,13 +35,13 @@ public class Scenery var encoding = Encoding.GetEncoding(1250); // Windows-1250 string content = File.ReadAllText(path, encoding); - // property scanning - var match = Regex.Match( - content, - @"^//\$l\s*([^\r\n]*)", - RegexOptions.Multiline - ); - this.Group = match.Success ? match.Groups[1].Value : null; + // property scanning - starter directives written as // comments. + // \b after the letter keeps //$d from matching //$decor, //$i from + // matching //$it, etc. + this.Group = MatchDirective(content, "l"); + this.Name = MatchDirective(content, "n"); + this.Description = MatchDirective(content, "d"); + this.ImageName = MatchDirective(content, "i"); // parsing trainsets @@ -65,4 +75,64 @@ public class Scenery result = result.Replace("{{" + i + "}}", Trainsets[i].ToSceneryEntry()); return result; } -} \ No newline at end of file + + /// + /// Reads a single starter directive value (the text after //$<letter>). + /// Returns null when the directive is absent. The trailing whitespace + /// requirement separates e.g. //$d from //$decor and //$i from //$it. + /// + private static string MatchDirective(string content, string letter) + { + var match = Regex.Match( + content, + @"^//\$" + letter + @"\b[ \t]*([^\r\n]*)", + RegexOptions.Multiline + ); + return match.Success ? match.Groups[1].Value.Trim() : null; + } + + /// + /// Resolved on-disk path of the //$i scenery image, or null if none is + /// declared or the file cannot be found. The value of //$i may be a bare + /// file name or a path; common locations are probed and the result cached. + /// + public string ImagePath + { + get + { + if (_imagePathResolved) + return _imagePath; + _imagePathResolved = true; + _imagePath = ResolveImagePath(); + return _imagePath; + } + } + + private string ResolveImagePath() + { + if (string.IsNullOrWhiteSpace(ImageName)) + return null; + + // normalise legacy back-slashes so paths work cross-platform + string name = ImageName.Replace('\\', '/').Trim(); + string scnDir = System.IO.Path.GetDirectoryName(Path) ?? "."; // e.g. scenery/ + string root = System.IO.Path.GetDirectoryName(scnDir) ?? "."; // MaSzyna root + + // probe the usual places, first hit wins + var candidates = new List + { + name, // as given (cwd / absolute) + System.IO.Path.Combine(root, name), // relative to MaSzyna root + System.IO.Path.Combine(scnDir, name), // next to the .scn + System.IO.Path.Combine(scnDir, "images", name), // scenery/images/ + System.IO.Path.Combine(root, "scenery", "images", name), // scenery/images/ from root + }; + + foreach (string candidate in candidates) + { + if (File.Exists(candidate)) + return candidate; + } + return null; + } +} diff --git a/StarterNG/Classes/VehicleDatabase.cs b/StarterNG/Classes/VehicleDatabase.cs index be9845e..0cdf436 100644 --- a/StarterNG/Classes/VehicleDatabase.cs +++ b/StarterNG/Classes/VehicleDatabase.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace StarterNG.Classes; @@ -14,43 +16,43 @@ namespace StarterNG.Classes; /// Root object of a single vehicle JSON file. public class VehicleEntry { - [JsonProperty("uuid")] public string? Uuid { get; set; } - [JsonProperty("schema_version")] public int SchemaVersion { get; set; } - [JsonProperty("groups")] public List Groups { get; set; } = new(); - [JsonProperty("textures")] public List Textures { get; set; } = new(); - [JsonProperty("sets")] public List Sets { get; set; } = new(); - [JsonProperty("unknown")] public List Unknown { get; set; } = new(); + [JsonPropertyName("uuid")] public string? Uuid { get; set; } + [JsonPropertyName("schema_version")] public int SchemaVersion { get; set; } + [JsonPropertyName("groups")] public List Groups { get; set; } = new(); + [JsonPropertyName("textures")] public List Textures { get; set; } = new(); + [JsonPropertyName("sets")] public List Sets { get; set; } = new(); + [JsonPropertyName("unknown")] public List Unknown { get; set; } = new(); } /// Merged database root (databases/vehicles/vehicles.json). public class VehicleEntryCollection { - [JsonProperty("vehicles")] public List Vehicles { get; set; } = new(); + [JsonPropertyName("vehicles")] public List Vehicles { get; set; } = new(); } /// Legacy header / vehicle category group. public class VehicleGroup { - [JsonProperty("id")] public string Id { get; set; } = ""; - [JsonProperty("category")] public string? Category { get; set; } - [JsonProperty("mini")] public string? Mini { get; set; } - [JsonProperty("archived")] public bool Archived { get; set; } - [JsonProperty("implicit")] public bool Implicit { get; set; } + [JsonPropertyName("id")] public string Id { get; set; } = ""; + [JsonPropertyName("category")] public string? Category { get; set; } + [JsonPropertyName("mini")] public string? Mini { get; set; } + [JsonPropertyName("archived")] public bool Archived { get; set; } + [JsonPropertyName("implicit")] public bool Implicit { get; set; } } /// A single .mat skin entry. public class VehicleTexture { - [JsonProperty("uuid")] public string? Uuid { get; set; } - [JsonProperty("directory")] public string Directory { get; set; } = ""; - [JsonProperty("skinfile")] public string Skinfile { get; set; } = ""; - [JsonProperty("model")] public string? Model { get; set; } - [JsonProperty("group")] public string? Group { get; set; } - [JsonProperty("mini_ref")] public string? MiniRef { get; set; } - [JsonProperty("texture_mini")] public string? TextureMini { get; set; } - [JsonProperty("wreck")] public bool Wreck { get; set; } - [JsonProperty("aliases")] public List Aliases { get; set; } = new(); - [JsonProperty("meta")] public VehicleMeta? Meta { get; set; } + [JsonPropertyName("uuid")] public string? Uuid { get; set; } + [JsonPropertyName("directory")] public string Directory { get; set; } = ""; + [JsonPropertyName("skinfile")] public string Skinfile { get; set; } = ""; + [JsonPropertyName("model")] public string? Model { get; set; } + [JsonPropertyName("group")] public string? Group { get; set; } + [JsonPropertyName("mini_ref")] public string? MiniRef { get; set; } + [JsonPropertyName("texture_mini")] public string? TextureMini { get; set; } + [JsonPropertyName("wreck")] public bool Wreck { get; set; } + [JsonPropertyName("aliases")] public List Aliases { get; set; } = new(); + [JsonPropertyName("meta")] public VehicleMeta? Meta { get; set; } /// Full skin path = directory + skinfile. [JsonIgnore] public string FullPath => Directory + Skinfile; @@ -69,25 +71,25 @@ public class VehicleTexture /// Alternate mapping from a malformed multi-= legacy line. public class VehicleAlias { - [JsonProperty("model")] public string? Model { get; set; } - [JsonProperty("group")] public string? Group { get; set; } - [JsonProperty("mini_ref")] public string? MiniRef { get; set; } - [JsonProperty("texture_mini")] public string? TextureMini { get; set; } + [JsonPropertyName("model")] public string? Model { get; set; } + [JsonPropertyName("group")] public string? Group { get; set; } + [JsonPropertyName("mini_ref")] public string? MiniRef { get; set; } + [JsonPropertyName("texture_mini")] public string? TextureMini { get; set; } } /// Parsed metadata from the legacy // comment section. public class VehicleMeta { - [JsonProperty("raw")] public string? Raw { get; set; } - [JsonProperty("version")] public string? Version { get; set; } - [JsonProperty("vehicle")] public string? Vehicle { get; set; } - [JsonProperty("operator")] public string? Operator { get; set; } - [JsonProperty("depot")] public string? Depot { get; set; } - [JsonProperty("revision_date")] public string? RevisionDate { get; set; } - [JsonProperty("revision_place")] public string? RevisionPlace { get; set; } - [JsonProperty("texture_author")] public string? TextureAuthor { get; set; } - [JsonProperty("photo_author")] public string? PhotoAuthor { get; set; } - [JsonProperty("extra")] public List Extra { get; set; } = new(); + [JsonPropertyName("raw")] public string? Raw { get; set; } + [JsonPropertyName("version")] public string? Version { get; set; } + [JsonPropertyName("vehicle")] public string? Vehicle { get; set; } + [JsonPropertyName("operator")] public string? Operator { get; set; } + [JsonPropertyName("depot")] public string? Depot { get; set; } + [JsonPropertyName("revision_date")] public string? RevisionDate { get; set; } + [JsonPropertyName("revision_place")] public string? RevisionPlace { get; set; } + [JsonPropertyName("texture_author")] public string? TextureAuthor { get; set; } + [JsonPropertyName("photo_author")] public string? PhotoAuthor { get; set; } + [JsonPropertyName("extra")] public List Extra { get; set; } = new(); } /// @@ -109,12 +111,16 @@ public class VehicleDatabase /// skinfile (without extension, lower-case) -> texture. public Dictionary TextureBySkin { get; } = new(); - // Newtonsoft.Json matches property names case-insensitively, allows trailing - // commas, and skips // and /* */ comments by default — the same leniency the - // previous System.Text.Json options provided. Unknown members are ignored. - private static readonly JsonSerializerSettings JsonOpts = new() + // System.Text.Json options matching the previous Newtonsoft leniency: property + // names matched case-insensitively, // and /* */ comments skipped, and trailing + // commas allowed. Unknown members are ignored by default. The source-generated + // VehicleJsonContext resolver keeps deserialization AOT/trim-safe (no reflection). + private static readonly JsonSerializerOptions JsonOpts = new() { - MissingMemberHandling = MissingMemberHandling.Ignore + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + TypeInfoResolver = VehicleJsonContext.Default, }; /// @@ -177,8 +183,9 @@ public class VehicleDatabase { try { - var collection = JsonConvert.DeserializeObject( - File.ReadAllText(file), JsonOpts); + var collection = JsonSerializer.Deserialize( + File.ReadAllText(file), + (JsonTypeInfo)JsonOpts.GetTypeInfo(typeof(VehicleEntryCollection))); if (collection?.Vehicles is null) return; foreach (var entry in collection.Vehicles) Ingest(entry); @@ -193,8 +200,9 @@ public class VehicleDatabase { try { - var entry = JsonConvert.DeserializeObject( - File.ReadAllText(file), JsonOpts); + var entry = JsonSerializer.Deserialize( + File.ReadAllText(file), + (JsonTypeInfo)JsonOpts.GetTypeInfo(typeof(VehicleEntry))); if (entry is not null) Ingest(entry); } @@ -359,8 +367,17 @@ public class VehicleDatabase /// Automatic consist definition (legacy ^x grouping). public class VehicleSet { - [JsonProperty("uuid")] public string? Uuid { get; set; } - [JsonProperty("mode")] public string? Mode { get; set; } - [JsonProperty("count")] public int Count { get; set; } - [JsonProperty("texture_refs")] public List TextureRefs { get; set; } = new(); + [JsonPropertyName("uuid")] public string? Uuid { get; set; } + [JsonPropertyName("mode")] public string? Mode { get; set; } + [JsonPropertyName("count")] public int Count { get; set; } + [JsonPropertyName("texture_refs")] public List TextureRefs { get; set; } = new(); +} + +// Source-generated JSON metadata — makes deserialization AOT/trim-safe by avoiding +// runtime reflection over the model types. Nested types (VehicleGroup, VehicleTexture, +// VehicleSet, VehicleAlias, VehicleMeta) are pulled in automatically by the generator. +[JsonSerializable(typeof(VehicleEntry))] +[JsonSerializable(typeof(VehicleEntryCollection))] +internal partial class VehicleJsonContext : JsonSerializerContext +{ } diff --git a/StarterNG/MainWindow.axaml b/StarterNG/MainWindow.axaml index 9ffc393..55e939b 100644 --- a/StarterNG/MainWindow.axaml +++ b/StarterNG/MainWindow.axaml @@ -1,16 +1,16 @@ + Title="MaSzyna - Symulator pojazdów szynowych" Height="800" Width="1200" WindowStartupLocation="CenterScreen"> - - - - \ No newline at end of file + diff --git a/StarterNG/Services/LocalizationService.cs b/StarterNG/Services/LocalizationService.cs index 02839ce..7f67e68 100644 --- a/StarterNG/Services/LocalizationService.cs +++ b/StarterNG/Services/LocalizationService.cs @@ -1,35 +1,166 @@ -using System.ComponentModel; -using Avalonia.Controls; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Xml; namespace StarterNG.Services; +/// +/// Loads UI translations from plain XML files shipped in a lang/ folder next +/// to the executable (instead of dictionaries compiled into the assembly). The files +/// are parsed with , which works under Native AOT — unlike the +/// Avalonia XAML loader, which has no runtime parser in AOT builds. +/// +/// File format (lang/en.xml): +/// +/// <Language code="en" name="English"> +/// <String key="NavScenarios">Scenarios</String> +/// </Language> +/// +/// public class LocalizationService : INotifyPropertyChanged { - private IResourceProvider _currentProvider = new ResourceDictionary(); + /// Folder holding the language files, resolved as starter/lang next to the executable. + public static string LangDirectory => + Path.Combine(AppContext.BaseDirectory, "starter", "lang"); + + private Dictionary _strings = new(); public event PropertyChangedEventHandler? PropertyChanged; + /// Display name of the active language (e.g. "English", "Polski"). + public string CurrentLanguage { get; private set; } = "English"; - public string CurrentLanguage; + /// Short ISO code of the active language (e.g. "en", "pl"). + public string CurrentLangCode { get; private set; } = "en"; - public string this[string key] + /// Indexer used by the XAML bindings: returns the key itself when missing. + public string this[string key] => + _strings.TryGetValue(key, out var value) ? value : key; + + /// + /// Metadata for one discovered language file. + /// + public readonly record struct LanguageInfo(string Code, string Name, string Path); + + /// + /// Scans the lang/ folder and returns the header metadata of every *.xml file + /// without loading all of its strings. Used to populate the language selector. + /// + public static IReadOnlyList AvailableLanguages() { - get + var list = new List(); + if (!Directory.Exists(LangDirectory)) + return list; + + foreach (var path in Directory.EnumerateFiles(LangDirectory, "*.xml")) { - if (_currentProvider.TryGetResource(key, null, out var value)) + try { - return value?.ToString() ?? key; + var (code, name) = ReadHeader(path); + if (!string.IsNullOrEmpty(code)) + list.Add(new LanguageInfo(code, string.IsNullOrEmpty(name) ? code : name, path)); + } + catch + { + // Skip malformed files rather than crashing the launcher. } - return key; } + return list; } - public void SetLanguage(IResourceProvider provider, string langName) + /// + /// Loads the language whose code or display name matches . + /// Falls back to English ("en") when no match is found. Returns the resolved + /// display name so callers can keep their stored setting consistent. + /// + public string Load(string codeOrName) { - _currentProvider = provider; - CurrentLanguage = langName; + var languages = AvailableLanguages(); + + LanguageInfo? match = null; + foreach (var lang in languages) + { + if (string.Equals(lang.Code, codeOrName, StringComparison.OrdinalIgnoreCase) || + string.Equals(lang.Name, codeOrName, StringComparison.OrdinalIgnoreCase)) + { + match = lang; + break; + } + } + + // Fall back to English, then to whatever is available first. + if (match is null) + { + foreach (var lang in languages) + if (string.Equals(lang.Code, "en", StringComparison.OrdinalIgnoreCase)) + { + match = lang; + break; + } + if (match is null && languages.Count > 0) + match = languages[0]; + } + + if (match is { } info) + LoadFile(info.Path, info.Code, info.Name); + else + { + // No files at all: keep keys visible instead of blank UI. + _strings = new Dictionary(); + CurrentLangCode = "en"; + CurrentLanguage = "English"; + } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item")); + return CurrentLanguage; } - -} \ No newline at end of file + private void LoadFile(string path, string code, string name) + { + var dict = new Dictionary(StringComparer.Ordinal); + + var settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true }; + using var reader = XmlReader.Create(path, settings); + + // NOTE: ReadElementContentAsString() already advances the reader past the + // element's end tag onto the *next* node, so we must NOT call Read() again + // in that case - doing so would skip every second entry. We only + // advance explicitly for nodes we don't consume here. + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element && reader.Name == "String") + { + var key = reader.GetAttribute("key"); + var value = reader.ReadElementContentAsString(); // advances the reader + if (!string.IsNullOrEmpty(key)) + dict[key] = value; + } + else + { + reader.Read(); + } + } + + _strings = dict; + CurrentLangCode = code; + CurrentLanguage = name; + } + + private static (string code, string name) ReadHeader(string path) + { + var settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true }; + using var reader = XmlReader.Create(path, settings); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && reader.Name == "Language") + { + return (reader.GetAttribute("code") ?? "", + reader.GetAttribute("name") ?? ""); + } + } + return ("", ""); + } +} diff --git a/StarterNG/StarterNG.csproj b/StarterNG/StarterNG.csproj index c0b1d47..25de13f 100644 --- a/StarterNG/StarterNG.csproj +++ b/StarterNG/StarterNG.csproj @@ -1,6 +1,8 @@  WinExe + true + true net9.0 enable true @@ -8,6 +10,8 @@ true 1.0.0.0 1.0.0.0 + Starter + eu07.pl x64 @@ -17,20 +21,18 @@ - - - - - + + + + - - None - All + + none + all - - - + + @@ -38,15 +40,24 @@ - - - - - - + + + + + + + + PreserveNewest + starter\lang\%(Filename)%(Extension) + + diff --git a/StarterNG/Views/Depot.axaml b/StarterNG/Views/Depot.axaml index 2158b46..7bdfb63 100644 --- a/StarterNG/Views/Depot.axaml +++ b/StarterNG/Views/Depot.axaml @@ -5,6 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:starterNg="clr-namespace:StarterNG" xmlns:views="clr-namespace:StarterNG.Views" + x:CompileBindings="True" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700" x:Class="StarterNG.Views.Depot"> diff --git a/StarterNG/Views/Scenarios.axaml b/StarterNG/Views/Scenarios.axaml index 0df469e..c8ae5ac 100644 --- a/StarterNG/Views/Scenarios.axaml +++ b/StarterNG/Views/Scenarios.axaml @@ -1,15 +1,17 @@ - + x:Class="StarterNG.Views.Scenarios" + x:CompileBindings="True"> + @@ -19,7 +21,7 @@ - + @@ -34,7 +36,20 @@ - + + + + + + + + + + + + + @@ -44,10 +59,10 @@ - \ No newline at end of file + diff --git a/StarterNG/Views/Scenarios.axaml.cs b/StarterNG/Views/Scenarios.axaml.cs index 194b547..adc9d5e 100644 --- a/StarterNG/Views/Scenarios.axaml.cs +++ b/StarterNG/Views/Scenarios.axaml.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -74,6 +74,9 @@ public partial class Scenarios : UserControl Scenery selectedScn = Sceneries[tag]; vehicleList.Items.Clear(); + // scenery-level info (distinct from the per-consist scenario description) + ShowSceneryInfo(selectedScn); + // add all trainsets to list for (int i = 0; i < selectedScn.Trainsets.Count; i++) { @@ -91,6 +94,29 @@ public partial class Scenarios : UserControl } + // Shows the selected scenery's description (//$d) and 1:1 image (//$i). + private void ShowSceneryInfo(Scenery scenery) + { + sceneryDescription.Text = scenery.Description; + + string? imagePath = scenery.ImagePath; + if (imagePath is not null) + { + try + { + sceneryImage.Source = new Bitmap(imagePath); + } + catch + { + sceneryImage.Source = null; // unreadable / unsupported image + } + } + else + { + sceneryImage.Source = null; + } + } + private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) { // get selected scenery @@ -190,4 +216,4 @@ public partial class Scenarios : UserControl // executable not found / not configured yet } } -} \ No newline at end of file +} diff --git a/StarterNG/Views/Settings.axaml b/StarterNG/Views/Settings.axaml index 0327740..e6ae6ef 100644 --- a/StarterNG/Views/Settings.axaml +++ b/StarterNG/Views/Settings.axaml @@ -6,7 +6,8 @@ xmlns:objectModel="clr-namespace:System.Collections.ObjectModel;assembly=System.ObjectModel" xmlns:starterNg="clr-namespace:StarterNG" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="StarterNG.Views.Settings"> + x:Class="StarterNG.Views.Settings" + x:CompileBindings="True"> diff --git a/StarterNG/Views/Settings.axaml.cs b/StarterNG/Views/Settings.axaml.cs index 0c6f40e..0a521bd 100644 --- a/StarterNG/Views/Settings.axaml.cs +++ b/StarterNG/Views/Settings.axaml.cs @@ -288,22 +288,7 @@ public partial class Settings : UserControl var lang = item.Content?.ToString(); if (string.IsNullOrEmpty(lang)) return; - var langDict = new ResourceInclude(new Uri("avares://StarterNG/")) - { - Source = new Uri($"Assets/Langs/{lang}.axaml", UriKind.Relative) - }; - - var currentLangDict = App.Current.Resources.MergedDictionaries - .OfType() - .FirstOrDefault(d => d.Source != null && d.Source.OriginalString.Contains("Langs/")); - - if (currentLangDict != null) - { - App.Current.Resources.MergedDictionaries.Remove(currentLangDict); - } - App.Current.Resources.MergedDictionaries.Add(langDict); - - App.Loc.SetLanguage(langDict, lang); + App.ApplyLanguage(lang); } // ── tiny helpers ────────────────────────────────────────────────────── diff --git a/StarterNG/starter/lang/en.xml b/StarterNG/starter/lang/en.xml new file mode 100644 index 0000000..e1c05d3 --- /dev/null +++ b/StarterNG/starter/lang/en.xml @@ -0,0 +1,187 @@ + + + + + + + Scenarios + Depot + Settings + Controls + + + Sceneries + Vehicles + Scenario description + Scenery description + Consist + Start + Open latest simulation log + + + Vehicle database + Search... + Load consist from scenery + New consist + Selected vehicle + Vehicle + with crew + reversed + No vehicles found in databases/vehicles/. + Add vehicles from the database on the left, or load an existing consist from a scenery file. + Move left + Move right + Reverse vehicle + Remove from consist + Change crew (cab 1 / cab 2 / passenger / none) + Scenery + Category + Class + All + Diesel + Electric + EMU + DMU + Carriages A + Carriages B + Electric locomotive + Diesel locomotive + Steam locomotive + Railbus + Draisine + Work vehicles + Prototype + Tram + Car + Bus + Truck + People + Animals + Wagons + Other + Brakes + Loads + Coupling + Select a vehicle in the consist to edit it. + Editing options coming soon. + Split + Split the unit into individual cars + Add + Add this vehicle to the consist + Add the whole unit to the consist + + + Loading vehicle database + Loading sceneries + Starting… + + + General + + Language + Fullscreen + Pause simulation when window is inactive + Pause on start + Cursor sensitivity: + Invert mouse horizontally + Invert mouse vertically + + Communication + + Ignore gamepad input + + Feedback + Off + Caps: CA/SHP; ScrollLock: starting resistances + Caps: CA; ScrollLock: SHP + LPT + PoKeys55 + Serial port (COM) + + Other + Select exe: + Select automatically + Debug mode (test mode) + Virtual shunting + + Graphics + Render engine + Full + Old + Simplified shader + Simplified + Disable visual layer + Experimental + + Resolution: + Buffer resolution: + Maximum texture resolution: + Maximum cabin texture resolution: + Texture filtering quality: + Multisampling: + Rendering range: + VSync + Smoke display + Smoke particles multiplier + + Post-processing + Reinhard + ACES + + Chromatic aberration + Motion blur + Additional shader effects + Reflections as cubemap + Render VBO + Render shadows + Reflections refresh rate + Shadows resolution + Reflections refresh rate + Cabin shadow sources range + + Display shadows + Only important + Limited + All + + Reflections detail level + Terrain geometry only + Terrain geometry and static models + Terrain geometry, static models and vehicles + + Cabin shadow sources range + Render onboard computer screens + Render screens in a separate thread + Onboard screens refresh rate + + Physics + + Track curves quality + Increased physics accuracy + Possibility of pantograph breaking + Powered only under overhead line + Save speedometer tapes + Save simulation log + Keep previous logs + Display simulation + Collision damage + + Sound + Enable sound + Volume + Radio volume + Vehicles volume + Positional sounds volume + Ambient volume + Volume during pause + + Starter + Automatically close starter + Large thumbnails + Automatically expand scenery tree + + + Save + Reset + Settings saved + diff --git a/StarterNG/starter/lang/pl.xml b/StarterNG/starter/lang/pl.xml new file mode 100644 index 0000000..fd16ed0 --- /dev/null +++ b/StarterNG/starter/lang/pl.xml @@ -0,0 +1,187 @@ + + + + + + + Scenariusze + Zajezdnia + Ustawienia + Sterowanie + + + Scenerie + Pojazdy + Opis scenariusza + Opis scenerii + Zestawienie + Uruchom + Otwórz ostatni log symulatora + + + Baza pojazdów + Szukaj... + Wczytaj skład ze scenerii + Nowy skład + Wybrany pojazd + Pojazd + z obsadą + obrócony + Nie znaleziono pojazdów w databases/vehicles/. + Dodaj pojazdy z bazy po lewej stronie lub wczytaj istniejący skład z pliku scenerii. + Przesuń w lewo + Przesuń w prawo + Obróć pojazd + Usuń ze składu + Zmień obsadę (kabina 1 / kabina 2 / pasażer / brak) + Sceneria + Kategoria + Klasa + Wszystkie + Spalinowe + Elektryczne + EZT + SZT + Wagony A + Wagony B + Lokomotywa elektryczna + Lokomotywa spalinowa + Parowóz + Szynobus + Drezyna + Pojazdy robocze + Prototyp + Tramwaj + Samochód osobowy + Autobus + Samochód ciężarowy + Ludzie + Zwierzęta + Wagony + Inne + Hamulce + Ładunki + Sprzęg + Wybierz pojazd w składzie, aby go edytować. + Opcje edycji wkrótce. + Rozdziel + Rozdziel zespół na pojedyncze człony + Dodaj + Dodaj ten pojazd do składu + Dodaj cały zespół do składu + + + Wczytywanie bazy pojazdów + Wczytywanie scenerii + Uruchamianie… + + + Ogólne + + Język + Tryb pełnoekranowy + Wstrzymaj symulację, gdy okno nie jest aktywne + Pauza po uruchomieniu + Czułość kursora: + Inwersja myszy w poziomie + Inwersja myszy w pionie + + Komunikacja + + Ignoruj sygnały z gamepada + + Informacja zwrotna + Wyłączona + Caps: CA/SHP; ScrollLock: opory rozr. + Caps: CA; Scrollock: SHP + LPT + PoKeys55 + Serialport (COM) + + Inne + Wybór exe: + Wybierz automatycznie + Tryb testowy (debugmode) + Wirtualny manewrowy + + Grafika + Silnik renderowania + Pełny + Stary + Uproszczony shaderowy + Uproszczony + Wyłącz warstwę wizualną + Eksperymentalny + + Rozdzielczość: + Rozdzielczość bufora: + Maksymalna rozdzielczość tekstur: + Maksymalna rozdzielczość tekstur kabiny: + Jakość filtrowania tekstur: + Multisampling: + Zasięg renderowania: + VSync + Wyświetlanie dymu + Mnożnik ilości cząsteczek dymu + + Postprocessing + Reinhard + ACES + + Aberracja chromatyczna + Rozmycie podczas ruchu + Dodatkowe efekty shaderów + Odbicia jako cubemapa + Renderowanie VBO + Renderowanie cieni + Odświeżanie odbić + Rozdzielczość cieni + Odświeżanie odbić + Zasięg źródeł cieni w kabinie + + Wyświetlanie cieni + Tylko ważne + Ograniczone + Wszystkie + + Szczegółowość odbić + Tylko geometria terenu + Geometria terenu i modele statyczne + Geometria terenu, modele statyczne i pojazdy + + Zasięg źródeł cieni w kabinie + Renderowanie ekranów komputerów pokładowych + Renderowanie ekranów w osobnym wątku + Częstotliwość odświeżania ekranów komputerów pokładowych + + Fizyka + + Jakość łuków torów + Zwiększona dokładność fizyki + Możliwość złamania pantografu + Zasilanie tylko pod siecią trakcyjna + Zapisuj taśmy z prędkościomierza + Zapisuj log z symulacji + Zachowuj poprzednie logi + Wyświetlaj symulację + Uszkodzenia przy zderzeniu + + Dżwięk + Włącz dźwięk + Głośność + Głośność radiotelefonu + Głośność pojazdów + Głośność dżwięków pozycjonowanych + Głośność otoczenia + Głośność podczas pauzy + + Starter + Zamknij starter automatycznie + Duże miniaturki + Automatycznie rozwijanie drzewka scenerii + + + Zapisz + Przywróć + Zapisano ustawienia +