From 3e6fc0e22a22b299e203c49fc8d76b9fff2a147c Mon Sep 17 00:00:00 2001 From: Timo T Date: Fri, 10 Jun 2022 15:44:29 +0200 Subject: [PATCH] Add multiplayer turn sound notification (#6995) * Refactor: Extract methods & reduce code duplication * Refactor: Rename Sounds to SoundPlayer * Refactor: Make UncivSound a data class As far as I can see, UncivSoundConstants served no purpose * Refactor: Reorder sounds * Refactor: Split up ExtensionFunctions and move into own package * Add multiplayer turn sound notification * Refactor: Remove unnecessary double translation * Refactor: Reduce code duplication * Refactor: No if for boolean logic --- .../jsons/translations/template.properties | 17 +- android/assets/sounds/notification1.mp3 | Bin 0 -> 8949 bytes android/assets/sounds/notification2.mp3 | Bin 0 -> 6504 bytes core/src/com/unciv/MainMenuScreen.kt | 25 +- core/src/com/unciv/UncivGame.kt | 52 +-- core/src/com/unciv/logic/BarbarianManager.kt | 2 +- core/src/com/unciv/logic/battle/Battle.kt | 11 +- .../com/unciv/logic/battle/BattleDamage.kt | 2 +- .../com/unciv/logic/battle/CityCombatant.kt | 8 +- .../unciv/logic/battle/MapUnitCombatant.kt | 8 +- .../com/unciv/logic/city/CityConstructions.kt | 10 +- .../unciv/logic/city/CityExpansionManager.kt | 12 +- .../logic/city/CityInfoConquestFunctions.kt | 4 +- core/src/com/unciv/logic/city/CityReligion.kt | 94 +++--- core/src/com/unciv/logic/city/CityStats.kt | 8 +- .../src/com/unciv/logic/city/IConstruction.kt | 10 +- .../com/unciv/logic/city/PopulationManager.kt | 7 +- .../unciv/logic/civilization/CivInfoStats.kt | 4 +- .../logic/civilization/CivilizationInfo.kt | 17 +- .../logic/civilization/GoldenAgeManager.kt | 4 +- .../unciv/logic/civilization/PolicyManager.kt | 2 +- .../unciv/logic/civilization/QuestManager.kt | 4 +- .../logic/civilization/ReligionManager.kt | 2 +- .../unciv/logic/civilization/TechManager.kt | 12 +- .../diplomacy/DiplomacyManager.kt | 12 +- core/src/com/unciv/logic/map/MapUnit.kt | 11 +- core/src/com/unciv/logic/map/TileInfo.kt | 13 +- .../logic/map/mapgenerator/MapRegions.kt | 13 +- .../logic/multiplayer/OnlineMultiplayer.kt | 2 +- .../multiplayer/OnlineMultiplayerGame.kt | 2 +- .../logic/multiplayer/storage/DropBox.kt | 10 +- .../com/unciv/logic/trade/TradeEvaluation.kt | 2 +- core/src/com/unciv/models/UncivSound.kt | 95 ++---- core/src/com/unciv/models/UnitAction.kt | 10 +- .../com/unciv/models/metadata/GameSettings.kt | 29 +- .../unciv/models/metadata/SettingsEvents.kt | 13 + core/src/com/unciv/models/ruleset/Building.kt | 27 +- core/src/com/unciv/models/ruleset/Era.kt | 8 +- core/src/com/unciv/models/ruleset/Nation.kt | 12 +- core/src/com/unciv/models/ruleset/Ruleset.kt | 2 +- core/src/com/unciv/models/ruleset/Victory.kt | 46 +-- .../com/unciv/models/ruleset/tile/Terrain.kt | 4 +- .../models/ruleset/tile/TileImprovement.kt | 3 +- .../ruleset/unique/UniqueParameterType.kt | 5 +- .../com/unciv/models/ruleset/unit/BaseUnit.kt | 41 +-- .../unciv/models/translations/Translations.kt | 3 +- core/src/com/unciv/ui/LanguagePickerScreen.kt | 6 +- core/src/com/unciv/ui/UncivStage.kt | 4 +- core/src/com/unciv/ui/audio/GameSounds.kt | 43 +++ .../ui/audio/{Sounds.kt => SoundPlayer.kt} | 14 +- .../ui/cityscreen/CitizenManagementTable.kt | 5 +- .../ui/cityscreen/CityConstructionsTable.kt | 28 +- .../com/unciv/ui/cityscreen/CityInfoTable.kt | 11 +- .../ui/cityscreen/CityReligionInfoTable.kt | 9 +- .../src/com/unciv/ui/cityscreen/CityScreen.kt | 8 +- .../cityscreen/CityScreenCityPickerTable.kt | 3 +- .../ui/cityscreen/CityScreenTileTable.kt | 15 +- .../com/unciv/ui/cityscreen/CityStatsTable.kt | 8 +- .../com/unciv/ui/cityscreen/CityTileGroup.kt | 4 +- .../ui/cityscreen/ConstructionInfoTable.kt | 5 +- .../cityscreen/SpecialistAllocationTable.kt | 10 +- .../src/com/unciv/ui/cityscreen/YieldGroup.kt | 4 +- .../ui/civilopedia/CivilopediaCategories.kt | 8 +- .../unciv/ui/civilopedia/CivilopediaScreen.kt | 17 +- .../unciv/ui/civilopedia/CivilopediaText.kt | 7 +- .../ui/crashhandling/CrashHandlingThread.kt | 47 ++- .../com/unciv/ui/crashhandling/CrashScreen.kt | 7 +- .../com/unciv/ui/images/IconCircleGroup.kt | 2 +- .../src/com/unciv/ui/images/IconTextButton.kt | 2 +- core/src/com/unciv/ui/images/ImageGetter.kt | 1 + .../com/unciv/ui/mapeditor/EditorMapHolder.kt | 11 +- .../ui/mapeditor/GameParametersScreen.kt | 1 - .../ui/mapeditor/MapEditorEditSubTabs.kt | 12 +- .../unciv/ui/mapeditor/MapEditorEditTab.kt | 7 +- .../unciv/ui/mapeditor/MapEditorFilesTable.kt | 8 +- .../ui/mapeditor/MapEditorGenerateTab.kt | 12 +- .../unciv/ui/mapeditor/MapEditorLoadTab.kt | 8 +- .../unciv/ui/mapeditor/MapEditorModsTab.kt | 8 +- .../unciv/ui/mapeditor/MapEditorOptionsTab.kt | 10 +- .../unciv/ui/mapeditor/MapEditorSaveTab.kt | 11 +- .../com/unciv/ui/mapeditor/MapEditorScreen.kt | 10 +- .../unciv/ui/mapeditor/MapEditorViewTab.kt | 12 +- .../unciv/ui/multiplayer/AddFriendScreen.kt | 5 +- .../multiplayer/AddMultiplayerGameScreen.kt | 6 +- .../unciv/ui/multiplayer/EditFriendScreen.kt | 5 +- .../EditMultiplayerGameInfoScreen.kt | 9 +- .../unciv/ui/multiplayer/FriendPickerList.kt | 2 +- core/src/com/unciv/ui/multiplayer/GameList.kt | 5 +- .../ui/multiplayer/LoadDeepLinkScreen.kt | 4 +- .../ui/multiplayer/MultiplayerHelpers.kt | 19 +- .../unciv/ui/multiplayer/MultiplayerScreen.kt | 10 +- .../ui/multiplayer/ViewFriendsListScreen.kt | 6 +- .../com/unciv/ui/newgamescreen/FriendTable.kt | 3 +- .../ui/newgamescreen/GameOptionsTable.kt | 6 +- .../unciv/ui/newgamescreen/MapOptionsTable.kt | 4 +- .../ui/newgamescreen/MapParametersTable.kt | 17 +- .../ui/newgamescreen/ModCheckboxTable.kt | 14 +- .../com/unciv/ui/newgamescreen/NationTable.kt | 4 +- .../unciv/ui/newgamescreen/NewGameScreen.kt | 19 +- .../ui/newgamescreen/PlayerPickerTable.kt | 3 +- core/src/com/unciv/ui/options/AdvancedTab.kt | 11 +- core/src/com/unciv/ui/options/DebugTab.kt | 7 +- core/src/com/unciv/ui/options/DisplayTab.kt | 7 +- core/src/com/unciv/ui/options/LanguageTab.kt | 4 +- core/src/com/unciv/ui/options/ModCheckTab.kt | 9 +- .../com/unciv/ui/options/MultiplayerTab.kt | 308 ++++++++++-------- core/src/com/unciv/ui/options/OptionsPopup.kt | 94 +++++- core/src/com/unciv/ui/options/SoundTab.kt | 10 +- .../ui/overviewscreen/CityOverviewTable.kt | 8 +- .../overviewscreen/DiplomacyOverviewTable.kt | 8 +- .../ui/overviewscreen/EmpireOverviewTab.kt | 4 +- .../overviewscreen/ReligionOverviewTable.kt | 11 +- .../overviewscreen/ResourcesOverviewTable.kt | 7 +- .../ui/overviewscreen/StatsOverviewTable.kt | 4 +- .../ui/overviewscreen/TradesOverviewTable.kt | 4 +- .../ui/overviewscreen/UnitOverviewTable.kt | 15 +- .../ui/overviewscreen/WonderOverviewTable.kt | 4 +- .../DiplomaticVotePickerScreen.kt | 4 +- .../DiplomaticVoteResultScreen.kt | 22 +- .../pickerscreens/GreatPersonPickerScreen.kt | 3 +- .../pickerscreens/ImprovementPickerScreen.kt | 6 +- .../ui/pickerscreens/ModManagementOptions.kt | 18 +- .../ui/pickerscreens/ModManagementScreen.kt | 12 +- .../com/unciv/ui/pickerscreens/PickerPane.kt | 8 +- .../unciv/ui/pickerscreens/PickerScreen.kt | 2 +- .../ui/pickerscreens/PolicyPickerScreen.kt | 9 +- .../ui/pickerscreens/PromotionPickerScreen.kt | 10 +- .../ReligionPickerScreenCommon.kt | 7 +- .../ReligiousBeliefsPickerScreen.kt | 9 +- .../com/unciv/ui/pickerscreens/TechButton.kt | 10 +- .../ui/pickerscreens/TechPickerScreen.kt | 14 +- core/src/com/unciv/ui/popup/AskNumberPopup.kt | 40 ++- core/src/com/unciv/ui/popup/AskTextPopup.kt | 18 +- core/src/com/unciv/ui/popup/Popup.kt | 20 +- core/src/com/unciv/ui/popup/ToastPopup.kt | 4 +- core/src/com/unciv/ui/popup/YesNoPopup.kt | 2 +- core/src/com/unciv/ui/saves/LoadGameScreen.kt | 10 +- .../com/unciv/ui/saves/LoadOrSaveScreen.kt | 16 +- core/src/com/unciv/ui/saves/SaveGameScreen.kt | 11 +- .../ui/saves/VerticalFileListScrollPane.kt | 2 +- .../src/com/unciv/ui/tilegroups/CityButton.kt | 11 +- core/src/com/unciv/ui/tilegroups/TileGroup.kt | 9 +- .../com/unciv/ui/tilegroups/TileGroupIcons.kt | 7 +- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 25 +- .../com/unciv/ui/trade/LeaderIntroTable.kt | 10 +- .../com/unciv/ui/trade/OfferColumnsTable.kt | 14 +- .../com/unciv/ui/trade/OffersListScroll.kt | 17 +- core/src/com/unciv/ui/trade/TradeTable.kt | 5 +- .../com/unciv/ui/tutorials/TutorialRender.kt | 3 +- core/src/com/unciv/ui/utils/ExpanderTab.kt | 6 +- core/src/com/unciv/ui/utils/LanguageTable.kt | 3 +- core/src/com/unciv/ui/utils/MayaCalendar.kt | 1 + core/src/com/unciv/ui/utils/TabbedPager.kt | 26 +- core/src/com/unciv/ui/utils/UncivSlider.kt | 18 +- core/src/com/unciv/ui/utils/UncivTooltip.kt | 22 +- core/src/com/unciv/ui/utils/UnitGroup.kt | 4 +- .../utils/extensions/CollectionExtensions.kt | 70 ++++ .../utils/extensions/FormattingExtensions.kt | 94 ++++++ .../Scene2dExtensions.kt} | 198 +---------- .../ui/utils/extensions/TimeExtensions.kt | 12 + .../unciv/ui/victoryscreen/VictoryScreen.kt | 8 +- .../com/unciv/ui/worldscreen/AlertPopup.kt | 19 +- .../ui/worldscreen/NotificationsScroll.kt | 4 +- .../unciv/ui/worldscreen/PlayerReadyScreen.kt | 4 +- .../com/unciv/ui/worldscreen/TradePopup.kt | 9 +- .../unciv/ui/worldscreen/WorldMapHolder.kt | 20 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 20 +- .../unciv/ui/worldscreen/WorldScreenTopBar.kt | 14 +- .../unciv/ui/worldscreen/ZoomButtonPair.kt | 4 +- .../ui/worldscreen/bottombar/BattleTable.kt | 20 +- .../ui/worldscreen/bottombar/TileInfoTable.kt | 5 +- .../minimap/MapOverlayToggleButton.kt | 4 +- .../unciv/ui/worldscreen/minimap/Minimap.kt | 2 + .../ui/worldscreen/minimap/MinimapTile.kt | 4 +- .../status/MultiplayerStatusButton.kt | 4 +- .../status/MultiplayerStatusPopup.kt | 2 +- .../ui/worldscreen/status/NextTurnButton.kt | 6 +- .../ui/worldscreen/unit/IdleUnitButton.kt | 4 +- .../unciv/ui/worldscreen/unit/UnitActions.kt | 2 +- .../ui/worldscreen/unit/UnitActionsTable.kt | 8 +- .../unciv/ui/worldscreen/unit/UnitTable.kt | 13 +- docs/Credits.md | 2 + 182 files changed, 1730 insertions(+), 972 deletions(-) create mode 100644 android/assets/sounds/notification1.mp3 create mode 100644 android/assets/sounds/notification2.mp3 create mode 100644 core/src/com/unciv/models/metadata/SettingsEvents.kt create mode 100644 core/src/com/unciv/ui/audio/GameSounds.kt rename core/src/com/unciv/ui/audio/{Sounds.kt => SoundPlayer.kt} (94%) create mode 100644 core/src/com/unciv/ui/utils/extensions/CollectionExtensions.kt create mode 100644 core/src/com/unciv/ui/utils/extensions/FormattingExtensions.kt rename core/src/com/unciv/ui/utils/{ExtensionFunctions.kt => extensions/Scene2dExtensions.kt} (54%) create mode 100644 core/src/com/unciv/ui/utils/extensions/TimeExtensions.kt diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 07bc0e26bf..8506105374 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -602,12 +602,16 @@ Resign = Are you sure you want to resign? = You can only resign if it's your turn = [civName] resigned and is now controlled by AI = -Last refresh: [time] [timeUnit] ago = -Current Turn: [civName] since [time] [timeUnit] ago = +Last refresh: [duration] ago = +Current Turn: [civName] since [duration] ago = Seconds = Minutes = Hours = Days = +[amount] Seconds = +[amount] Minutes = +[amount] Hours = +[amount] Days = Server limit reached! Please wait for [time] seconds = File could not be found on the multiplayer server = Unhandled problem, [errorMessage] = @@ -1395,7 +1399,7 @@ Doing this will reset your current user ID to the clipboard contents - are you s ID successfully set! = Invalid ID! = -# Multiplayer options menu +# Multiplayer options tab Enable multiplayer status button in singleplayer games = Update status of currently played game every: = @@ -1406,7 +1410,12 @@ Check connection to server = Awaiting response... = Success! = Failed! = - +Sound notification for when it's your turn in your currently open game: = +Sound notification for when it's your turn in any other game: = +Notification [number] = +Chimes = +Choir = +[unit] Attack Sound = # Mods diff --git a/android/assets/sounds/notification1.mp3 b/android/assets/sounds/notification1.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bd83668f70b645e6e62de515998e440351ed86de GIT binary patch literal 8949 zcmd^kbyQVf*X}-tE;&f|p+i8r1UWQ^u0w-|l9G~;ltZIPDUB#CEpZ5GB}71JL_$Kk zkNJv6LMn*wFRaH~d(9p!h#LCLn*2&4!^VzfD;PCL5FB1|{QnIpg za&n4_%F3#%>+74FJ32Z$dwT~4hKI+;CnqOoX6EOYme$s`w)XZ84vvq{&Mq%8m>XT( zn(|u8A_78!g4l)shC~Tv$L^2^K#jLJGCjV${r89eU+$QTs#}6uK*DGM;Awip(=L&C z%Su8#Q!imYu@1D4grq$cb?c5HdJaKg!3H$sW!|ZOIbcDItqrgf_HPxyeuxI zFt$7|@FWHt8M=!BMJQ*{vEV`urIaFQfUs-hGm+AdVk%c+ij6NE`!RJ4iGM1StHoNV zRj0?8@gOZOe?&8nLS-9VF%{F2g<{#G7eDxyB=qjj%M?{!%fRQ_qvvIibxg2=hSr1h zvpOLyo6Pau?90yIN9x8S?=LWMyXRUD>sxWhXtX446aTz)kUN+H2b9_ueGN;6sb!}# z9!5y%A-N}5<%)wWf_tOXQ7-5V#>_s~jRjo;>7p#eV+X}|zPT^vvaEH7L9EFFJ+JwC z*AeT;m*KU9NNhV3BkE@uB+U~QJ3pl9*#N_Y;UtQUT+MpZwkl{?EQnaxi2?zn(F(lT zt7R<0B`pYG*mn|t<0QJSFwaZ;o>9Y~Tc$>nZJlsm;N?!Q1+apX{@P>dDHY70(XwQ~ z3FQ<3JGvOcz1PN@zLrN0C_#tqLP?*;KQGf~cp{c4U)~R}Jx2r`(OKqxj!1rgof+~6 z?Fd4_Pu#n+gVoj6Usun%JId-@k0Y_)R%rwugBU%hORvGajDmDwu%$2{2*Jhc-<42l zZ#w_gA?NO;Vr1E;=4DzHT2I;W&ir$;5=*3k_9!!^b9{j0H56{*#b=~)w`IFRC;$3T zqxl$t;x^K)yY9ew&_GQLq8SrZqC~k>VGF^bmd;uQP~*d(kAa7#r42`uBANF}7FxjU70) z)s~n&pn0Q_DEaP~uXQ;c4rLdRcW*iG4&AXVwm^oq;=Q%#X+7PYz)yao<`O@pPwMYy zmE`&p@NaDIJSpj%nw+dC(EDo^UE#`Jjxw6InhQokrmpP!_DqOfPmRj6XPWRd7Hob} zJn*hMlG^Bfy82E0B4}&FhbQ^T_|Zx0?v?fjty_CGP{cHYrrSbzJS<72bqzU%JQA(D z9%H9}LP0QC;BP!b*atdqyYo@*S)m8=SH=hwAfhY+|9F&xjpUcDl3>HjcqpW7`%0wY zd#F}aPdp7>W$m4g&03pc?Kj4**b;J#A`@{m?x|I8lj{Z2f*rTZkVJ9X|C zXr+f2+crn6jo~AcM^%c zEQc(YMOL`a99)OoaoKaS!aS8XhPi-sU84ww8Vo9`9azkgoa-8@B7~(~c0QiL zE6f@*6Se{#E+BrBS&Uks3mQvsIq^%8*tGu9*F(U**k^pZ6kIo1CDVv&liMdVM1Sq-k?qIUDTS@J^mb$^UmpD**=lQJ|c z5(m!LxgY#a($ zvqoFDU8C-yGjY{PH2sw`4PH~ydVD&KLp3<|2`{NrI{Eq$ecvUBm^{C1n+xqNThxp- zY#P%j!8{_Tx_1N@-%)}9N1 zI40JhV!>fSBW2U)L(b({X)kVZTvq!$@V>Mftpw_GU{6#MKUcaQQd`tL808}T{26Wu zH&itKG4!ZEiyZN5^nL4r0lIjEW13Myr8`?_tKNs;geS?!ns8DK0Mx~wO+c|aJ&Db$ z^bN=Gi6>Ex0C5jLPZ-wc$DAVxKZd1O%BK~ymoxiOv17E(WWHk^e~rd^&9kd&%H4j5 z-6s+Vl(;YOF{E?~F{qJLh1mX{d4-6tA89ksdB#USz3Ie zk|D+#6NT3aNzW&TIdioY03hz^_9+zA^y^do&*09>E#Dyz9KTa#`rfOiFbBY}4{=^4^umZOT73O@~RgR zW-04p_3W2kZGr7GWbWUapRX@8emAxeIu0nlqLRBBD+xBCcRtgL1cZd4ce&;~pb;Z_ zoY#^v7JvxW0)vmkVzm^^O`n~V(qYqW0$rU^HN!&t%g|4>xF;xwQz1CGQ z+_@ZJasrwrK(JkSe@i|`&QOcT`{RxAeDP71FX6VP&tKT` zK{|H1%TD~Luf$v+Z)z|Zv{moZz+I4eQn^G`70zaz1C~YfcF5yrwPp6S#F@%H6aDz? zree>;Vyba_(-iXw$Jv9%w*Yn@GP#jZy`m1+?jzQG8>YX4D%i`|y1#G>PwPJ;Br;AlR^EF#A<*qIs^}dzOF!l<w& z`E1A#XldQCC)nQ-bO{h3V{NzcNLU1tT#ll|*q`a8i^4@^wdd3erOP&xSkA`M!09+e zG%y#6Enc&yXi@*t?xUF*4}e`*L?L(w)GVBLFo$3tG>$eW6PgF%dEXtDQj-lB8R_$q zY>F2gh;{Ai&}s+{KqLsH{#cCizOGoix^Jv|RBC2_gel9_KFMDVqf{+td3p$tD502- zL-9m7dzQZ5hWsIbzZGj)UJbXFB`XU~mFIhjRpJ*V(`t!U8M&`>{|EOL-WC{(qB>C3 zu7|bVOdeG*cQ_&;X<=Wq;cCFrlMSyd^-T!Jz9YUq$~N2qrGVf2WhUh!?&8 z1qrtNzfhwHA+e@gd3P4zH@=SI;Rl7;{s_dL;JR*@2jc@r^_|G5sVP|QtO6yfe@rRT zoSQEq9rwDREbO}y3t_%g!BSB{R6buFD{pehQ93G~;AyD9ld#^xSG7l^LhItv-A?-R z$Uc0@@S{x zE5o3{rhwQZW&%42QGGXR%+MX&f|b^#jqTehe2OAmDzvO%soc=6;?ZhhY>^2%DZMB- zo_(5?xmX=w<%!a(^gDDo*+j50m6^xF06e}#XpvQYC`uems7b?ZENn!eC_7@q;^_3C zyt2C3^~1%zH?p)%3(l(3eFLbmhbK&d*Du#wo@1 zTs#{IX~=6_Ui}zLqKHvBd`)lt+#79z)ocYHc=355 zlJ#CAneun#340faP1p3f7p$CVdF1p`SAQTOola)179z_PY2d^^(l{^dOF%IOXNg2~ z0BS*zET&Z|8O{ylIL%S8DMDs!Y$k~F|6Wy)EH&u67TsA0By(P`6)QlOQMpTn_t%M# zusORZO9=Oc(^9gPD@*rH|9*tIo~(Z7tYVr_QW(fY>6%(PmJBV9!_gJ>AN?iAJ6VW5 zV|EQUXKM;Xgag1`fXdjjo&xSxhJ}2S68kKI9;F&_pV(zgm<0ZE{-KpAI;v?;T+4P> z`s+n#i5KQ{TM=dF`M82gNLYX*05LA1{&*5;l4aAhY{3VL?F?I1_{Qz4qcQorvI9_3 z`nWYu5}Mn<5kHCen~f)Yhi16r^46Yx6k%6`bvcJdZdAI{Y{QF|%tSw9jzsKr?zBF# zfZu+bZ`Xal#%g3VU(Yvoe;1orbTYEUa<1AIT(^e4Vl;6%;6xYIQ7?e)mHVmT?i*HGPB z@`6SWsDOz@ubZr}o>HuDDuSA5))8PIb_=${`n}3B_=IOMT!rR!r2uG;YRW*FW68tD zkTau#U2{u}Nn1en-Y9VnK7QA&JqIAdnK%P2+R6q0BxfIVYr&*z;>(4fkc%I4QkVt< zg}=2;*MBc2lmDuR91rFVd7}UT3P{U?Vo9SO`GW7{@fj}72m!>*wqG4!UOX;CuX&=# zi1O-LH(cR$T4VPvj_TpT_)+-yBTG(McT)BA>_53$O)D#S|Ed_mO83~Z z(I#&3VC%xCfBbj69E!`zfzKv%UW<<-{CYGl4KK`_fY+1FNM&3Ur%*0`h3BYfP1jxE zq1S^hrZ%~sXOSBs$=c5LDYy0<9M?a1HjSMP_EH+ma%<&JbqTK9GKvqU2Sg$0}R5dNuNE; zo6!A8(<->@IF}LDar|Mq7=$uZmsDXT0nzYzQw!1qq<$qXdV(xQ#bvqtN2MO7PQ?HS zKRu}FPc79(Z`8|ClP)8oQ(`KqFX;@=b+4GD>n0=z5yi*%T=_5Lg7(+k;e(ifDst>< zhCr5q_;=HT?I&LQ7wWgE?;J(&Huh0@`qqLHS(#WqWy*I~`$DJQ$!2BLw|1Hd&YOvF zvzSb%$YG?sUMi0!_REkVc4deG6eOP(ET4nOev1^Dd$0Sp#&0~_*e>447ZiUdcQu0_ zE|EQ&?_56x9)wV_v+@VK%S#1xhxJCTbmzQXt}yp)WWApc$d{@!sCUg7Ygi0ZD|vRU zD(inG7Cb!MJ71^su7X)%+#$LjJM6jN0023J znz;_eV5%M~^P)*NB{Q?ePDu$TUT7kN=+%e{l3q|VrXhyc+@un%k!9cE{O8d%N7Xzr zwTvrq{`<6I<<->MocF@(pnxjpXZ9=Ym~oqA0KkG$Qper0aDh_q@YmU9-?Y}D;i;=< z@dntwaqi`8CMq5IW%4X^e`W=?7dG4j=J98Ss<{iP3^X;IV<(;jAzKvRW29&B!P9&N zQ!C$!g~@Fr%cN+!Np4l)0;S5H~qusWgW#P)Rwr3GFV zxpmbIK&_Pq)WAh7;=C8TBIJpp+jFC&TH8rE@zN6c5hRJ3M(KvmE?9 zp(*UvxwoiKz=WsKTHDnwEbL@XvqdZj@YgDUw{!QR7y$>|<#+Hs0aeg5!|+#I{qHwS z56J{0?Pm9WI5y2*7WHvG#bBN(*z3I3iuYYw`h$!3_-N&_{Ce~^3P6F_dH_nNNf$CR z#gW*>#~iQsOM>%RX1P1n;4GnB z=xP{y#2-*D){06gp6x@#Wr73_`ESGK5ar@0aM69I4M__HPBNCx`0hvz0>$|_>!%MM zh}!``*j>gPAox)^!)t@CmvHimTvvt9@9{!AOzcU*lyT_Q>ST?C4Ymu5N>~5FLW{RU zFuRAugyyU^1*#Np1dyU2;$*m9&tj{@)@aBqrsT%CVo+?nR{-@ZiQu<{IIy0_|Jt`5 zp$N*u;oG!s3#N%{b?6Fh^>B7&5tjL#+O) z45c)gLq30+;GC*t#R-9txvT!0sKXI9T15lQ#yktIT=2Mf5HW22Q*_Mk&|_VK7{eE0 z=pxnf{FRcvcYEccb89O304{C7kj#EPqkkjLYfVPHXx?6c2*ud~fP}YIh4ffi`Aj)}VxaNET<&x@ z=}P7Lj#!D$rzT<>uH(yI9nNQz`1aK9TP_IMKd<*D&~RU($fw- z8#Q(~VV88kc=`>Ktnf56K%%w9azqy4S%;&wB0fg(V@P~H~Yv?h+{2~IAttFchPuET5!=!P!(M^JyC z%Z%$uZrMVg2Y?f%*ndSl<`y^Aw-C=6DDNH9ohXb~Oc!u3(?)2WtS&$OggNToiECxJ za_0xEtxpMxdS)MPv7Mms(Eh3w9E7}JPW)6>jun}DxdQ%7#1 zZW8bnOi&2NVJZ)+-uShRci^YQaJD{_IYk;)!tbY)<9kn~wPq2F8>3-+p(U2b5|ALt z7o!vvZ72F5wVO8&d8L=Sdl)E zW2T}^(ayZO&A%J+I0ph30DQ6(ebr;cBi!?mSG_$<=fwfx%5zhvaahuWEOc?R4Ex&l za#P%NT5^N$l%usFbQX$874&58s>K*03IHU;?!C{s$)kc@2Pn04Pxuy8%W?6}4Rg6# z!q1Hjb`C40)Cm}U<2CzPAs3xts^~;h{KYdJhCSiD3-D+0KkdhR&H3N&xK=fUh+p5w zg#yKn`g4*$Z7bg>hb}7!DVFaGxjd*Y@lF;g@-F4I+29}#_=(1~v~6Q1*8_Nr^5sQX zaNANSOOl~bC1ov#4nU~pv^g%W7{;SLYNKqM^u7DmJEwqZRkX$yZ3T2Pj^`>OSJPew zE-f#jtvr>Gv8CA?VhcvT|Dbu1fOxwhFv6xYhv>VhI^&4Iwnbj{dfjnj1}VO8hay7i z6;Ae%&Qco;%7``svv$by537c6rjEqK&nZ&@sGo$@>JUJ!O^Wl~ADqcc3xWETaEsl6 z_1=2t5T*LcNoR{@8@d>`9vseC7+F1%5tBdDAMW_TZY2!lu(JJ{TQ~b_SoGU|6lQ1$ z3cPw*Jt(BGW1{s!{|jy+8+Z^zSEhr)C(j>vC@~PqmC``+?k{psJmb`}bi-;OLZNsf zbNn=nzvrgIAZ}#Vhmg`v6Qv*PO1UV&rg7*>zm*>6_&(UBPd!om+571 zvmd!|8dv>BvX0O8yi?oSbCXq~gJxh|t+6Q+26c)s-@PFs@RhCT&lqEx7rCP!N|(LM zH~GHdiC?8t5IkqmtS@JLNVXnXwfFUAeSO0ZN;N1DzQsZ4xEjB970#0UL|udG5Sw#k zcdtzUOvbWFBZ9sC?U(B{^}O}yJ(E;|z=1`%_G&c$yAcKtg`7kKOu+% z!nNThX{lKgiFDcPQWB|rqx7CfDBKl4rVgH|PH~w2m2CG6f=UZ2)Tx{wo2@Aqy+v>c zsG7LnM2Hr58_9ah{yU0cTy-MIw~9Y~U6NS@<&Y#?U|3o>H8Sn#;A6}0Z(`FH$1FA> z)S0Ef&p3Kbj`U0YVESnekJrV>Ba zA!Scu6Qef{eq{LT_{-Nhei;T>-6s zj;qdv0s_Z9NBnil|EcG{iTWRp|B3^YO1Zo0YBgbTGs(Kl{yWP5 zF$OGov$Z3@H7Dqx;~;9s7<}Yn*EOPJE&VS&rT;hW7Qrb>h5HeFHRDNfOWZ2@7`pTi zf&Upj{}B0)4(0-KbJC<@;jX+|;F6O?xYmLT75&GE|4ipr&pAq&!d-DSy&Ue&Vq)fi<_B=b-$EG|Ky1^XW({xh9hJ&T~vhtv{C*)Sv%7RMMCF~fgG c^gpEB>Y2dm06;f${^lP8Ac_Kj{}=ZBFY4HwJOBUy literal 0 HcmV?d00001 diff --git a/android/assets/sounds/notification2.mp3 b/android/assets/sounds/notification2.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cdd2281b063ef17a6282dfab3de11aad0384edd1 GIT binary patch literal 6504 zcmds5do+}5+kb{(#LzfT42@&RnW9n#GnmFXib|$Tk;Igu-KH5tB01%hVjQv=iVj{v z&4`MqEwhK81wmIMM}`}Xa=zP>bCSXfwOWaODMXA%<=FJ8Qu zot=I4YH4X{Wo2beP0gJJ*9}6KGggnq6O9TG==6CTsv=uHpE9+M+t;b!#YKbDeC(_kgnhX$bEuV*v zLUCw}Y=rS`jLmvka{{eGTvW5_(gb4cmQ(q)qLD zUyy$8`BMDSX|DYdlWK41uIwsp_?wsJIh~}zdv@IG>%hX8q%&Fq+M;h~7kkp-@}_1m zMk#Bb7Awr~3NGBX&vrAt(RI}qab~U*2WB(7vNyIDb904sPC&_=^YG7%zp@I2@yNw} z17KU^x2Nx3B&Bd+F4}oFHTnmtwvTNpLl-inUTYk^iNd;u3cHY8A(;Pk-2I zw{rLniM)10ZG}yvMS?yr<8xDpL)_L&IAlw4BWFH^YbN>>v;J*Vb*;)~C84t?Yult* zTR8&{7Eh!m)0|&$!1u}RY*uYh8doZ=4K@5zdGkZY_qJVwjy6dJs-B#_NVyLia!Nh~ zj7zkkXGHy-PwEECisSpZg!KKldco3RCC5SM1t~{lR$@14l}7W&k=Mi8;#2sKCb>4V zT|wM6vc|vuu4|*pE|q}cX39(D5*a-J=3e(-X<~Kn=Dmut>K41Fo z6zz6XWBr_Mw$C-5yc`rtt0?H1FI=tnP=kxfQ$QZs13ru*!LoPTPe_KG8ZdS-eVlxA zD!R?NVXw)c+E590OXo-{t(zZWVXmNu#u!xe^&15@I+Q74{3$ROe&06nHx`MVPO}4{ zeNJ=Q5n!z%Bu~vRWE$kI{b6o=@ynX4(9I3U^*(I6+g5Y??XBJ38Pd+;sO)@->ynPg z^X)`a^k(k81fCwFq~6RNk0-L!Ed!^b?$UA@{D&+wVCJ_ zd!f>#Ro!_wkymbvkvVNqv9!)}QVAVx*mCp1_Ya8-nA4ov+~Ud*y@7Ag&*Xx>u&|pHMOHCc zYl}}LWTgU-vl=BivYa&&%sq6hJ9-V6i1bx6vlU3kziY7S{jeG=)7q9Gw|hZ;qb2D> zHPsW%y`JxmH-AnKK4tdgxb0x;Hr_&fbFHG9;#xSMvX|Y$xc;!^oDsY3hztN`j%1_m zhG>UADhhK4U%INbYj+ zG``GE*1P0%+_AIdn*PRqF9#b9efIp)`n6{V2@XgEwbgQG4VXQFU=lL8jV0dMp#3Jz z1CJ%5Q}Ch1gd|OK?OjGWomx0m(~S%vskAn|-7b4eMq~;Wju8SM$Kr|2m$0Rmw=+O{ z@Uz?t+n>J$%V*nuFfrsHAKntA<$GiOuNLW0>>gIAfQf0_dsx7D{!2sfuL4Y0gG1Y^ zNuRi~7tG7`cWNva^E~_nH4($LRuqJV$gmSwdi5_U11RIH#~qHq-z4P{f}4UcbBKKqP^ZLA!m#bj-4}??&{XrS zn}Whod~G2E_O_g;soTC$;EgDW^L>99fZ~%~-1mc&=MNx^mh-+_uV*mCV;O6D6=V-Z zQcQJzMg{fo4~TLo?3)W@S=_7{1I(uqWF#~ym0eBvJ4Do#PTkLEul{h$+G&)s3x?+> z<$$@N!+itbG5dXI*Wjk-TnHnBn4{ej*4G%*%Reh%WeL(~GGE`!^Za~zN}I4bzO=*W zvj3qNjhU@GpH1<)`A}GCB=1U$O!nC)TNq%AX=YwI_CJlUuU-^x1TPoq>f`YhAonT(m#^MLz zbRnrX%I5xd+apzi0BFSNH91$+&-&Dk)*hGRsR4kOL&Ywrm_eUn?E|Z$FIxeRYwr5s z%kM$F1y7`ZiL#?cMI@oP&r+<9Q*OjjMxEMEScaY|Xw2VEj=sds?7xvV7I)o-|B75e#C(03xJBFY4S`ni*)7&$S_0#v5;$?IHM>Wf+rJYTi|Lh|zHjhr%g3eN zV?)9)R05goF5hK>smaoOaUcYABI_B&^Kz^{uY+x?(^I|H$;j09yem+LPdCYfl4cTe zGCGB}V2td@hsB)8qcYR=g5aIjK*;|)wB6s&=d(=gey=KndmDEU?e`2Fv7O+&o>-4Z zj*CR9kY(pj$cxJHiby%f2y?CjiR7&}zy_AwIkR|qZpFYM0iN75`MY~o|&JZot#{t?r<*Pjrz=KZz^ z1{EH6CZ?#R=E+(FBAnZFRF48dvAg)AxV8AZHJ~CB1{2ifGbNbTD7Xhv7F|5g6A1b9 znB=L~yT)}lYuKsk-`ljqMPD3?(u^dV%q!K;@0m1(M#>Y%S?=>$66jN!=umY#uW&Fm zJR(U%5Q?MHZnIwkzjL4{2}(-eRMm=-{x}(;=b9s;|0$D@N675e*s|Uypdh{|=c#`F zN$lkm=lu>3uE5N$C9#5sv_-F+{{+AWz9ig$rejyZybM47^ju94jnDm}U$l6B)x^ZE z9t~Estk10PnkQFNO2pz-%hc_LY{A!9lt?5BEED1NPyYPUbM&%XvHazU=yfkH8Cu3+ z_c?*lk0m0IW(&attEuV3Q`MrrG+yf}VTbYJcP%2RCExvqQ^oaR}!5*KrP z@;M`_H;Nj?bYArUZiamt=hZ|j#LdeU3MzGh4p}YqH~VRLm1t3X+TBVmorC?$OE8(X zU?A>51Hx6obG2?uIB1+>vtz5Qao%0)52I-z!`7RsuvtQ#7nu}^S@Kw6n#Kl|iD+I& zAE~^Qbn8gRNG@0z#OkdnXJ*96%{e~U3FnE18H&~Ug-T~`%7>S}=7VkWVI1NciHDv) zQ`vE@=H7KxQM-6(?uXQFJ9CGpuB2DRIkp_YcQ`>wz*4bmEQ!do2*|^_aNj-44c8?$ z7kYp>?|ug8hK|9dYAwp3)atzBnHYvJfZz+lNF-Y^@*JJ6oc|btbPko59SMuaD@swe zR(TmWGq%3r2OI#)mTfDLGjrB;EHNIxSfJnB-GdGl=k3wODYPYr+3yXOiuLO;0-!ns zd7;6o+TTAxEY62&gGa?P4z6?ah34C@pakA^d5}trvdZrxR7=mZr(X^Hf0E>C~ z=nexQX$`sQLD0<}eXoh7WldrqvP7k#6nx^d58bMa3H*X?h&U8_M|w9S<%p2a0_2Q+ zO&Kpgjql5?Tv2}L63hKcH&bDN&)u5h*jVB_igYJHn2yX)z(3W=2{#-btsgRTa& zvmX;7~tf|}Z5LBR%zh`GV{XElF+FQc}BV&6B#X@f%i3)M$jQ5jGA`pmZOy5?r)x!Y7WZ8=Lq2wzavfwTnxb_8V1*j9 zmy-2--Qe&yN_Tmrde51&ba)@2RYW%9e#IpCep?;;cBT~8jr*{Jsf)}h7niq!2-65eX39wbAZ zaJSZ1%f{}t#>1ILG|iX1k(=Z8$Y!iHnrL+1wUmq(m-gE2P}dk{C1Ln-#k-dai6X4& zrlzLjECjo3`^Uux%3!4y>q>5t{%s5wBln#HH@DNTWQ2ZsaJ+RSwppWIaZZR$`W>1z zm2OQ%Y}mSsBvGp*?>h~?pEUtXt;&WCv9n3PUalTQ76HBo0mMF*zlcgc9up%nDBEm$ z%nyvf?ZkKHCg1z{%#Y*Nx8z=jtOYAGD~BKy6N5%cKniR|-;|+di^hmB?>xvcuZ~of z9H{p&r(z}D z06(n%`tHW2Ll6@qgVKZ0AqVZXwt~;@V3Aw@wKZa;?G_HSb`L`Ew+$vXu-C)p69Z(Z z0{#J&#LXe7fo}^-LKHTe&4j2-Q?$_U0@KmE&Y>)Ee{glqVH@kKcTg-y!fJ9tmipnz zT7maRFd^g0lLBRl_GBY}E)G%>jm?0>Gz(6@o!JqLZZa;KJN9iJQJJmHCm##|YyM*C z^M@r;qaeBriVC4SwCg7Q&q(&SMtFf`g|=mgWSKy!6fvddDIK=K5cVc;z@Yu(!n1-* zvsYK3Pm(Cne!=x`&<%}6`{zKQcBgMyQAykFFMK$N|Fji4#G+e5&|x9|0+VD|M=mC~ z4wfh#wp>?tR!STSHL+bO4hQq%7ZU|#Mi7L9`*`Sv5m$BXcbbI^ zl?DLbW97ng1y0F-;r~za;!HZ-Lk2~FHupLNlvix2Qoqvk5zP2g(t9p=e@TSSNPDe` zGRc1`j7tC^Kc6R$rf$aPDh01lEibjH5>mj@a|U=UQ%FAfC;q7<6q72=B|rwr;$>sk zn(R9zZ0D=Oe*@BIuGZXN<@gEr6%1c`s@eKMRu*|^lgEYV!k`zw>juS7C6EVWi}Cb} zhpjP|d!Jk2)FW~OUTr>b_xddFPkhtAg!%W);3;vX3u17+K(HY)a|0k1Z znm`_n^$0$gp?`v9fc~4g|72K*KO5^{7*A%!IhfiVj{QqX|KN%L{&m$aaXNt84I5oTyt})AaCiH)a|6e+gy!rqD literal 0 HcmV?d00001 diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index a5546560d1..cc1695f1a3 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -1,7 +1,7 @@ package com.unciv -import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.Input +import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.badlogic.gdx.scenes.scene2d.ui.Table @@ -14,22 +14,35 @@ import com.unciv.logic.map.MapSizeNew import com.unciv.logic.map.MapType import com.unciv.logic.map.mapgenerator.MapGenerator import com.unciv.models.metadata.BaseRuleset -import com.unciv.models.ruleset.RulesetCache -import com.unciv.ui.multiplayer.MultiplayerScreen -import com.unciv.ui.mapeditor.* import com.unciv.models.metadata.GameSetupInfo +import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter +import com.unciv.ui.mapeditor.EditorMapHolder +import com.unciv.ui.mapeditor.MapEditorScreen +import com.unciv.ui.multiplayer.MultiplayerScreen import com.unciv.ui.map.TileGroupMap import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.pickerscreens.ModManagementScreen -import com.unciv.ui.popup.* +import com.unciv.ui.popup.ExitGamePopup +import com.unciv.ui.popup.Popup +import com.unciv.ui.popup.ToastPopup +import com.unciv.ui.popup.closeAllPopups +import com.unciv.ui.popup.hasOpenPopups +import com.unciv.ui.popup.popups import com.unciv.ui.saves.LoadGameScreen import com.unciv.ui.saves.QuickSave -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup import kotlin.math.min diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 073a6f7d57..fe0c9ed3d2 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -9,18 +9,16 @@ import com.badlogic.gdx.utils.Align import com.unciv.logic.GameInfo import com.unciv.logic.GameSaver import com.unciv.logic.civilization.PlayerType +import com.unciv.logic.multiplayer.OnlineMultiplayer import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.RulesetCache import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations +import com.unciv.ui.LanguagePickerScreen +import com.unciv.ui.audio.GameSounds import com.unciv.ui.audio.MusicController import com.unciv.ui.audio.MusicMood -import com.unciv.ui.utils.* -import com.unciv.ui.worldscreen.PlayerReadyScreen -import com.unciv.ui.worldscreen.WorldScreen -import com.unciv.logic.multiplayer.OnlineMultiplayer -import com.unciv.ui.LanguagePickerScreen -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.crashhandling.closeExecutors import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable @@ -28,6 +26,11 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.multiplayer.LoadDeepLinkScreen import com.unciv.ui.multiplayer.MultiplayerHelpers import com.unciv.ui.popup.Popup +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.crashhandling.wrapCrashHandlingUnit +import com.unciv.ui.worldscreen.PlayerReadyScreen +import com.unciv.ui.worldscreen.WorldScreen import com.unciv.utils.debug import kotlinx.coroutines.runBlocking import java.util.* @@ -103,6 +106,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { */ settings = gameSaver.getGeneralSettings() // needed for the screen screen = LoadingScreen() // NOT dependent on any atlas or skin + GameSounds.init() musicController = MusicController() // early, but at this point does only copy volume from settings audioExceptionHelper?.installHooks( musicController.getAudioLoopCallback(), @@ -194,23 +198,25 @@ class UncivGame(parameters: UncivGameParameters) : Game() { } private fun tryLoadDeepLinkedGame() = launchCrashHandling("LoadDeepLinkedGame") { - if (deepLinkedMultiplayerGame != null) { + if (deepLinkedMultiplayerGame == null) return@launchCrashHandling + + postCrashHandlingRunnable { + setScreen(LoadDeepLinkScreen()) + } + try { + onlineMultiplayer.loadGame(deepLinkedMultiplayerGame!!) + } catch (ex: Exception) { postCrashHandlingRunnable { - setScreen(LoadDeepLinkScreen()) - } - try { - onlineMultiplayer.loadGame(deepLinkedMultiplayerGame!!) - } catch (ex: Exception) { - postCrashHandlingRunnable { - val mainMenu = MainMenuScreen() - setScreen(mainMenu) - val popup = Popup(mainMenu) - popup.addGoodSizedLabel(MultiplayerHelpers.getLoadExceptionMessage(ex)) - popup.row() - popup.addCloseButton() - popup.open() - } + val mainMenu = MainMenuScreen() + setScreen(mainMenu) + val popup = Popup(mainMenu) + popup.addGoodSizedLabel(MultiplayerHelpers.getLoadExceptionMessage(ex)) + popup.row() + popup.addCloseButton() + popup.open() } + } finally { + deepLinkedMultiplayerGame = null } } @@ -242,7 +248,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { Gdx.input.inputProcessor = null // don't allow ANRs when shutting down, that's silly cancelDiscordEvent?.invoke() - Sounds.clearCache() + SoundPlayer.clearCache() if (::musicController.isInitialized) musicController.gracefulShutdown() // Do allow fade-out closeExecutors() @@ -274,6 +280,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { companion object { lateinit var Current: UncivGame fun isCurrentInitialized() = this::Current.isInitialized + fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.isGameInfoInitialized() && Current.gameInfo.gameId == gameId + fun isDeepLinkedGameLoading() = isCurrentInitialized() && Current.deepLinkedMultiplayerGame != null } } diff --git a/core/src/com/unciv/logic/BarbarianManager.kt b/core/src/com/unciv/logic/BarbarianManager.kt index e5abc55200..1692271e4b 100644 --- a/core/src/com/unciv/logic/BarbarianManager.kt +++ b/core/src/com/unciv/logic/BarbarianManager.kt @@ -8,7 +8,7 @@ import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.metadata.GameSpeed import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.randomWeighted +import com.unciv.ui.utils.extensions.randomWeighted import java.util.* import kotlin.math.max import kotlin.math.min diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index ba737538ac..e9a1b137ca 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -3,9 +3,13 @@ package com.unciv.logic.battle import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.utils.debug import com.unciv.logic.city.CityInfo -import com.unciv.logic.civilization.* +import com.unciv.logic.civilization.AlertType +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.civilization.LocationAction +import com.unciv.logic.civilization.NotificationIcon +import com.unciv.logic.civilization.PlayerType +import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.map.RoadStatus @@ -18,7 +22,8 @@ import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent +import com.unciv.utils.debug import java.util.* import kotlin.math.max import kotlin.math.min diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index f15d6ff561..88b5b280a2 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -8,7 +8,7 @@ import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import java.util.* import kotlin.collections.set import kotlin.math.max diff --git a/core/src/com/unciv/logic/battle/CityCombatant.kt b/core/src/com/unciv/logic/battle/CityCombatant.kt index c258d7a295..e1259fd4be 100644 --- a/core/src/com/unciv/logic/battle/CityCombatant.kt +++ b/core/src/com/unciv/logic/battle/CityCombatant.kt @@ -7,7 +7,7 @@ import com.unciv.models.UncivSound import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.UnitType -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.pow import kotlin.math.roundToInt @@ -54,14 +54,14 @@ class CityCombatant(val city: CityInfo) : ICombatant { // The way all of this adds up... // All ancient techs - 0.5 extra, Classical - 2.7, Medieval - 8, Renaissance - 17.5, // Industrial - 32.4, Modern - 51, Atomic - 72.5, All - 118.3 - + // Garrisoned unit gives up to 20% of strength to city, health-dependant if (cityTile.militaryUnit != null) strength += cityTile.militaryUnit!!.baseUnit().strength * (cityTile.militaryUnit!!.health / 100f) * modConstants.cityStrengthFromGarrison var buildingsStrength = city.cityConstructions.getBuiltBuildings().sumOf { it.cityStrength }.toFloat() val stateForConditionals = StateForConditionals(getCivInfo(), city, ourCombatant = this, combatAction = combatAction) - + for (unique in getCivInfo().getMatchingUniques(UniqueType.BetterDefensiveBuildings, stateForConditionals)) buildingsStrength *= unique.params[0].toPercent() strength += buildingsStrength @@ -70,4 +70,4 @@ class CityCombatant(val city: CityInfo) : ICombatant { } override fun toString() = city.name // for debug -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/battle/MapUnitCombatant.kt b/core/src/com/unciv/logic/battle/MapUnitCombatant.kt index da39208d9c..f925dc40f3 100644 --- a/core/src/com/unciv/logic/battle/MapUnitCombatant.kt +++ b/core/src/com/unciv/logic/battle/MapUnitCombatant.kt @@ -19,8 +19,8 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant { override fun isInvisible(to: CivilizationInfo): Boolean = unit.isInvisible(to) override fun canAttack(): Boolean = unit.canAttack() override fun matchesCategory(category: String) = unit.matchesFilter(category) - override fun getAttackSound() = unit.baseUnit.attackSound.let { - if (it==null) UncivSound.Click else UncivSound.custom(it) + override fun getAttackSound() = unit.baseUnit.attackSound.let { + if (it == null) UncivSound.Click else UncivSound(it) } override fun takeDamage(damage: Int) { @@ -50,10 +50,10 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant { return unit.name+" of "+unit.civInfo.civName } - fun getMatchingUniques(uniqueType: UniqueType, conditionalState: StateForConditionals, checkCivUniques: Boolean): Sequence = + fun getMatchingUniques(uniqueType: UniqueType, conditionalState: StateForConditionals, checkCivUniques: Boolean): Sequence = unit.getMatchingUniques(uniqueType, conditionalState, checkCivUniques) fun hasUnique(uniqueType: UniqueType, conditionalState: StateForConditionals? = null): Boolean = if (conditionalState == null) unit.hasUnique(uniqueType) else unit.hasUnique(uniqueType, conditionalState) -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index df71f3da9f..963dc44b4f 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -6,7 +6,7 @@ import com.unciv.logic.automation.ConstructionAutomation import com.unciv.logic.civilization.AlertType import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.PopupAlert -import com.unciv.logic.map.MapUnit // for Kdoc only +import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Ruleset @@ -21,11 +21,9 @@ import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaCategories import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.withItem -import com.unciv.ui.utils.withoutItem -import com.unciv.ui.worldscreen.unit.UnitActions // for Kdoc only -import java.util.* -import kotlin.collections.ArrayList +import com.unciv.ui.utils.extensions.withItem +import com.unciv.ui.utils.extensions.withoutItem +import com.unciv.ui.worldscreen.unit.UnitActions import kotlin.math.ceil import kotlin.math.roundToInt diff --git a/core/src/com/unciv/logic/city/CityExpansionManager.kt b/core/src/com/unciv/logic/city/CityExpansionManager.kt index bdf80a11b0..aadab64af7 100644 --- a/core/src/com/unciv/logic/city/CityExpansionManager.kt +++ b/core/src/com/unciv/logic/city/CityExpansionManager.kt @@ -7,9 +7,9 @@ import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.unique.LocalUniqueCache import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.toPercent -import com.unciv.ui.utils.withItem -import com.unciv.ui.utils.withoutItem +import com.unciv.ui.utils.extensions.toPercent +import com.unciv.ui.utils.extensions.withItem +import com.unciv.ui.utils.extensions.withoutItem import kotlin.math.max import kotlin.math.pow import kotlin.math.roundToInt @@ -64,18 +64,18 @@ class CityExpansionManager { val baseCost = 50 val distanceFromCenter = tileInfo.aerialDistanceTo(cityInfo.getCenterTile()) var cost = baseCost * (distanceFromCenter - 1) + tilesClaimed() * 5.0 - + for (unique in cityInfo.getMatchingUniques(UniqueType.TileCostPercentage)) { if (cityInfo.matchesFilter(unique.params[1])) cost *= unique.params[0].toPercent() } - + return cost.roundToInt() } fun getChoosableTiles() = cityInfo.getCenterTile().getTilesInDistance(5) .filter { it.getOwner() == null } - + fun chooseNewTileToOwn(): TileInfo? { // Technically, in the original a random tile with the lowest score was selected // However, doing this requires either caching it, which is way more work, diff --git a/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt b/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt index 9793f17f7f..63578ab9f5 100644 --- a/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt +++ b/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt @@ -2,7 +2,6 @@ import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.utils.debug import com.unciv.logic.battle.Battle import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.NotificationIcon @@ -12,7 +11,8 @@ import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeType import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.withoutItem +import com.unciv.ui.utils.extensions.withoutItem +import com.unciv.utils.debug import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt diff --git a/core/src/com/unciv/logic/city/CityReligion.kt b/core/src/com/unciv/logic/city/CityReligion.kt index 1c26ccdf24..bee03ae21d 100644 --- a/core/src/com/unciv/logic/city/CityReligion.kt +++ b/core/src/com/unciv/logic/city/CityReligion.kt @@ -7,13 +7,13 @@ import com.unciv.models.Religion import com.unciv.models.metadata.GameSpeed import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent class CityInfoReligionManager { @Transient lateinit var cityInfo: CityInfo - - // This needs to be kept track of for the + + // This needs to be kept track of for the // "[Stats] when a city adopts this religion for the first time" unique val religionsAtSomePointAdopted: HashSet = hashSetOf() @@ -21,7 +21,7 @@ class CityInfoReligionManager { // Cached because using `updateNumberOfFollowers` to get this value resulted in many calls @Transient private val followers: Counter = Counter() - + @delegate:Transient private val pressureFromAdjacentCities: Int by lazy { when (cityInfo.civInfo.gameInfo.gameParameters.gameSpeed) { @@ -31,13 +31,13 @@ class CityInfoReligionManager { GameSpeed.Marathon -> 2 } } - - var religionThisIsTheHolyCityOf: String? = null - + + var religionThisIsTheHolyCityOf: String? = null + init { clearAllPressures() } - + fun clone(): CityInfoReligionManager { val toReturn = CityInfoReligionManager() toReturn.cityInfo = cityInfo @@ -47,7 +47,7 @@ class CityInfoReligionManager { toReturn.religionThisIsTheHolyCityOf = religionThisIsTheHolyCityOf return toReturn } - + fun setTransients(cityInfo: CityInfo) { this.cityInfo = cityInfo // We don't need to check for changes in the majority religion, and as this @@ -55,33 +55,33 @@ class CityInfoReligionManager { // have any effect updateNumberOfFollowers(false) } - + fun endTurn() { getAffectedBySurroundingCities() } - + fun getUniques(): Sequence { val majorityReligion = getMajorityReligion() ?: return sequenceOf() return majorityReligion.getFollowerUniques() } - + fun getPressures(): Counter = pressures.clone() - + private fun clearAllPressures() { pressures.clear() // We add pressure for following no religion - // Basically used as a failsafe so that there is always some religion, + // Basically used as a failsafe so that there is always some religion, // and we don't suddenly divide by 0 somewhere - // Should be removed when updating the followers so it never becomes the majority religion, + // Should be removed when updating the followers so it never becomes the majority religion, // `null` is used for that instead. pressures.add(Constants.noReligionName, 100) } - + fun addPressure(religionName: String, amount: Int, shouldUpdateFollowers: Boolean = true) { if (!cityInfo.civInfo.gameInfo.isReligionEnabled()) return // No religion, no pressures pressures.add(religionName, amount) - + if (shouldUpdateFollowers) { updateNumberOfFollowers(shouldUpdateFollowers) } @@ -96,12 +96,12 @@ class CityInfoReligionManager { if (pressureFromAtheism != null) pressures[Constants.noReligionName] = pressureFromAtheism updateNumberOfFollowers() } - + fun updatePressureOnPopulationChange(populationChangeAmount: Int) { val majorityReligion = if (getMajorityReligionName() != null) getMajorityReligionName()!! else Constants.noReligionName - + if (populationChangeAmount > 0) { addPressure(majorityReligion, 100 * populationChangeAmount) } else { @@ -112,18 +112,18 @@ class CityInfoReligionManager { private fun triggerReligionAdoption(newMajorityReligion: String) { val newMajorityReligionObject = cityInfo.civInfo.gameInfo.religions[newMajorityReligion]!! cityInfo.civInfo.addNotification("Your city [${cityInfo.name}] was converted to [${newMajorityReligionObject.getReligionDisplayName()}]!", cityInfo.location, NotificationIcon.Faith) - + if (newMajorityReligion in religionsAtSomePointAdopted) return - + val religionOwningCiv = newMajorityReligionObject.getFounder() if (religionOwningCiv.hasUnique(UniqueType.StatsWhenAdoptingReligionSpeed) || religionOwningCiv.hasUnique(UniqueType.StatsWhenAdoptingReligion)) { - val statsGranted = + val statsGranted = ( - religionOwningCiv.getMatchingUniques(UniqueType.StatsWhenAdoptingReligionSpeed) + religionOwningCiv.getMatchingUniques(UniqueType.StatsWhenAdoptingReligionSpeed) + religionOwningCiv.getMatchingUniques(UniqueType.StatsWhenAdoptingReligion) ).map { it.stats } .reduce { acc, stats -> acc + stats } - + for ((key, value) in statsGranted) religionOwningCiv.addStat(key, value.toInt()) if (cityInfo.location in religionOwningCiv.exploredTiles) @@ -140,12 +140,12 @@ class CityInfoReligionManager { } religionsAtSomePointAdopted.add(newMajorityReligion) } - + private fun updateNumberOfFollowers(checkForReligionAdoption: Boolean = true) { - val oldMajorityReligion = + val oldMajorityReligion = if (checkForReligionAdoption) getMajorityReligionName() else null - + followers.clear() if (cityInfo.population.population <= 0) return @@ -172,7 +172,7 @@ class CityInfoReligionManager { remainders[largestRemainder.key] = 0f unallocatedPopulation -= 1 } - + followers.remove(Constants.noReligionName) if (checkForReligionAdoption) { @@ -182,24 +182,24 @@ class CityInfoReligionManager { } } } - + fun getNumberOfFollowers(): Counter { return followers.clone() } - + fun getFollowersOf(religion: String): Int? { return followers[religion] } - + fun getFollowersOfMajorityReligion(): Int { val majorityReligion = getMajorityReligionName() ?: return 0 return followers[majorityReligion]!! } - + fun getFollowersOfOtherReligionsThan(religion: String): Int { return followers.filterNot { it.key == religion }.values.sum() } - + /** Removes all pantheons except for the one founded by the current owner of the city * Should be called whenever a city changes hands, e.g. conquering and trading */ @@ -207,7 +207,7 @@ class CityInfoReligionManager { for (pressure in pressures.keys.toList()) { // Copy the keys because we might modify if (pressure == Constants.noReligionName) continue val correspondingReligion = cityInfo.civInfo.gameInfo.religions[pressure]!! - if (correspondingReligion.isPantheon() + if (correspondingReligion.isPantheon() && correspondingReligion.foundingCivName != cityInfo.civInfo.civName ) { pressures.remove(pressure) @@ -225,7 +225,7 @@ class CityInfoReligionManager { else -> null } } - + fun getMajorityReligion(): Religion? { return cityInfo.civInfo.gameInfo.religions[getMajorityReligionName()] } @@ -233,40 +233,40 @@ class CityInfoReligionManager { private fun getAffectedBySurroundingCities() { if (!cityInfo.civInfo.gameInfo.isReligionEnabled()) return // No religion, no spreading // We don't update the amount of followers yet, as only the end result should matter - // If multiple religions would become the majority religion due to pressure, + // If multiple religions would become the majority religion due to pressure, // this will make it so we only receive a notification for the last one. // Also, doing it like this increases performance :D if (cityInfo.isHolyCity()) { addPressure(religionThisIsTheHolyCityOf!!,5 * pressureFromAdjacentCities, false) } - + val allCitiesWithinSpreadRange = cityInfo.civInfo.gameInfo.getCities() .filter { - it != cityInfo - && it.getCenterTile().aerialDistanceTo(cityInfo.getCenterTile()) <= it.religion.getSpreadRange() + it != cityInfo + && it.getCenterTile().aerialDistanceTo(cityInfo.getCenterTile()) <= it.religion.getSpreadRange() } for (city in allCitiesWithinSpreadRange) { val majorityReligionOfCity = city.religion.getMajorityReligionName() ?: continue if (!cityInfo.civInfo.gameInfo.religions[majorityReligionOfCity]!!.isMajorReligion()) continue - addPressure(majorityReligionOfCity, city.religion.pressureAmountToAdjacentCities(cityInfo), false) + addPressure(majorityReligionOfCity, city.religion.pressureAmountToAdjacentCities(cityInfo), false) } - + updateNumberOfFollowers() } private fun getSpreadRange(): Int { var spreadRange = 10 - + for (unique in cityInfo.getLocalMatchingUniques(UniqueType.ReligionSpreadDistance)) { spreadRange += unique.params[0].toInt() } - + if (getMajorityReligion() != null) { for (unique in getMajorityReligion()!!.getFounder().getMatchingUniques(UniqueType.ReligionSpreadDistance)) spreadRange += unique.params[0].toInt() } - + return spreadRange } @@ -297,12 +297,12 @@ class CityInfoReligionManager { if (cityInfo.getCenterTile().civilianUnit?.name == "Inquisitor") return true return false } - + private fun pressureAmountToAdjacentCities(pressuredCity: CityInfo): Int { var pressure = pressureFromAdjacentCities.toFloat() // Follower beliefs of this religion - for (unique in cityInfo.getLocalMatchingUniques(UniqueType.NaturalReligionSpreadStrength)) { + for (unique in cityInfo.getLocalMatchingUniques(UniqueType.NaturalReligionSpreadStrength)) { if (pressuredCity.matchesFilter(unique.params[1])) pressure *= unique.params[0].toPercent() } @@ -316,4 +316,4 @@ class CityInfoReligionManager { return pressure.toInt() } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index 08f1e516b7..92f24e2253 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -9,12 +9,16 @@ import com.unciv.models.Counter import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.GlobalUniques import com.unciv.models.ruleset.ModOptionsConstants -import com.unciv.models.ruleset.unique.* +import com.unciv.models.ruleset.unique.LocalUniqueCache +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueTarget +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat import com.unciv.models.stats.StatMap import com.unciv.models.stats.Stats -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.min diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index ad178f14e1..9162c44610 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -7,7 +7,7 @@ import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.INamed import com.unciv.models.stats.Stat import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.pow import kotlin.math.roundToInt @@ -47,7 +47,7 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { val rejectionReasons = getRejectionReasons(cityConstructions) return rejectionReasons.all { it.rejectionReason == RejectionReason.Unbuildable } } - + fun canBePurchasedWithAnyStat(cityInfo: CityInfo): Boolean { return Stat.values().any { canBePurchasedWithStat(cityInfo, it) } } @@ -61,7 +61,7 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) - + // Can be purchased for [amount] [Stat] [cityFilter] val lowestCostUnique = getMatchingUniques(UniqueType.CanBePurchasedForAmountStat, conditionalState) .filter { it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) } @@ -145,7 +145,7 @@ class RejectionReasons: HashSet() { RejectionReason.NoPlaceToPutUnit, ) } -} +} enum class RejectionReason(val shouldShow: Boolean, val errorMessage: String) { @@ -197,7 +197,7 @@ enum class RejectionReason(val shouldShow: Boolean, val errorMessage: String) { NoSettlerForOneCityPlayers(false, "No settlers for city-states or one-city challengers"), NoPlaceToPutUnit(true, "No space to place this unit"); - + fun toInstance(errorMessage: String = this.errorMessage, shouldShow: Boolean = this.shouldShow): RejectionReasonInstance { return RejectionReasonInstance(this, errorMessage, shouldShow) diff --git a/core/src/com/unciv/logic/city/PopulationManager.kt b/core/src/com/unciv/logic/city/PopulationManager.kt index 6e88d4d1c7..982b9e21f6 100644 --- a/core/src/com/unciv/logic/city/PopulationManager.kt +++ b/core/src/com/unciv/logic/city/PopulationManager.kt @@ -2,14 +2,13 @@ package com.unciv.logic.city import com.unciv.logic.automation.Automation import com.unciv.logic.civilization.NotificationIcon -import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.map.TileInfo import com.unciv.models.Counter import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat -import com.unciv.ui.utils.toPercent -import com.unciv.ui.utils.withItem -import com.unciv.ui.utils.withoutItem +import com.unciv.ui.utils.extensions.toPercent +import com.unciv.ui.utils.extensions.withItem +import com.unciv.ui.utils.extensions.withoutItem import kotlin.math.floor import kotlin.math.pow diff --git a/core/src/com/unciv/logic/civilization/CivInfoStats.kt b/core/src/com/unciv/logic/civilization/CivInfoStats.kt index dbb92c04fc..04ee0d76de 100644 --- a/core/src/com/unciv/logic/civilization/CivInfoStats.kt +++ b/core/src/com/unciv/logic/civilization/CivInfoStats.kt @@ -6,13 +6,13 @@ import com.unciv.logic.map.RoadStatus import com.unciv.models.metadata.BASE_GAME_DURATION_TURNS import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.Policy -import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.StatMap import com.unciv.models.stats.Stats -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.max import kotlin.math.min import kotlin.math.pow diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index e7d5c6252a..4884424823 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -13,11 +13,20 @@ import com.unciv.logic.civilization.RuinsManager.RuinsManager import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyManager import com.unciv.logic.civilization.diplomacy.DiplomaticStatus -import com.unciv.logic.map.* +import com.unciv.logic.map.MapShape +import com.unciv.logic.map.MapUnit +import com.unciv.logic.map.TileInfo +import com.unciv.logic.map.UnitMovementAlgorithms import com.unciv.logic.trade.TradeEvaluation import com.unciv.logic.trade.TradeRequest import com.unciv.models.Counter -import com.unciv.models.ruleset.* +import com.unciv.models.ruleset.Building +import com.unciv.models.ruleset.Difficulty +import com.unciv.models.ruleset.Era +import com.unciv.models.ruleset.ModOptionsConstants +import com.unciv.models.ruleset.Nation +import com.unciv.models.ruleset.Policy +import com.unciv.models.ruleset.Victory import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileResource @@ -29,8 +38,8 @@ import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.utils.MayaCalendar -import com.unciv.ui.utils.toPercent -import com.unciv.ui.utils.withItem +import com.unciv.ui.utils.extensions.toPercent +import com.unciv.ui.utils.extensions.withItem import com.unciv.ui.victoryscreen.RankingType import java.util.* import kotlin.math.max diff --git a/core/src/com/unciv/logic/civilization/GoldenAgeManager.kt b/core/src/com/unciv/logic/civilization/GoldenAgeManager.kt index a2a969b99d..c6cf293de9 100644 --- a/core/src/com/unciv/logic/civilization/GoldenAgeManager.kt +++ b/core/src/com/unciv/logic/civilization/GoldenAgeManager.kt @@ -1,7 +1,7 @@ package com.unciv.logic.civilization import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent class GoldenAgeManager { @Transient @@ -47,4 +47,4 @@ class GoldenAgeManager { } } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index c6bc16b34f..f3588c39b5 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -9,7 +9,7 @@ import com.unciv.models.ruleset.unique.UniqueTriggerActivation import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.equalsPlaceholderText import com.unciv.models.translations.getPlaceholderParameters -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.pow import kotlin.math.roundToInt diff --git a/core/src/com/unciv/logic/civilization/QuestManager.kt b/core/src/com/unciv/logic/civilization/QuestManager.kt index a5a6f52f23..d79b84eabf 100644 --- a/core/src/com/unciv/logic/civilization/QuestManager.kt +++ b/core/src/com/unciv/logic/civilization/QuestManager.kt @@ -17,8 +17,8 @@ import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.tr -import com.unciv.ui.utils.randomWeighted -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.randomWeighted +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.max import kotlin.random.Random diff --git a/core/src/com/unciv/logic/civilization/ReligionManager.kt b/core/src/com/unciv/logic/civilization/ReligionManager.kt index 7e805cfb08..28c757e34b 100644 --- a/core/src/com/unciv/logic/civilization/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/ReligionManager.kt @@ -6,7 +6,7 @@ import com.unciv.models.Religion import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.random.Random class ReligionManager { diff --git a/core/src/com/unciv/logic/civilization/TechManager.kt b/core/src/com/unciv/logic/civilization/TechManager.kt index 73047ec298..4220e5508a 100644 --- a/core/src/com/unciv/logic/civilization/TechManager.kt +++ b/core/src/com/unciv/logic/civilization/TechManager.kt @@ -4,17 +4,15 @@ import com.unciv.logic.city.CityInfo import com.unciv.logic.map.MapSize import com.unciv.logic.map.RoadStatus import com.unciv.models.ruleset.Era +import com.unciv.models.ruleset.tech.Technology import com.unciv.models.ruleset.unique.UniqueMap import com.unciv.models.ruleset.unique.UniqueTriggerActivation -import com.unciv.models.ruleset.tech.Technology import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.ui.utils.MayaCalendar -import com.unciv.ui.utils.toPercent -import com.unciv.ui.utils.withItem +import com.unciv.ui.utils.extensions.toPercent +import com.unciv.ui.utils.extensions.withItem import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashSet import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -49,7 +47,7 @@ class TechManager { var freeTechs = 0 // For calculating score - var repeatingTechsResearched = 0 + var repeatingTechsResearched = 0 /** For calculating Great Scientist yields - see https://civilization.fandom.com/wiki/Great_Scientist_(Civ5) */ var scienceOfLast8Turns = IntArray(8) { 0 } @@ -120,7 +118,7 @@ class TechManager { fun researchOfTech(TechName: String?) = techsInProgress[TechName] ?: 0 // Was once duplicated as fun scienceSpentOnTech(tech: String): Int - + fun remainingScienceToTech(techName: String) = costOfTech(techName) - researchOfTech(techName) fun turnsToTech(techName: String) = when { diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index d88f762b20..93f4d756c0 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -2,13 +2,19 @@ package com.unciv.logic.civilization.diplomacy import com.badlogic.gdx.graphics.Color import com.unciv.Constants -import com.unciv.logic.civilization.* +import com.unciv.logic.civilization.AlertType +import com.unciv.logic.civilization.CityStatePersonality +import com.unciv.logic.civilization.CityStateType +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.civilization.NotificationIcon +import com.unciv.logic.civilization.PlayerType +import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.trade.Trade import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeType -import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.tile.ResourceSupplyList -import com.unciv.ui.utils.toPercent +import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.ceil import kotlin.math.max import kotlin.math.min diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 022e0a59d6..6190da6282 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -12,17 +12,20 @@ import com.unciv.logic.city.RejectionReason import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.LocationAction import com.unciv.logic.civilization.NotificationIcon -import com.unciv.models.helpers.UnitMovementMemoryType import com.unciv.models.UnitActionType +import com.unciv.models.helpers.UnitMovementMemoryType import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TileImprovement -import com.unciv.models.ruleset.unique.* +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueMap +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.stats.Stats -import com.unciv.ui.utils.filterAndLogic -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.filterAndLogic +import com.unciv.ui.utils.extensions.toPercent import java.text.DecimalFormat import kotlin.math.pow diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 8851412c9c..9d5b61125f 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -8,14 +8,21 @@ import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.PlayerType import com.unciv.models.ruleset.Ruleset -import com.unciv.models.ruleset.tile.* -import com.unciv.models.ruleset.unique.* +import com.unciv.models.ruleset.tile.ResourceType +import com.unciv.models.ruleset.tile.Terrain +import com.unciv.models.ruleset.tile.TerrainType +import com.unciv.models.ruleset.tile.TileImprovement +import com.unciv.models.ruleset.tile.TileResource +import com.unciv.models.ruleset.unique.LocalUniqueCache +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.abs import kotlin.math.min import kotlin.random.Random diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt index d1b6e1c2e7..61a63c7f22 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt @@ -5,7 +5,10 @@ import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.logic.HexMath import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.logic.map.* +import com.unciv.logic.map.MapResources +import com.unciv.logic.map.MapShape +import com.unciv.logic.map.TileInfo +import com.unciv.logic.map.TileMap import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.Terrain @@ -17,8 +20,12 @@ import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.translations.equalsPlaceholderText import com.unciv.models.translations.getPlaceholderParameters -import com.unciv.ui.utils.randomWeighted -import kotlin.math.* +import com.unciv.ui.utils.extensions.randomWeighted +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.pow +import kotlin.math.roundToInt import kotlin.random.Random class MapRegions (val ruleset: Ruleset){ diff --git a/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt b/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt index a3807c60fc..5fb70daf59 100644 --- a/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt +++ b/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt @@ -13,7 +13,7 @@ import com.unciv.models.metadata.GameSettings import com.unciv.ui.crashhandling.CRASH_HANDLING_DAEMON_SCOPE import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable -import com.unciv.ui.utils.isLargerThan +import com.unciv.ui.utils.extensions.isLargerThan import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn diff --git a/core/src/com/unciv/logic/multiplayer/OnlineMultiplayerGame.kt b/core/src/com/unciv/logic/multiplayer/OnlineMultiplayerGame.kt index fd8532a447..d828f49325 100644 --- a/core/src/com/unciv/logic/multiplayer/OnlineMultiplayerGame.kt +++ b/core/src/com/unciv/logic/multiplayer/OnlineMultiplayerGame.kt @@ -8,7 +8,7 @@ import com.unciv.logic.event.EventBus import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver import com.unciv.ui.crashhandling.postCrashHandlingRunnable -import com.unciv.ui.utils.isLargerThan +import com.unciv.ui.utils.extensions.isLargerThan import java.io.FileNotFoundException import java.time.Duration import java.time.Instant diff --git a/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt b/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt index fb41be3604..285f2a665a 100644 --- a/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt +++ b/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt @@ -1,10 +1,14 @@ package com.unciv.logic.multiplayer.storage -import com.unciv.utils.debug import com.unciv.json.json -import com.unciv.ui.utils.UncivDateFormat.parseDate +import com.unciv.ui.utils.extensions.UncivDateFormat.parseDate import com.unciv.utils.Log -import java.io.* +import com.unciv.utils.debug +import java.io.BufferedReader +import java.io.DataOutputStream +import java.io.FileNotFoundException +import java.io.InputStream +import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL import java.nio.charset.Charset diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt index d02824d914..98aaafbc3d 100644 --- a/core/src/com/unciv/logic/trade/TradeEvaluation.kt +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -9,7 +9,7 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import com.unciv.ui.victoryscreen.RankingType import kotlin.math.min import kotlin.math.sqrt diff --git a/core/src/com/unciv/models/UncivSound.kt b/core/src/com/unciv/models/UncivSound.kt index 9a2b5387f4..a2ac47302a 100644 --- a/core/src/com/unciv/models/UncivSound.kt +++ b/core/src/com/unciv/models/UncivSound.kt @@ -1,79 +1,34 @@ package com.unciv.models -private enum class UncivSoundConstants (val value: String) { - Click("click"), - Fortify("fortify"), - Promote("promote"), - Upgrade("upgrade"), - Setup("setup"), - Chimes("chimes"), - Coin("coin"), - Choir("choir"), - Fire("fire"), - Policy("policy"), - Paper("paper"), - Whoosh("whoosh"), - Bombard("bombard"), - Slider("slider"), - Construction("construction"), - Swap("swap"), - Silent(""), - Custom("") -} - /** * Represents an Unciv Sound, either from a predefined set or custom with a specified filename. */ -class UncivSound private constructor ( - private val type: UncivSoundConstants, - filename: String? = null -) { +data class UncivSound( /** The base filename without extension. */ - val value: String = filename ?: type.value - -/* - init { - // Checking contract "use non-custom *w/o* filename OR custom *with* one - // Removed due to private constructor - if ((type == UncivSoundConstants.Custom) == filename.isNullOrEmpty()) { - throw IllegalArgumentException("Invalid UncivSound constructor arguments") - } - } -*/ + val fileName: String +) { + /** For serialization */ + private constructor() : this("") companion object { - val Click = UncivSound(UncivSoundConstants.Click) - val Fortify = UncivSound(UncivSoundConstants.Fortify) - val Promote = UncivSound(UncivSoundConstants.Promote) - val Upgrade = UncivSound(UncivSoundConstants.Upgrade) - val Setup = UncivSound(UncivSoundConstants.Setup) - val Chimes = UncivSound(UncivSoundConstants.Chimes) - val Coin = UncivSound(UncivSoundConstants.Coin) - val Choir = UncivSound(UncivSoundConstants.Choir) - val Policy = UncivSound(UncivSoundConstants.Policy) - val Paper = UncivSound(UncivSoundConstants.Paper) - val Whoosh = UncivSound(UncivSoundConstants.Whoosh) - val Bombard = UncivSound(UncivSoundConstants.Bombard) - val Slider = UncivSound(UncivSoundConstants.Slider) - val Construction = UncivSound(UncivSoundConstants.Construction) - val Swap = UncivSound(UncivSoundConstants.Swap) - val Silent = UncivSound(UncivSoundConstants.Silent) - val Fire = UncivSound(UncivSoundConstants.Fire) - /** Creates an UncivSound instance for a custom sound. - * @param filename The base filename without extension. - */ - fun custom(filename: String) = UncivSound(UncivSoundConstants.Custom, filename) + val Bombard = UncivSound("bombard") + val Chimes = UncivSound("chimes") + val Choir = UncivSound("choir") + val Click = UncivSound("click") + val Coin = UncivSound("coin") + val Construction = UncivSound("construction") + val Fire = UncivSound("fire") + val Fortify = UncivSound("fortify") + val Notification1 = UncivSound("notification1") + val Notification2 = UncivSound("notification2") + val Paper = UncivSound("paper") + val Policy = UncivSound("policy") + val Promote = UncivSound("promote") + val Setup = UncivSound("setup") + val Silent = UncivSound("") + val Slider = UncivSound("slider") + val Swap = UncivSound("swap") + val Upgrade = UncivSound("upgrade") + val Whoosh = UncivSound("whoosh") } - - // overrides ensure usability as hash key - override fun hashCode(): Int { - return type.hashCode() xor value.hashCode() - } - override fun equals(other: Any?): Boolean { - if (other == null || other !is UncivSound) return false - if (type != other.type) return false - return type != UncivSoundConstants.Custom || value == other.value - } - - override fun toString(): String = value -} \ No newline at end of file +} diff --git a/core/src/com/unciv/models/UnitAction.kt b/core/src/com/unciv/models/UnitAction.kt index 1b6b212be7..562ecfc929 100644 --- a/core/src/com/unciv/models/UnitAction.kt +++ b/core/src/com/unciv/models/UnitAction.kt @@ -4,11 +4,11 @@ import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.utils.Align -import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.images.ImageGetter import com.unciv.Constants import com.unciv.models.translations.getPlaceholderParameters -import com.unciv.ui.utils.darken +import com.unciv.ui.images.ImageGetter +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.extensions.darken /** Unit Actions - class - carries dynamic data and actual execution. @@ -54,7 +54,7 @@ data class UnitAction( } /** Unit Actions - generic enum with static properties - * + * * @param value _default_ label to display, can be overridden in UnitAction instantiation * @param imageGetter optional lambda to get an Icon - `null` if icon is dependent on outside factors and needs special handling * @param key keyboard binding - can be a [KeyCharAndCode], a [Char], or omitted. @@ -97,7 +97,7 @@ enum class UnitActionType( { imageGetPromote() }, 'o', UncivSound.Promote), Upgrade("Upgrade", null, 'u', UncivSound.Upgrade), - Pillage("Pillage", + Pillage("Pillage", { ImageGetter.getImage("OtherIcons/Pillage") }, 'p'), Paradrop("Paradrop", { ImageGetter.getUnitIcon("Paratrooper") }, 'p'), diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 1de06ae5d7..d2e7180dbd 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -4,12 +4,14 @@ import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx import com.unciv.Constants import com.unciv.UncivGame +import com.unciv.models.UncivSound import com.unciv.logic.multiplayer.FriendList import com.unciv.ui.utils.Fonts import java.text.Collator import java.time.Duration import java.util.* -import kotlin.collections.HashSet +import kotlin.reflect.KClass +import kotlin.reflect.KMutableProperty0 data class WindowState (val width: Int = 900, val height: Int = 600) @@ -167,5 +169,30 @@ class GameSettingsMultiplayer { var statusButtonInSinglePlayer = false var currentGameRefreshDelay = Duration.ofSeconds(10) var allGameRefreshDelay = Duration.ofMinutes(5) + var currentGameTurnNotificationSound: UncivSound = UncivSound.Silent + var otherGameTurnNotificationSound: UncivSound = UncivSound.Silent var hideDropboxWarning = false } + +enum class GameSetting( + val kClass: KClass<*>, + private val propertyGetter: (GameSettings) -> KMutableProperty0<*> +) { +// Uncomment these once they are refactored to send events on change +// MULTIPLAYER_USER_ID(String::class, { it.multiplayer::userId }), +// MULTIPLAYER_SERVER(String::class, { it.multiplayer::server }), +// MULTIPLAYER_STATUSBUTTON_IN_SINGLEPLAYER(Boolean::class, { it.multiplayer::statusButtonInSinglePlayer }), +// MULTIPLAYER_TURN_CHECKER_ENABLED(Boolean::class, { it.multiplayer::turnCheckerEnabled }), +// MULTIPLAYER_TURN_CHECKER_PERSISTENT_NOTIFICATION_ENABLED(Boolean::class, { it.multiplayer::turnCheckerPersistentNotificationEnabled }), +// MULTIPLAYER_HIDE_DROPBOX_WARNING(Boolean::class, { it.multiplayer::hideDropboxWarning }), + MULTIPLAYER_TURN_CHECKER_DELAY(Duration::class, { it.multiplayer::turnCheckerDelay }), + MULTIPLAYER_CURRENT_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::currentGameRefreshDelay }), + MULTIPLAYER_ALL_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::allGameRefreshDelay }), + MULTIPLAYER_CURRENT_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::currentGameTurnNotificationSound }), + MULTIPLAYER_OTHER_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::otherGameTurnNotificationSound }); + + /** **Warning:** It is the obligation of the caller to select the same type [T] that the [kClass] of this property has */ + fun getProperty(settings: GameSettings): KMutableProperty0 { + return propertyGetter(settings) as KMutableProperty0 + } +} diff --git a/core/src/com/unciv/models/metadata/SettingsEvents.kt b/core/src/com/unciv/models/metadata/SettingsEvents.kt new file mode 100644 index 0000000000..1972fd63d8 --- /dev/null +++ b/core/src/com/unciv/models/metadata/SettingsEvents.kt @@ -0,0 +1,13 @@ +package com.unciv.models.metadata + +import com.unciv.logic.event.Event +import com.unciv.models.UncivSound + +/** **Warning:** this event is in the process of completion and **not** used for all settings yet! **Only the settings in [GameSetting] get events sent!** */ +interface SettingsPropertyChanged : Event { + val gameSetting: GameSetting +} + +interface SettingsPropertyUncivSoundChanged : SettingsPropertyChanged { + val value: UncivSound +} diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 3a9fbd4e90..b09822ffc1 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -1,19 +1,30 @@ package com.unciv.models.ruleset -import com.unciv.logic.city.* +import com.unciv.logic.city.CityConstructions +import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.logic.city.RejectionReason +import com.unciv.logic.city.RejectionReasons import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.Counter import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileImprovement -import com.unciv.models.ruleset.unique.* +import com.unciv.models.ruleset.unique.LocalUniqueCache +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueFlag +import com.unciv.models.ruleset.unique.UniqueParameterType +import com.unciv.models.ruleset.unique.UniqueTarget +import com.unciv.models.ruleset.unique.UniqueTriggerActivation +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.getConsumesAmountString -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.getConsumesAmountString +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.pow @@ -156,7 +167,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { if (maintenance != 0 && !isFree) lines += "{Maintenance cost}: $maintenance {Gold}" if (showAdditionalInfo && missingCities.isNotEmpty()) { // Could be red. But IMO that should be done by enabling GDX's ColorMarkupLanguage globally instead of adding a separate label. - lines += "\n" + + lines += "\n" + "[${cityInfo.civInfo.getEquivalentBuilding(missingUnique!!.params[0])}] required:".tr() + " " + missingCities.joinToString(", ") { "{${it.name}}" } // Can't nest square bracket placeholders inside curlies, and don't see any way to define wildcard placeholders. So run translation explicitly on base text. @@ -164,7 +175,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { return lines.joinToString("\n") { it.tr() }.trim() } - fun getStats(city: CityInfo, + fun getStats(city: CityInfo, /* By default, do not cache - if we're getting stats for only one building this isn't efficient. * Only use a cache if it was sent to us from outside, which means we can use the results for other buildings. */ localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)): Stats { @@ -193,12 +204,12 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { fun getStatPercentageBonuses(cityInfo: CityInfo?, localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)): Stats { val stats = percentStatBonus?.clone() ?: Stats() val civInfo = cityInfo?.civInfo ?: return stats // initial stats - + for (unique in localUniqueCache.get("StatPercentFromObject", civInfo.getMatchingUniques(UniqueType.StatPercentFromObject))) { if (matchesFilter(unique.params[2])) stats.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat()) } - + for (unique in localUniqueCache.get("AllStatsPercentFromObject", civInfo.getMatchingUniques(UniqueType.AllStatsPercentFromObject))) { if (!matchesFilter(unique.params[1])) continue for (stat in Stat.values()) { diff --git a/core/src/com/unciv/models/ruleset/Era.kt b/core/src/com/unciv/models/ruleset/Era.kt index db09bd3ed5..24b637a73a 100644 --- a/core/src/com/unciv/models/ruleset/Era.kt +++ b/core/src/com/unciv/models/ruleset/Era.kt @@ -3,10 +3,14 @@ package com.unciv.models.ruleset import com.badlogic.gdx.graphics.Color import com.unciv.logic.civilization.CityStateType import com.unciv.logic.civilization.diplomacy.RelationshipLevel -import com.unciv.models.ruleset.unique.* +import com.unciv.models.ruleset.unique.IHasUniques +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueTarget +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.colorFromRGB +import com.unciv.ui.utils.extensions.colorFromRGB class Era : RulesetObject(), IHasUniques { var eraNumber: Int = -1 diff --git a/core/src/com/unciv/models/ruleset/Nation.kt b/core/src/com/unciv/models/ruleset/Nation.kt index 7acdf00b4f..c1a10e4d10 100644 --- a/core/src/com/unciv/models/ruleset/Nation.kt +++ b/core/src/com/unciv/models/ruleset/Nation.kt @@ -12,7 +12,7 @@ import com.unciv.models.translations.squareBraceRegex import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.colorFromRGB +import com.unciv.ui.utils.extensions.colorFromRGB class Nation : RulesetObject() { var leaderName = "" @@ -37,16 +37,16 @@ class Nation : RulesetObject() { var uniqueText = "" var innerColor: List? = null var startBias = ArrayList() - + var startIntroPart1 = "" var startIntroPart2 = "" /* Properties present in json but not yet implemented: var adjective = ArrayList() */ - + var favoredReligion: String? = null - + @Transient private lateinit var outerColorObject: Color fun getOuterColor(): Color = outerColorObject @@ -118,7 +118,7 @@ class Nation : RulesetObject() { val link = if ('[' !in it.value) it.value else squareBraceRegex.find(it.value)!!.groups[1]!!.value textList += FormattedLine( - (if (it.index == 0) "[Start bias:] " else "") + it.value.tr(), // extra tr because tr cannot nest {[]} + (if (it.index == 0) "[Start bias:] " else "") + it.value.tr(), // extra tr because tr cannot nest {[]} link = "Terrain/$link", indent = if (it.index == 0) 0 else 1, iconCrossed = it.value.startsWith("Avoid ")) @@ -170,7 +170,7 @@ class Nation : RulesetObject() { if (allMercantileResources.isNotEmpty()) { textList += FormattedLine() textList += FormattedLine("The unique luxury is one of:") - allMercantileResources.forEach { + allMercantileResources.forEach { textList += FormattedLine(it.name, it.makeLink(), indent = 1) } } diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 279a985895..1759f79dd4 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -33,7 +33,7 @@ import com.unciv.models.stats.NamedStats import com.unciv.models.stats.Stats import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.tr -import com.unciv.ui.utils.colorFromRGB +import com.unciv.ui.utils.extensions.colorFromRGB import com.unciv.ui.utils.getRelativeTextDistance import com.unciv.utils.Log import com.unciv.utils.debug diff --git a/core/src/com/unciv/models/ruleset/Victory.kt b/core/src/com/unciv/models/ruleset/Victory.kt index eeb94306ba..e6cc166ad6 100644 --- a/core/src/com/unciv/models/ruleset/Victory.kt +++ b/core/src/com/unciv/models/ruleset/Victory.kt @@ -3,12 +3,12 @@ package com.unciv.models.ruleset import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.Constants -import com.unciv.models.stats.INamed import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.Counter +import com.unciv.models.stats.INamed import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.getPlaceholderText -import com.unciv.ui.utils.toTextButton +import com.unciv.ui.utils.extensions.toTextButton enum class MilestoneType(val text: String) { @@ -41,7 +41,7 @@ class Victory : INamed { CityStates, Score, } - + override var name = "" val victoryScreenHeader = "Do things to win!" val hiddenInVictoryScreen = false @@ -57,10 +57,10 @@ class Victory : INamed { parts.add(spaceshipPart, 1) parts } - + val victoryString = "Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself!" val defeatString = "You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory!" - + fun enablesMaxTurns(): Boolean = milestoneObjects.any { it.type == MilestoneType.ScoreAfterTimeOut } fun getThingsToFocus(civInfo: CivilizationInfo): Set = milestoneObjects .filter { !it.hasBeenCompletedBy(civInfo) } @@ -78,7 +78,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor incompleteSpaceshipParts.remove(civInfo.victoryManager.currentsSpaceshipParts) return incompleteSpaceshipParts } - + fun hasBeenCompletedBy(civInfo: CivilizationInfo): Boolean { return when (type!!) { MilestoneType.BuiltBuilding -> @@ -101,9 +101,9 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor && civInfo == civInfo.gameInfo.civilizations.maxByOrNull { it.calculateTotalScore() } } MilestoneType.WorldReligion -> { - civInfo.gameInfo.isReligionEnabled() + civInfo.gameInfo.isReligionEnabled() && civInfo.religionManager.religion != null - && civInfo.gameInfo.civilizations.all { + && civInfo.gameInfo.civilizations.all { it.religionManager.isMajorityReligionForCiv(civInfo.religionManager.religion!!) } } @@ -116,11 +116,11 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor else textButton.color = Color.GRAY return textButton } - + fun getVictoryScreenButtonHeaderText(completed: Boolean, civInfo: CivilizationInfo): String { return when (type!!) { - MilestoneType.BuildingBuiltGlobally, MilestoneType.WinDiplomaticVote, - MilestoneType.ScoreAfterTimeOut, MilestoneType.BuiltBuilding -> + MilestoneType.BuildingBuiltGlobally, MilestoneType.WinDiplomaticVote, + MilestoneType.ScoreAfterTimeOut, MilestoneType.BuiltBuilding -> uniqueDescription MilestoneType.CompletePolicyBranches -> { val amountToDo = params[0] @@ -148,9 +148,9 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() val amountToDo = incompleteSpaceshipParts.sumValues() incompleteSpaceshipParts.remove(completeSpaceshipParts) - + val amountDone = amountToDo - incompleteSpaceshipParts.sumValues() - + "{$uniqueDescription} ($amountDone/$amountToDo)" } MilestoneType.WorldReligion -> { @@ -168,10 +168,10 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor } } } - + fun getVictoryScreenButtons(completionStatus: Victory.CompletionStatus, civInfo: CivilizationInfo): List { val headerButton = getMilestoneButton( - getVictoryScreenButtonHeaderText(completionStatus == Victory.CompletionStatus.Completed, civInfo), + getVictoryScreenButtonHeaderText(completionStatus == Victory.CompletionStatus.Completed, civInfo), completionStatus == Victory.CompletionStatus.Completed ) if (completionStatus == Victory.CompletionStatus.Completed || completionStatus == Victory.CompletionStatus.Incomplete) { @@ -182,12 +182,12 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor val buttons = mutableListOf(headerButton) when (type) { // No extra buttons necessary - MilestoneType.BuiltBuilding, MilestoneType.BuildingBuiltGlobally, - MilestoneType.ScoreAfterTimeOut, MilestoneType.WinDiplomaticVote -> {} + MilestoneType.BuiltBuilding, MilestoneType.BuildingBuiltGlobally, + MilestoneType.ScoreAfterTimeOut, MilestoneType.WinDiplomaticVote -> {} MilestoneType.AddedSSPartsInCapital -> { val completedSpaceshipParts = civInfo.victoryManager.currentsSpaceshipParts val incompleteSpaceshipParts = getIncompleteSpaceshipParts(civInfo) - + for (part in completedSpaceshipParts) { repeat(part.value) { buttons.add(getMilestoneButton(part.key, true)) @@ -199,7 +199,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor } } } - + MilestoneType.DestroyAllPlayers -> { val majorCivs = civInfo.gameInfo.civilizations.filter { it.isMajorCiv() && it != civInfo } for (civ in majorCivs) { @@ -209,7 +209,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor buttons.add(getMilestoneButton(milestoneText, !civ.isAlive())) } } - + MilestoneType.CaptureAllCapitals -> { val originalCapitals = civInfo.gameInfo.getCities().filter { it.isOriginalCapital } for (city in originalCapitals) { @@ -219,7 +219,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor buttons.add(getMilestoneButton(milestoneText, city.civInfo == civInfo)) } } - + MilestoneType.CompletePolicyBranches -> { for (branch in civInfo.gameInfo.ruleSet.policyBranches.values) { val finisher = branch.policies.last().name @@ -241,7 +241,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor } return buttons } - + fun getFocus(civInfo: CivilizationInfo): Victory.Focus { val ruleset = civInfo.gameInfo.ruleSet return when (type!!) { @@ -275,4 +275,4 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor MilestoneType.WorldReligion -> Victory.Focus.Faith } } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index 1f0a113cfe..a5aa6cbb76 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -9,7 +9,7 @@ import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.civilopedia.FormattedLine -import com.unciv.ui.utils.colorFromRGB +import com.unciv.ui.utils.extensions.colorFromRGB class Terrain : RulesetStatsObject() { @@ -43,7 +43,7 @@ class Terrain : RulesetStatsObject() { // Shouldn't this just be a lazy property so it's automatically cached? fun isRough(): Boolean = hasUnique(UniqueType.RoughTerrain) - + /** Tests base terrains, features and natural wonders whether they should be treated as Land/Water. * Currently only used for civilopedia display, as other code can test the tile itself. */ diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index 80459e7d67..1420e6303b 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -13,9 +13,8 @@ import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import com.unciv.ui.worldscreen.unit.UnitActions -import java.util.* import kotlin.math.roundToInt class TileImprovement : RulesetStatsObject() { diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index 3f3509371f..ba0bd6e5fd 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -6,9 +6,10 @@ import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.tile.ResourceType +import com.unciv.models.ruleset.unique.UniqueParameterType.Companion.guessTypeForTranslationWriter import com.unciv.models.stats.Stat -import com.unciv.models.translations.TranslationFileWriter // for Kdoc only -import com.unciv.ui.utils.filterCompositeLogic +import com.unciv.models.translations.TranslationFileWriter +import com.unciv.ui.utils.extensions.filterCompositeLogic // 'region' names beginning with an underscore are used here for a prettier "Structure window" - they go in front ot the rest. diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 1e65aa41bb..b9c4aa8c8f 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -1,6 +1,10 @@ package com.unciv.models.ruleset.unit -import com.unciv.logic.city.* +import com.unciv.logic.city.CityConstructions +import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.logic.city.RejectionReason +import com.unciv.logic.city.RejectionReasons import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.models.ruleset.Ruleset @@ -13,12 +17,9 @@ import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts -import com.unciv.ui.utils.filterAndLogic -import com.unciv.ui.utils.getConsumesAmountString -import com.unciv.ui.utils.toPercent -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.collections.HashSet +import com.unciv.ui.utils.extensions.filterAndLogic +import com.unciv.ui.utils.extensions.getConsumesAmountString +import com.unciv.ui.utils.extensions.toPercent import kotlin.math.pow // This is BaseUnit because Unit is already a base Kotlin class and to avoid mixing the two up @@ -46,8 +47,8 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { var promotions = HashSet() var obsoleteTech: String? = null var upgradesTo: String? = null - val specialUpgradesTo: String? by lazy { - getMatchingUniques(UniqueType.RuinsUpgrade).map { it.params[0] }.firstOrNull() + val specialUpgradesTo: String? by lazy { + getMatchingUniques(UniqueType.RuinsUpgrade).map { it.params[0] }.firstOrNull() } var replaces: String? = null var uniqueTo: String? = null @@ -238,8 +239,8 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { unit.owner = civInfo.civName // must be after setting name & civInfo because it sets the baseUnit according to the name - // and the civInfo is required for using `hasUnique` when determining its movement options - unit.setTransients(civInfo.gameInfo.ruleSet) + // and the civInfo is required for using `hasUnique` when determining its movement options + unit.setTransients(civInfo.gameInfo.ruleSet) return unit } @@ -259,7 +260,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { override fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean { if (cityInfo == null) return super.canBePurchasedWithStat(cityInfo, stat) val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) - + return (cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState) .any { it.params[2] == stat.name @@ -394,17 +395,17 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { val rejectionReasons = RejectionReasons() val ruleSet = civInfo.gameInfo.ruleSet - if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) - rejectionReasons.add(RejectionReason.RequiresTech.toInstance("$requiredTech not researched")) + if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) + rejectionReasons.add(RejectionReason.RequiresTech.toInstance("$requiredTech not researched")) if (obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) rejectionReasons.add(RejectionReason.Obsoleted.toInstance("Obsolete by $obsoleteTech")) - if (uniqueTo != null && uniqueTo != civInfo.civName) + if (uniqueTo != null && uniqueTo != civInfo.civName) rejectionReasons.add(RejectionReason.UniqueToOtherNation.toInstance("Unique to $uniqueTo")) if (ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name }) rejectionReasons.add(RejectionReason.ReplacedByOurUnique.toInstance("Our unique unit replaces this")) - if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled && isNuclearWeapon()) + if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled && isNuclearWeapon()) rejectionReasons.add(RejectionReason.DisabledBySetting) for (unique in uniqueObjects) { @@ -481,11 +482,11 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { // If this unit has special abilities that need to be kept track of, start doing so here if (unit.hasUnique(UniqueType.ReligiousUnit) && civInfo.gameInfo.isReligionEnabled()) { - unit.religion = + unit.religion = if (unit.hasUnique(UniqueType.TakeReligionOverBirthCity)) civInfo.religionManager.religion?.name else cityConstructions.cityInfo.religion.getMajorityReligionName() - + unit.setupAbilityUses(cityConstructions.cityInfo) } @@ -509,7 +510,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { if (unit.matchesFilter(unique.params[0])) XP += unique.params[1].toInt() } - unit.promotions.XP = XP + unit.promotions.XP = XP for (unique in cityConstructions.cityInfo.getMatchingUniques(UniqueType.UnitStartingPromotions) .filter { cityConstructions.cityInfo.matchesFilter(it.params[1]) }) { @@ -613,7 +614,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { && (uniqueObjects + getType().uniqueObjects) .any { it.isOfType(UniqueType.Strength) && it.params[0].toInt() > 0 - && it.conditionals.any { conditional -> conditional.isOfType(UniqueType.ConditionalVsCity) } + && it.conditionals.any { conditional -> conditional.isOfType(UniqueType.ConditionalVsCity) } } ) diff --git a/core/src/com/unciv/models/translations/Translations.kt b/core/src/com/unciv/models/translations/Translations.kt index c22253dd19..db84142dcb 100644 --- a/core/src/com/unciv/models/translations/Translations.kt +++ b/core/src/com/unciv/models/translations/Translations.kt @@ -8,9 +8,8 @@ import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.utils.Log import com.unciv.utils.debug +import java.time.temporal.ChronoUnit import java.util.* -import kotlin.collections.HashMap -import kotlin.collections.LinkedHashSet /** * This collection holds all translations for the game. diff --git a/core/src/com/unciv/ui/LanguagePickerScreen.kt b/core/src/com/unciv/ui/LanguagePickerScreen.kt index 2714aa9733..2c5cfa9ba4 100644 --- a/core/src/com/unciv/ui/LanguagePickerScreen.kt +++ b/core/src/com/unciv/ui/LanguagePickerScreen.kt @@ -2,12 +2,12 @@ package com.unciv.ui import com.unciv.MainMenuScreen import com.unciv.models.translations.tr +import com.unciv.ui.options.OptionsPopup import com.unciv.ui.pickerscreens.PickerScreen -import com.unciv.ui.utils.enable -import com.unciv.ui.utils.onClick import com.unciv.ui.utils.LanguageTable import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables -import com.unciv.ui.options.OptionsPopup +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick /** A [PickerScreen] to select a language, used once on the initial run after a fresh install. * After that, [OptionsPopup] provides the functionality. diff --git a/core/src/com/unciv/ui/UncivStage.kt b/core/src/com/unciv/ui/UncivStage.kt index 9833c16949..06a1944346 100644 --- a/core/src/com/unciv/ui/UncivStage.kt +++ b/core/src/com/unciv/ui/UncivStage.kt @@ -4,8 +4,8 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.g2d.Batch import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.utils.viewport.Viewport -import com.unciv.ui.utils.wrapCrashHandling -import com.unciv.ui.utils.wrapCrashHandlingUnit +import com.unciv.ui.crashhandling.wrapCrashHandling +import com.unciv.ui.crashhandling.wrapCrashHandlingUnit /** Main stage for the game. Safely brings the game to a [CrashScreen] if any event handlers throw an exception or an error that doesn't get otherwise handled. */ diff --git a/core/src/com/unciv/ui/audio/GameSounds.kt b/core/src/com/unciv/ui/audio/GameSounds.kt new file mode 100644 index 0000000000..8cb477cf37 --- /dev/null +++ b/core/src/com/unciv/ui/audio/GameSounds.kt @@ -0,0 +1,43 @@ +package com.unciv.ui.audio + +import com.unciv.UncivGame +import com.unciv.logic.event.EventBus +import com.unciv.logic.multiplayer.MultiplayerGameUpdated +import com.unciv.logic.multiplayer.isUsersTurn +import com.unciv.models.metadata.SettingsPropertyUncivSoundChanged + +/** + * Controls which sounds should be played when something happens while playing the game. + */ +object GameSounds { + private val events = EventBus.EventReceiver() + private val settings get() = UncivGame.Current.settings + private val mpSettings get() = settings.multiplayer + + /** + * Has to be called for sounds to be played. + */ + fun init() { + playSettingsSounds() + playMultiplayerTurnNotification() + } + + private fun playSettingsSounds() { + events.receive(SettingsPropertyUncivSoundChanged::class) { + SoundPlayer.play(it.value) + } + } + + private fun playMultiplayerTurnNotification() { + events.receive(MultiplayerGameUpdated::class, { it.preview.isUsersTurn() }) { + if (UncivGame.isDeepLinkedGameLoading()) return@receive // This means we already arrived here through a turn notification, no need to notify again + val gameId = it.preview.gameId + val sound = if (UncivGame.isCurrentGame(gameId)) { + mpSettings.currentGameTurnNotificationSound + } else { + mpSettings.otherGameTurnNotificationSound + } + SoundPlayer.play(sound) + } + } +} diff --git a/core/src/com/unciv/ui/audio/Sounds.kt b/core/src/com/unciv/ui/audio/SoundPlayer.kt similarity index 94% rename from core/src/com/unciv/ui/audio/Sounds.kt rename to core/src/com/unciv/ui/audio/SoundPlayer.kt index 7c2ab2d4a4..92b41b0d38 100644 --- a/core/src/com/unciv/ui/audio/Sounds.kt +++ b/core/src/com/unciv/ui/audio/SoundPlayer.kt @@ -43,7 +43,7 @@ import java.io.File * a handful of them in memory we should be able to get away with keeping them alive for the * app lifetime - and we do dispose them when the app is disposed. */ -object Sounds { +object SoundPlayer { @Suppress("EnumEntryName") private enum class SupportedExtensions { mp3, ogg, wav } // Per Gdx docs, no aac/m4a @@ -117,7 +117,7 @@ object Sounds { else GetSoundResult(soundMap[sound]!!, false) // Not cached - try loading it - val fileName = sound.value + val fileName = sound.fileName var file: FileHandle? = null for ( (modFolder, extension) in getFolders().flatMap { // This is essentially a cross join. To operate on all combinations, we pack both lambda @@ -134,12 +134,12 @@ object Sounds { @Suppress("LiftReturnOrAssignment") if (file == null || !file.exists()) { - debug("Sound %s not found!", sound.value) + debug("Sound %s not found!", sound.fileName) // remember that the actual file is missing soundMap[sound] = null return null } else { - debug("Sound %s loaded from %s", sound.value, file.path()) + debug("Sound %s loaded from %s", sound.fileName, file.path()) val newSound = Gdx.audio.newSound(file) // Store Sound for reuse soundMap[sound] = newSound @@ -147,7 +147,11 @@ object Sounds { } } - /** Find, cache and play a Sound + /** + * Find, cache and play a Sound. + * + * **Attention:** The [GameSounds] object has been set up to control playing all sounds of the game. Chances are that you shouldn't be calling this method + * from anywhere but [GameSounds]. * * Sources are mods from a loaded game, then mods marked as permanent audiovisual, * and lastly Unciv's own assets/sounds. Will fail silently if the sound file cannot be found. diff --git a/core/src/com/unciv/ui/cityscreen/CitizenManagementTable.kt b/core/src/com/unciv/ui/cityscreen/CitizenManagementTable.kt index c97054c8ff..1aeacaef28 100644 --- a/core/src/com/unciv/ui/cityscreen/CitizenManagementTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CitizenManagementTable.kt @@ -6,7 +6,10 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.Constants import com.unciv.logic.city.CityFocus import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin) { val city = cityScreen.city diff --git a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt index 5f7744f359..57d273d2a8 100644 --- a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt @@ -8,7 +8,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.Constants -import com.unciv.logic.city.* +import com.unciv.logic.city.CityConstructions +import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.IConstruction +import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.logic.city.PerpetualConstruction import com.unciv.logic.map.TileInfo import com.unciv.models.UncivSound import com.unciv.models.ruleset.Building @@ -16,15 +20,29 @@ import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat import com.unciv.models.translations.tr -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.Popup import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.popup.closeAllPopups -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.addCell +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.brighten +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.getConsumesAmountString +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.packIfNeeded +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.max import kotlin.math.min import com.unciv.ui.utils.AutoScrollPane as ScrollPane @@ -409,7 +427,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { } cityScreen.stopPickTileForCreatesOneImprovement() - Sounds.play(getConstructionSound(construction)) + SoundPlayer.play(getConstructionSound(construction)) cityConstructions.addToQueue(construction.name) if (!construction.shouldBeDisplayed(cityConstructions)) // For buildings - unlike units which can be queued multiple times @@ -533,7 +551,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { stat: Stat = Stat.Gold, tile: TileInfo? = null ) { - Sounds.play(stat.purchaseSound) + SoundPlayer.play(stat.purchaseSound) val city = cityScreen.city if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false, stat, tile)) { Popup(cityScreen).apply { diff --git a/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt b/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt index 8ed9f0a02e..c3c95a02b8 100644 --- a/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt @@ -19,7 +19,16 @@ import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.popup.closeAllPopups -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.text.DecimalFormat import com.unciv.ui.utils.AutoScrollPane as ScrollPane diff --git a/core/src/com/unciv/ui/cityscreen/CityReligionInfoTable.kt b/core/src/com/unciv/ui/cityscreen/CityReligionInfoTable.kt index 82fdb49dd9..010b8eae29 100644 --- a/core/src/com/unciv/ui/cityscreen/CityReligionInfoTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityReligionInfoTable.kt @@ -12,7 +12,12 @@ import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter import com.unciv.ui.overviewscreen.EmpireOverviewCategories import com.unciv.ui.overviewscreen.EmpireOverviewScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.addSeparatorVertical +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel class CityReligionInfoTable( private val religionManager: CityInfoReligionManager, @@ -67,7 +72,7 @@ class CityReligionInfoTable( private fun getIconAndLabel(religionName: String?) = getIconAndLabel(gameInfo.religions[religionName]) private fun getIconAndLabel(religion: Religion?): Pair { - return if (religion == null) "Religion" to "None" + return if (religion == null) "Religion" to "None" else religion.getIconName() to religion.getReligionDisplayName() } private fun linkedReligionIcon(iconName: String, religion: String?): IconCircleGroup { diff --git a/core/src/com/unciv/ui/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/cityscreen/CityScreen.kt index 59849655b9..dc692557bc 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreen.kt @@ -19,7 +19,13 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.map.TileGroupMap import com.unciv.ui.popup.ToastPopup import com.unciv.ui.tilegroups.TileSetStrings -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ZoomableScrollPane +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.packIfNeeded +import com.unciv.ui.utils.extensions.toTextButton class CityScreen( internal val city: CityInfo, diff --git a/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt b/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt index b8875268a7..85944dc4d1 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt @@ -6,7 +6,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.AskTextPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel /** Widget for the City Screen - * the panel at bottom center showing the city name and offering arrows to cycle through the cities. */ diff --git a/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt b/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt index afd686a526..53fe9946e3 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt @@ -8,15 +8,20 @@ import com.unciv.models.UncivSound import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.tr -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.FormattedLine.IconDisplay import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.popup.closeAllPopups -import com.unciv.ui.utils.* import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.roundToInt class CityScreenTileTable(private val cityScreen: CityScreen): Table() { @@ -91,7 +96,7 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() { } /** Ask whether user wants to buy [selectedTile] for gold. - * + * * Used from onClick and keyboard dispatch, thus only minimal parameters are passed, * and it needs to do all checks and the sound as appropriate. */ @@ -108,7 +113,7 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() { YesNoPopup( purchasePrompt, action = { - Sounds.play(UncivSound.Coin) + SoundPlayer.play(UncivSound.Coin) city.expansion.buyTile(selectedTile) // preselect the next tile on city screen rebuild so bulk buying can go faster UncivGame.Current.setScreen(CityScreen(city, initSelectedTile = city.expansion.chooseNewTileToOwn())) @@ -142,4 +147,4 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() { } return statsTable } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt index 1e982505d4..9e9aa0486c 100644 --- a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt @@ -14,7 +14,13 @@ import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.ceil import kotlin.math.round import com.unciv.ui.utils.AutoScrollPane as ScrollPane diff --git a/core/src/com/unciv/ui/cityscreen/CityTileGroup.kt b/core/src/com/unciv/ui/cityscreen/CityTileGroup.kt index b07e012e41..4afa5de7a1 100644 --- a/core/src/com/unciv/ui/cityscreen/CityTileGroup.kt +++ b/core/src/com/unciv/ui/cityscreen/CityTileGroup.kt @@ -4,11 +4,11 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.utils.Align import com.unciv.logic.city.CityInfo import com.unciv.logic.map.TileInfo +import com.unciv.ui.images.ImageGetter import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.centerX +import com.unciv.ui.utils.extensions.centerX class CityTileGroup(private val city: CityInfo, tileInfo: TileInfo, tileSetStrings: TileSetStrings) : TileGroup(tileInfo,tileSetStrings) { diff --git a/core/src/com/unciv/ui/cityscreen/ConstructionInfoTable.kt b/core/src/com/unciv/ui/cityscreen/ConstructionInfoTable.kt index 196363096e..4a4f2bf5a2 100644 --- a/core/src/com/unciv/ui/cityscreen/ConstructionInfoTable.kt +++ b/core/src/com/unciv/ui/cityscreen/ConstructionInfoTable.kt @@ -13,7 +13,10 @@ import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle class ConstructionInfoTable(val cityScreen: CityScreen): Table() { private val selectedConstructionTable = Table() diff --git a/core/src/com/unciv/ui/cityscreen/SpecialistAllocationTable.kt b/core/src/com/unciv/ui/cityscreen/SpecialistAllocationTable.kt index f2a495c332..e6edb6195d 100644 --- a/core/src/com/unciv/ui/cityscreen/SpecialistAllocationTable.kt +++ b/core/src/com/unciv/ui/cityscreen/SpecialistAllocationTable.kt @@ -6,9 +6,15 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.addSeparatorVertical +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel class SpecialistAllocationTable(val cityScreen: CityScreen) : Table(BaseScreen.skin) { val cityInfo = cityScreen.city diff --git a/core/src/com/unciv/ui/cityscreen/YieldGroup.kt b/core/src/com/unciv/ui/cityscreen/YieldGroup.kt index 982bc0b11a..5f78b27a1e 100644 --- a/core/src/com/unciv/ui/cityscreen/YieldGroup.kt +++ b/core/src/com/unciv/ui/cityscreen/YieldGroup.kt @@ -5,7 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.models.stats.Stats import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.surroundWithCircle +import com.unciv.ui.utils.extensions.surroundWithCircle class YieldGroup : HorizontalGroup() { init { @@ -56,4 +56,4 @@ class YieldGroup : HorizontalGroup() { table.pack() return table } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaCategories.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaCategories.kt index e8ac3619fe..9f35579d8d 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaCategories.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaCategories.kt @@ -9,11 +9,11 @@ import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.TerrainType +import com.unciv.ui.images.ImageGetter import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileSetStrings -import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.surroundWithCircle +import com.unciv.ui.utils.extensions.surroundWithCircle import java.io.File /** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */ @@ -90,7 +90,7 @@ object CivilopediaImageGetters { val belief = { name: String, size: Float -> // Kludge until we decide how exactly to show Religions fun getInvertedCircledReligionIcon(iconName: String, size: Float) = - ImageGetter.getCircledReligionIcon(iconName, size).apply { + ImageGetter.getCircledReligionIcon(iconName, size).apply { circle.color = Color.WHITE actor.color = Color.BLACK } @@ -108,7 +108,7 @@ object CivilopediaImageGetters { /** Enum used as keys for Civilopedia "pages" (categories). * * Note names are singular on purpose - a "link" allows both key and label - * Order of values determines ordering of the categories in the Civilopedia top bar + * Order of values determines ordering of the categories in the Civilopedia top bar * * @param label Translatable caption for the Civilopedia button */ diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt index af62d9abb5..016cdb82b0 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt @@ -4,18 +4,27 @@ import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Touchable -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.ui.Button +import com.badlogic.gdx.scenes.scene2d.ui.SplitPane +import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.models.ruleset.* +import com.unciv.models.ruleset.Belief +import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.INamed import com.unciv.models.translations.tr import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.utils.AutoScrollPane as ScrollPane /** Screen displaying the Civilopedia @@ -299,7 +308,7 @@ class CivilopediaScreen( } private fun navigateEntries(direction: Int) { - //todo this is abusing a Map as Array - there must be a collection allowing both easy positional and associative access + //todo this is abusing a Map as Array - there must be a collection allowing both easy positional and associative access val index = entryIndex.keys.indexOf(currentEntry) if (index < 0) return selectEntry(entryIndex.keys.first(), true) val newIndex = when (direction) { diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt index 9f318355b0..828460cc31 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt @@ -12,11 +12,12 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.unique.Unique import com.unciv.models.stats.INamed +import com.unciv.ui.civilopedia.MarkupRenderer.render import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.addSeparator -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel import com.unciv.utils.Log import kotlin.math.max diff --git a/core/src/com/unciv/ui/crashhandling/CrashHandlingThread.kt b/core/src/com/unciv/ui/crashhandling/CrashHandlingThread.kt index d6f1bcfa37..9497ee71a3 100644 --- a/core/src/com/unciv/ui/crashhandling/CrashHandlingThread.kt +++ b/core/src/com/unciv/ui/crashhandling/CrashHandlingThread.kt @@ -1,7 +1,7 @@ package com.unciv.ui.crashhandling import com.badlogic.gdx.Gdx -import com.unciv.ui.utils.wrapCrashHandlingUnit +import com.unciv.UncivGame import kotlinx.coroutines.* import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory @@ -72,4 +72,47 @@ fun launchCrashHandling(name: String, runAsDaemon: Boolean = true, private fun getCoroutineContext(runAsDaemon: Boolean): CoroutineScope { return if (runAsDaemon) CRASH_HANDLING_DAEMON_SCOPE else CRASH_HANDLING_SCOPE -} \ No newline at end of file +} +/** + * Returns a wrapped version of a function that safely crashes the game to [CrashScreen] if an exception or error is thrown. + * + * In case an exception or error is thrown, the return will be null. Therefore the return type is always nullable. + * + * The game loop, threading, and event systems already use this to wrap nearly everything that can happen during the lifespan of the Unciv application. + * + * Therefore, it usually shouldn't be necessary to manually use this. See the note at the top of [CrashScreen].kt for details. + * + * @param postToMainThread Whether the [CrashScreen] should be opened by posting a runnable to the main thread, instead of directly. Set this to true if the function is going to run on any thread other than the main loop. + * @return Result from the function, or null if an exception is thrown. + * */ +fun (() -> R).wrapCrashHandling( + postToMainThread: Boolean = false +): () -> R? + = { + try { + this() + } catch (e: Throwable) { + if (postToMainThread) { + Gdx.app.postRunnable { + UncivGame.Current.setScreen(CrashScreen(e)) + } + } else UncivGame.Current.setScreen(CrashScreen(e)) + null + } + } +/** + * Returns a wrapped a version of a Unit-returning function which safely crashes the game to [CrashScreen] if an exception or error is thrown. + * + * The game loop, threading, and event systems already use this to wrap nearly everything that can happen during the lifespan of the Unciv application. + * + * Therefore, it usually shouldn't be necessary to manually use this. See the note at the top of [CrashScreen].kt for details. + * + * @param postToMainThread Whether the [CrashScreen] should be opened by posting a runnable to the main thread, instead of directly. Set this to true if the function is going to run on any thread other than the main loop. + * */ +fun (() -> Unit).wrapCrashHandlingUnit( + postToMainThread: Boolean = false +): () -> Unit { + val wrappedReturning = this.wrapCrashHandling(postToMainThread) + // Don't instantiate a new lambda every time the return get called. + return { wrappedReturning() ?: Unit } +} diff --git a/core/src/com/unciv/ui/crashhandling/CrashScreen.kt b/core/src/com/unciv/ui/crashhandling/CrashScreen.kt index 3ed5394961..49d883e88c 100644 --- a/core/src/com/unciv/ui/crashhandling/CrashScreen.kt +++ b/core/src/com/unciv/ui/crashhandling/CrashScreen.kt @@ -13,7 +13,12 @@ import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.toLabel import com.unciv.utils.Log import java.io.PrintWriter import java.io.StringWriter diff --git a/core/src/com/unciv/ui/images/IconCircleGroup.kt b/core/src/com/unciv/ui/images/IconCircleGroup.kt index 3a78711942..38495837a2 100644 --- a/core/src/com/unciv/ui/images/IconCircleGroup.kt +++ b/core/src/com/unciv/ui/images/IconCircleGroup.kt @@ -5,7 +5,7 @@ import com.badlogic.gdx.graphics.g2d.Batch import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.utils.Align -import com.unciv.ui.utils.center +import com.unciv.ui.utils.extensions.center class IconCircleGroup(size: Float, val actor: Actor, resizeActor: Boolean = true, color: Color = Color.WHITE): Group(){ val circle = ImageGetter.getCircle().apply { diff --git a/core/src/com/unciv/ui/images/IconTextButton.kt b/core/src/com/unciv/ui/images/IconTextButton.kt index cca6f6a1fb..00a9c64d93 100644 --- a/core/src/com/unciv/ui/images/IconTextButton.kt +++ b/core/src/com/unciv/ui/images/IconTextButton.kt @@ -8,7 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.toLabel /** * Translate a [String] and make a [Button] widget from it, with control over font size, font colour, an optional icon, and custom formatting. diff --git a/core/src/com/unciv/ui/images/ImageGetter.kt b/core/src/com/unciv/ui/images/ImageGetter.kt index 8ac67cbf4d..7a4346830d 100644 --- a/core/src/com/unciv/ui/images/ImageGetter.kt +++ b/core/src/com/unciv/ui/images/ImageGetter.kt @@ -25,6 +25,7 @@ import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.stats.Stats import com.unciv.models.tilesets.TileSetCache import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.* import com.unciv.utils.debug import kotlin.math.atan2 import kotlin.math.max diff --git a/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt b/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt index 644c2dd1ba..9fd9b878d9 100644 --- a/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt +++ b/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt @@ -1,7 +1,12 @@ package com.unciv.ui.mapeditor import com.badlogic.gdx.math.Vector2 -import com.badlogic.gdx.scenes.scene2d.* +import com.badlogic.gdx.scenes.scene2d.Action +import com.badlogic.gdx.scenes.scene2d.EventListener +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.InputListener +import com.badlogic.gdx.scenes.scene2d.Stage +import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.unciv.UncivGame import com.unciv.logic.HexMath @@ -12,8 +17,8 @@ import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ZoomableScrollPane -import com.unciv.ui.utils.center -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.onClick /** diff --git a/core/src/com/unciv/ui/mapeditor/GameParametersScreen.kt b/core/src/com/unciv/ui/mapeditor/GameParametersScreen.kt index 5843e0ee28..4ef88b2b81 100644 --- a/core/src/com/unciv/ui/mapeditor/GameParametersScreen.kt +++ b/core/src/com/unciv/ui/mapeditor/GameParametersScreen.kt @@ -6,7 +6,6 @@ import com.unciv.ui.newgamescreen.GameOptionsTable import com.unciv.ui.newgamescreen.IPreviousScreen import com.unciv.ui.newgamescreen.PlayerPickerTable import com.unciv.ui.pickerscreens.PickerScreen -import com.unciv.ui.utils.* /** * As of MapEditor V2, the editor no longer deals with GameParameters, **only** with MapParameters, diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorEditSubTabs.kt b/core/src/com/unciv/ui/mapeditor/MapEditorEditSubTabs.kt index fe09c8d68e..76d1469301 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorEditSubTabs.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorEditSubTabs.kt @@ -9,7 +9,11 @@ import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Nation import com.unciv.models.ruleset.Ruleset -import com.unciv.models.ruleset.tile.* +import com.unciv.models.ruleset.tile.ResourceType +import com.unciv.models.ruleset.tile.Terrain +import com.unciv.models.ruleset.tile.TerrainType +import com.unciv.models.ruleset.tile.TileImprovement +import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine @@ -18,7 +22,11 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.mapeditor.MapEditorEditTab.BrushHandlerType import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileSetStrings -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel internal interface IMapEditorEditSubTabs { fun isDisabled(): Boolean diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorEditTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorEditTab.kt index 3b48f4bcdd..1813bfd05d 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorEditTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorEditTab.kt @@ -15,7 +15,12 @@ import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.images.ImageGetter import com.unciv.ui.mapeditor.MapEditorOptionsTab.TileMatchFuzziness import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.toLabel import com.unciv.utils.Log class MapEditorEditTab( diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorFilesTable.kt b/core/src/com/unciv/ui/mapeditor/MapEditorFilesTable.kt index 4e2741c691..1547b50207 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorFilesTable.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorFilesTable.kt @@ -9,9 +9,9 @@ import com.unciv.logic.MapSaver import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.pad -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toLabel class MapEditorFilesTable( initWidth: Float, @@ -74,7 +74,7 @@ class MapEditorFilesTable( var lastMod = "" for ((index, entry) in sortedFiles.withIndex()) { - val (mod, mapFile) = entry + val (mod, mapFile) = entry if (mod != lastMod) { // One header per Mod add(Table().apply { diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorGenerateTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorGenerateTab.kt index cf37d481b3..b5b15db400 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorGenerateTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorGenerateTab.kt @@ -15,7 +15,17 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.newgamescreen.MapParametersTable import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toCheckBox +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.utils.Log import kotlin.concurrent.thread diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt index e7573c884e..3bec0c5a9e 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt @@ -13,7 +13,13 @@ import com.unciv.models.translations.tr import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.utils.Log import kotlin.concurrent.thread diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorModsTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorModsTab.kt index 704e6df100..3c352c4089 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorModsTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorModsTab.kt @@ -10,8 +10,14 @@ import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.newgamescreen.ModCheckboxTable import com.unciv.ui.newgamescreen.TranslatedSelectBox import com.unciv.ui.popup.Popup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.TabbedPager import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton class MapEditorModsTab( private val editorScreen: MapEditorScreen diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTab.kt index e29dabfdee..1e31420eb5 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTab.kt @@ -8,7 +8,15 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.logic.MapSaver import com.unciv.models.translations.tr import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toCheckBox +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton class MapEditorOptionsTab( private val editorScreen: MapEditorScreen diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorSaveTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorSaveTab.kt index 7d03a56a63..af5df78892 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorSaveTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorSaveTab.kt @@ -13,7 +13,14 @@ import com.unciv.models.translations.tr import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toTextButton import kotlin.concurrent.thread class MapEditorSaveTab( @@ -123,4 +130,4 @@ class MapEditorSaveTab( } } } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt index 631a9b6b50..ff02c10754 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt @@ -5,7 +5,12 @@ import com.badlogic.gdx.graphics.Color import com.unciv.MainMenuScreen import com.unciv.UncivGame import com.unciv.logic.HexMath -import com.unciv.logic.map.* +import com.unciv.logic.map.MapParameters +import com.unciv.logic.map.MapShape +import com.unciv.logic.map.MapSize +import com.unciv.logic.map.MapSizeNew +import com.unciv.logic.map.TileInfo +import com.unciv.logic.map.TileMap import com.unciv.models.metadata.BaseRuleset import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.Ruleset @@ -15,7 +20,8 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.tilegroups.TileGroup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.worldscreen.ZoomButtonPair diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorViewTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorViewTab.kt index 116a7d56f8..714401180c 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorViewTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorViewTab.kt @@ -15,10 +15,18 @@ import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.FormattedLine -import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.civilopedia.FormattedLine.IconDisplay +import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.WrappableLabel +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toTextButton class MapEditorViewTab( private val editorScreen: MapEditorScreen diff --git a/core/src/com/unciv/ui/multiplayer/AddFriendScreen.kt b/core/src/com/unciv/ui/multiplayer/AddFriendScreen.kt index 7c8aae2041..966661016c 100644 --- a/core/src/com/unciv/ui/multiplayer/AddFriendScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/AddFriendScreen.kt @@ -8,7 +8,10 @@ import com.unciv.logic.multiplayer.FriendList import com.unciv.models.translations.tr import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.util.* class AddFriendScreen(backScreen: ViewFriendsListScreen) : PickerScreen() { diff --git a/core/src/com/unciv/ui/multiplayer/AddMultiplayerGameScreen.kt b/core/src/com/unciv/ui/multiplayer/AddMultiplayerGameScreen.kt index 35cc40c0bf..0a87d4f6f7 100644 --- a/core/src/com/unciv/ui/multiplayer/AddMultiplayerGameScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/AddMultiplayerGameScreen.kt @@ -7,11 +7,13 @@ import com.unciv.logic.IdChecker import com.unciv.models.translations.tr import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable -import com.unciv.ui.multiplayer.MultiplayerHelpers import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.util.* class AddMultiplayerGameScreen(backScreen: MultiplayerScreen) : PickerScreen() { diff --git a/core/src/com/unciv/ui/multiplayer/EditFriendScreen.kt b/core/src/com/unciv/ui/multiplayer/EditFriendScreen.kt index 463f23efe9..4189f8e594 100644 --- a/core/src/com/unciv/ui/multiplayer/EditFriendScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/EditFriendScreen.kt @@ -10,7 +10,10 @@ import com.unciv.models.translations.tr import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.util.* class EditFriendScreen(selectedFriend: FriendList.Friend, backScreen: ViewFriendsListScreen) : PickerScreen() { diff --git a/core/src/com/unciv/ui/multiplayer/EditMultiplayerGameInfoScreen.kt b/core/src/com/unciv/ui/multiplayer/EditMultiplayerGameInfoScreen.kt index c4fdbb61a9..5e82930040 100644 --- a/core/src/com/unciv/ui/multiplayer/EditMultiplayerGameInfoScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/EditMultiplayerGameInfoScreen.kt @@ -4,14 +4,17 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.logic.multiplayer.OnlineMultiplayerGame import com.unciv.models.translations.tr -import com.unciv.ui.pickerscreens.PickerScreen -import com.unciv.ui.utils.* import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable -import com.unciv.ui.multiplayer.MultiplayerHelpers +import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton /** Subscreen of MultiplayerScreen to edit and delete saves * backScreen is used for getting back to the MultiplayerScreen so it doesn't have to be created over and over again */ diff --git a/core/src/com/unciv/ui/multiplayer/FriendPickerList.kt b/core/src/com/unciv/ui/multiplayer/FriendPickerList.kt index ab2278d806..cb60b02879 100644 --- a/core/src/com/unciv/ui/multiplayer/FriendPickerList.kt +++ b/core/src/com/unciv/ui/multiplayer/FriendPickerList.kt @@ -6,7 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup import com.unciv.logic.multiplayer.FriendList import com.unciv.ui.newgamescreen.PlayerPickerTable import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.onClick class FriendPickerList( playerPicker: PlayerPickerTable, diff --git a/core/src/com/unciv/ui/multiplayer/GameList.kt b/core/src/com/unciv/ui/multiplayer/GameList.kt index 2854b98d3a..f59d26c260 100644 --- a/core/src/com/unciv/ui/multiplayer/GameList.kt +++ b/core/src/com/unciv/ui/multiplayer/GameList.kt @@ -17,13 +17,12 @@ import com.unciv.logic.multiplayer.MultiplayerGameUpdateEnded import com.unciv.logic.multiplayer.MultiplayerGameUpdateFailed import com.unciv.logic.multiplayer.MultiplayerGameUpdateStarted import com.unciv.logic.multiplayer.MultiplayerGameUpdateSucceeded -import com.unciv.logic.multiplayer.MultiplayerGameUpdateUnchanged import com.unciv.logic.multiplayer.MultiplayerGameUpdated import com.unciv.logic.multiplayer.isUsersTurn import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.setSize +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setSize class GameList( onSelected: (String) -> Unit diff --git a/core/src/com/unciv/ui/multiplayer/LoadDeepLinkScreen.kt b/core/src/com/unciv/ui/multiplayer/LoadDeepLinkScreen.kt index 54a6c32a69..af0f2f2bb7 100644 --- a/core/src/com/unciv/ui/multiplayer/LoadDeepLinkScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/LoadDeepLinkScreen.kt @@ -2,8 +2,8 @@ package com.unciv.ui.multiplayer import com.unciv.Constants import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.center -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.toLabel class LoadDeepLinkScreen : BaseScreen() { init { diff --git a/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt b/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt index 68a53d111e..e51f39125b 100644 --- a/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt +++ b/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt @@ -11,7 +11,8 @@ import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.popup.Popup import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.toCheckBox +import com.unciv.ui.utils.extensions.formatShort +import com.unciv.ui.utils.extensions.toCheckBox import java.io.FileNotFoundException import java.time.Duration import java.time.Instant @@ -50,27 +51,15 @@ object MultiplayerHelpers { descriptionText.appendLine(message.tr()) } val lastUpdate = multiplayerGame.lastUpdate - descriptionText.appendLine("Last refresh: ${formattedElapsedTime(lastUpdate)} ago".tr()) + descriptionText.appendLine("Last refresh: [${Duration.between(lastUpdate, Instant.now()).formatShort()}] ago".tr()) val preview = multiplayerGame.preview if (preview?.currentPlayer != null) { val currentTurnStartTime = Instant.ofEpochMilli(preview.currentTurnStartTime) - descriptionText.appendLine("Current Turn: [${preview.currentPlayer}] since ${formattedElapsedTime(currentTurnStartTime)} ago".tr()) + descriptionText.appendLine("Current Turn: [${preview.currentPlayer}] since [${Duration.between(currentTurnStartTime, Instant.now()).formatShort()}] ago".tr()) } return descriptionText } - private fun formattedElapsedTime(lastUpdate: Instant): String { - val durationToNow = Duration.between(lastUpdate, Instant.now()) - val elapsedMinutes = durationToNow.toMinutes() - if (elapsedMinutes < 120) return "[$elapsedMinutes] [Minutes]" - val elapsedHours = durationToNow.toHours() - if (elapsedHours < 48) { - return "[${elapsedHours}] [Hours]" - } else { - return "[${durationToNow.toDays()}] [Days]" - } - } - fun showDropboxWarning(screen: BaseScreen) { if (!OnlineMultiplayer.usesDropbox() || UncivGame.Current.settings.multiplayer.hideDropboxWarning) return diff --git a/core/src/com/unciv/ui/multiplayer/MultiplayerScreen.kt b/core/src/com/unciv/ui/multiplayer/MultiplayerScreen.kt index c6c2743af9..cc7c4ccdb0 100644 --- a/core/src/com/unciv/ui/multiplayer/MultiplayerScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/MultiplayerScreen.kt @@ -7,16 +7,14 @@ import com.unciv.logic.event.EventBus import com.unciv.logic.multiplayer.MultiplayerGameDeleted import com.unciv.logic.multiplayer.OnlineMultiplayerGame import com.unciv.models.translations.tr -import com.unciv.ui.multiplayer.GameList -import com.unciv.ui.multiplayer.MultiplayerHelpers import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.enable -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toTextButton +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.utils.AutoScrollPane as ScrollPane class MultiplayerScreen(previousScreen: BaseScreen) : PickerScreen() { diff --git a/core/src/com/unciv/ui/multiplayer/ViewFriendsListScreen.kt b/core/src/com/unciv/ui/multiplayer/ViewFriendsListScreen.kt index 68ff816df0..cc600a9491 100644 --- a/core/src/com/unciv/ui/multiplayer/ViewFriendsListScreen.kt +++ b/core/src/com/unciv/ui/multiplayer/ViewFriendsListScreen.kt @@ -4,7 +4,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.* import com.unciv.logic.multiplayer.FriendList import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.Popup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.utils.AutoScrollPane as ScrollPane class ViewFriendsListScreen(previousScreen: BaseScreen) : PickerScreen() { diff --git a/core/src/com/unciv/ui/newgamescreen/FriendTable.kt b/core/src/com/unciv/ui/newgamescreen/FriendTable.kt index 590ab8d7a2..76ec3f879b 100644 --- a/core/src/com/unciv/ui/newgamescreen/FriendTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/FriendTable.kt @@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.logic.multiplayer.FriendList import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.pad class FriendTable(val friend: FriendList.Friend, width: Float, minHeight: Float) : Table() { @@ -26,7 +27,7 @@ class FriendTable(val friend: FriendList.Friend, width: Float, minHeight: Float) friendDisplayLabel.wrap = true titleTable.add(friendDisplayLabel).width(friendDisplayNameMaxWidth) } else { - titleTable.add(friendDisplayLabel).align(Align.center).pad(10f,0f) + titleTable.add(friendDisplayLabel).align(Align.center).pad(10f, 0f) } innerTable.add(titleTable).growX().fillY().row() diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index a17654e13c..f9d3cd2b5b 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -17,7 +17,11 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.multiplayer.MultiplayerHelpers import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.toCheckBox +import com.unciv.ui.utils.extensions.toLabel class GameOptionsTable( val previousScreen: IPreviousScreen, diff --git a/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt index 6771ae2a05..addcba2591 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt @@ -11,8 +11,8 @@ import com.unciv.logic.map.MapType import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.popup.Popup import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onChange -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.toLabel class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() { diff --git a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt index 35912b3923..8232c4b844 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt @@ -6,8 +6,21 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldFilter.DigitsOnlyFilter import com.unciv.UncivGame -import com.unciv.logic.map.* -import com.unciv.ui.utils.* +import com.unciv.logic.map.MapParameters +import com.unciv.logic.map.MapResources +import com.unciv.logic.map.MapShape +import com.unciv.logic.map.MapSize +import com.unciv.logic.map.MapSizeNew +import com.unciv.logic.map.MapType +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toCheckBox +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton /** Table for editing [mapParameters] * diff --git a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt index 0f0232f962..6b151455fe 100644 --- a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt @@ -7,12 +7,16 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.translations.tr import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toCheckBox /** - * A widget containing one expander for extension mods. + * A widget containing one expander for extension mods. * Manages compatibility checks, warns or prevents incompatibilities. - * + * * @param mods In/out set of active mods, modified in place * @param baseRuleset The selected base Ruleset //todo clarify * @param screen Parent screen, used only to show [ToastPopup]s @@ -51,8 +55,8 @@ class ModCheckboxTable( }).padTop(padTop).growX().row() } } - - fun setBaseRuleset(newBaseRuleset: String) { baseRuleset = newBaseRuleset } + + fun setBaseRuleset(newBaseRuleset: String) { baseRuleset = newBaseRuleset } fun disableAllCheckboxes() { for (checkBox in extensionRulesetModButtons) { checkBox.isChecked = false diff --git a/core/src/com/unciv/ui/newgamescreen/NationTable.kt b/core/src/com/unciv/ui/newgamescreen/NationTable.kt index f4aaf3d169..27cd506759 100644 --- a/core/src/com/unciv/ui/newgamescreen/NationTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/NationTable.kt @@ -11,7 +11,9 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.ui.civilopedia.FormattedLine.IconDisplay import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.WrappableLabel +import com.unciv.ui.utils.extensions.pad // The ruleset also acts as a secondary parameter to determine if this is the right or self side of the player picker class NationTable(val nation: Nation, width: Float, minHeight: Float, ruleset: Ruleset? = null) diff --git a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt index 4d0255a46f..3922bc6247 100644 --- a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt +++ b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt @@ -8,7 +8,10 @@ import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup import com.badlogic.gdx.utils.Array import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.logic.* +import com.unciv.logic.GameInfo +import com.unciv.logic.GameStarter +import com.unciv.logic.IdChecker +import com.unciv.logic.MapSaver import com.unciv.logic.civilization.PlayerType import com.unciv.logic.map.MapType import com.unciv.logic.multiplayer.OnlineMultiplayer @@ -23,7 +26,16 @@ import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.addSeparatorVertical +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.net.URL import java.util.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane @@ -210,10 +222,9 @@ class NewGameScreen( } private fun checkConnectionToMultiplayerServer(): Boolean { - val isDropbox = UncivGame.Current.settings.multiplayer.server == Constants.dropboxMultiplayerServer return try { val multiplayerServer = UncivGame.Current.settings.multiplayer.server - val u = URL(if (isDropbox) "https://content.dropboxapi.com" else multiplayerServer) + val u = URL(if (OnlineMultiplayer.usesDropbox()) "https://content.dropboxapi.com" else multiplayerServer) val con = u.openConnection() con.connectTimeout = 3000 con.connect() diff --git a/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt b/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt index 15aad391d0..84e6669639 100644 --- a/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt @@ -1,6 +1,5 @@ package com.unciv.ui.newgamescreen -import com.unciv.ui.utils.AutoScrollPane as ScrollPane import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Group @@ -28,7 +27,9 @@ import com.unciv.ui.pickerscreens.PickerPane import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.Popup import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.* import java.util.* +import com.unciv.ui.utils.AutoScrollPane as ScrollPane /** * This [Table] is used to pick or edit players information for new game creation. diff --git a/core/src/com/unciv/ui/options/AdvancedTab.kt b/core/src/com/unciv/ui/options/AdvancedTab.kt index 4c391c025d..9faab47836 100644 --- a/core/src/com/unciv/ui/options/AdvancedTab.kt +++ b/core/src/com/unciv/ui/options/AdvancedTab.kt @@ -15,8 +15,17 @@ import com.unciv.models.translations.tr import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.popup.YesNoPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.FontFamilyData +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.UncivSlider import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontColor +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.util.* fun advancedTab( diff --git a/core/src/com/unciv/ui/options/DebugTab.kt b/core/src/com/unciv/ui/options/DebugTab.kt index 070bc83ef9..90faf49751 100644 --- a/core/src/com/unciv/ui/options/DebugTab.kt +++ b/core/src/com/unciv/ui/options/DebugTab.kt @@ -7,7 +7,12 @@ import com.unciv.logic.GameSaver import com.unciv.logic.MapSaver import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.tile.ResourceType -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toCheckBox +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton fun debugTab() = Table(BaseScreen.skin).apply { pad(10f) diff --git a/core/src/com/unciv/ui/options/DisplayTab.kt b/core/src/com/unciv/ui/options/DisplayTab.kt index ed675c27a8..219f60b2b1 100644 --- a/core/src/com/unciv/ui/options/DisplayTab.kt +++ b/core/src/com/unciv/ui/options/DisplayTab.kt @@ -9,7 +9,12 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.WrappableLabel +import com.unciv.ui.utils.extensions.brighten +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.worldscreen.WorldScreen private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000")) diff --git a/core/src/com/unciv/ui/options/LanguageTab.kt b/core/src/com/unciv/ui/options/LanguageTab.kt index e92f1abdda..6d43af1927 100644 --- a/core/src/com/unciv/ui/options/LanguageTab.kt +++ b/core/src/com/unciv/ui/options/LanguageTab.kt @@ -3,7 +3,7 @@ package com.unciv.ui.options import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.onClick fun languageTab( optionsPopup: OptionsPopup, @@ -34,4 +34,4 @@ fun languageTab( updateSelection() } } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/options/ModCheckTab.kt b/core/src/com/unciv/ui/options/ModCheckTab.kt index 1ad448bec6..d418b05826 100644 --- a/core/src/com/unciv/ui/options/ModCheckTab.kt +++ b/core/src/com/unciv/ui/options/ModCheckTab.kt @@ -15,7 +15,14 @@ import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter import com.unciv.ui.newgamescreen.TranslatedSelectBox import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.utils.Log import com.unciv.utils.debug diff --git a/core/src/com/unciv/ui/options/MultiplayerTab.kt b/core/src/com/unciv/ui/options/MultiplayerTab.kt index 300fd2b494..35cb06dda9 100644 --- a/core/src/com/unciv/ui/options/MultiplayerTab.kt +++ b/core/src/com/unciv/ui/options/MultiplayerTab.kt @@ -2,71 +2,125 @@ package com.unciv.ui.options import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx -import com.badlogic.gdx.scenes.scene2d.ui.Label -import com.badlogic.gdx.scenes.scene2d.ui.SelectBox import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextField -import com.badlogic.gdx.utils.Array import com.unciv.Constants import com.unciv.logic.multiplayer.OnlineMultiplayer import com.unciv.logic.multiplayer.storage.SimpleHttp +import com.unciv.models.UncivSound +import com.unciv.models.metadata.GameSetting import com.unciv.models.metadata.GameSettings +import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.RulesetCache import com.unciv.models.translations.tr import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.popup.Popup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.format +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toGdxArray +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.time.Duration -import kotlin.reflect.KMutableProperty0 +import java.time.temporal.ChronoUnit fun multiplayerTab( optionsPopup: OptionsPopup -): Table = Table(BaseScreen.skin).apply { - pad(10f) - defaults().pad(5f) +): Table { + val tab = Table(BaseScreen.skin) + tab.pad(10f) + tab.defaults().pad(5f) val settings = optionsPopup.settings - optionsPopup.addCheckbox(this, "Enable multiplayer status button in singleplayer games", - settings.multiplayer.statusButtonInSinglePlayer, updateWorld = true - ) { - settings.multiplayer.statusButtonInSinglePlayer = it - settings.save() - } + optionsPopup.addCheckbox( + tab, "Enable multiplayer status button in singleplayer games", + settings.multiplayer::statusButtonInSinglePlayer, updateWorld = true + ) - val curRefreshSelect = addRefreshSelect(this, settings, settings.multiplayer::currentGameRefreshDelay, - "Update status of currently played game every:".toLabel(), curRefreshDropboxOptions, curRefreshCustomServerOptions) - val allRefreshSelect = addRefreshSelect(this, settings, settings.multiplayer::allGameRefreshDelay, - "In-game, update status of all games every:".toLabel(), allRefreshDropboxOptions, allRefreshCustomServerOptions) + val curRefreshSelect = RefreshSelect( + "Update status of currently played game every:", + createRefreshOptions(ChronoUnit.SECONDS, 3, 5), + createRefreshOptions(ChronoUnit.SECONDS, 10, 20, 30, 60), + GameSetting.MULTIPLAYER_CURRENT_GAME_REFRESH_DELAY, + settings + ) + curRefreshSelect.addTo(tab) - var turnCheckerSelect: SelectBox? = null - // at the moment the notification service only exists on Android - if (Gdx.app.type == Application.ApplicationType.Android) { - optionsPopup.addCheckbox( - this, "Enable out-of-game turn notifications", - settings.multiplayer.turnCheckerEnabled - ) { - settings.multiplayer.turnCheckerEnabled = it - settings.save() - } + val allRefreshSelect = RefreshSelect( + "In-game, update status of all games every:", + createRefreshOptions(ChronoUnit.SECONDS, 15, 30), + createRefreshOptions(ChronoUnit.MINUTES, 1, 2, 5, 15), + GameSetting.MULTIPLAYER_ALL_GAME_REFRESH_DELAY, + settings + ) + allRefreshSelect.addTo(tab) - if (settings.multiplayer.turnCheckerEnabled) { - turnCheckerSelect = addRefreshSelect(this, settings, settings.multiplayer::turnCheckerDelay, - "Out-of-game, update status of all games every:".toLabel(), turnCheckerDropboxOptions, turnCheckerCustomServerOptions) + val turnCheckerSelect = addTurnCheckerOptions(tab, optionsPopup) - optionsPopup.addCheckbox( - this, "Show persistent notification for turn notifier service", - settings.multiplayer.turnCheckerPersistentNotificationEnabled - ) - { settings.multiplayer.turnCheckerPersistentNotificationEnabled = it } - } - } + SettingsSelect("Sound notification for when it's your turn in your currently open game:", + createNotificationSoundOptions(), + GameSetting.MULTIPLAYER_CURRENT_GAME_TURN_NOTIFICATION_SOUND, + settings + ).addTo(tab) + + SettingsSelect("Sound notification for when it's your turn in any other game:", + createNotificationSoundOptions(), + GameSetting.MULTIPLAYER_OTHER_GAME_TURN_NOTIFICATION_SOUND, + settings + ).addTo(tab) + + addMultiplayerServerOptions(tab, optionsPopup, listOf(curRefreshSelect, allRefreshSelect, turnCheckerSelect).filterNotNull()) + + return tab +} + +private fun createNotificationSoundOptions(): List> = listOf( + SelectItem("None", UncivSound.Silent), + SelectItem("Notification [1]", UncivSound.Notification1), + SelectItem("Notification [2]", UncivSound.Notification2), + SelectItem("Chimes", UncivSound.Chimes), + SelectItem("Choir", UncivSound.Choir), + SelectItem("Buy", UncivSound.Coin), + SelectItem("Create", UncivSound.Construction), + SelectItem("Fortify", UncivSound.Fortify), + SelectItem("Pick a tech", UncivSound.Paper), + SelectItem("Adopt policy", UncivSound.Policy), + SelectItem("Promote", UncivSound.Promote), + SelectItem("Set up", UncivSound.Setup), + SelectItem("Swap units", UncivSound.Swap), + SelectItem("Upgrade", UncivSound.Upgrade), + SelectItem("Bombard", UncivSound.Bombard) +) + buildUnitAttackSoundOptions() + +private fun buildUnitAttackSoundOptions(): List> { + return RulesetCache.getSortedBaseRulesets() + .map(RulesetCache::get).filterNotNull() + .map(Ruleset::units).map { it.values } + .flatMap { it } + .filter { it.attackSound != null } + .filter { it.attackSound != "nuke" } // much too long for a notification + .distinctBy { it.attackSound } + .map { SelectItem("[${it.name}] Attack Sound", UncivSound(it.attackSound!!)) } +} + +private fun addMultiplayerServerOptions( + tab: Table, + optionsPopup: OptionsPopup, + toUpdate: Iterable +) { + val settings = optionsPopup.settings val connectionToServerButton = "Check connection to server".toTextButton() - val textToShowForMultiplayerAddress = - if (OnlineMultiplayer.usesCustomServer()) settings.multiplayer.server - else "https://..." + val textToShowForMultiplayerAddress = if (OnlineMultiplayer.usesCustomServer()) { + settings.multiplayer.server + } else { + "https://" + } val multiplayerServerTextField = TextField(textToShowForMultiplayerAddress, BaseScreen.skin) multiplayerServerTextField.setTextFieldFilter { _, c -> c !in " \r\n\t\\" } multiplayerServerTextField.programmaticChangeEvents = true @@ -76,14 +130,10 @@ fun multiplayerTab( multiplayerServerTextField.text = Gdx.app.clipboard.contents }).row() multiplayerServerTextField.onChange { - val isCustomServer = multiplayerServerTextField.text != Constants.dropboxMultiplayerServer + val isCustomServer = OnlineMultiplayer.usesCustomServer() connectionToServerButton.isEnabled = isCustomServer - updateRefreshSelectOptions(curRefreshSelect, isCustomServer, curRefreshDropboxOptions, curRefreshCustomServerOptions) - updateRefreshSelectOptions(allRefreshSelect, isCustomServer, allRefreshDropboxOptions, allRefreshCustomServerOptions) - if (turnCheckerSelect != null) { - updateRefreshSelectOptions(turnCheckerSelect, isCustomServer, allRefreshDropboxOptions, allRefreshCustomServerOptions) - } + for (refreshSelect in toUpdate) refreshSelect.update(isCustomServer) if (isCustomServer) { fixTextFieldUrlOnType(multiplayerServerTextField) @@ -97,23 +147,15 @@ fun multiplayerTab( val screen = optionsPopup.screen serverIpTable.add(multiplayerServerTextField).minWidth(screen.stage.width / 2).growX() - add(serverIpTable).fillX().row() + tab.add(serverIpTable).colspan(2).fillX().row() - add("Reset to Dropbox".toTextButton().onClick { + tab.add("Reset to Dropbox".toTextButton().onClick { multiplayerServerTextField.text = Constants.dropboxMultiplayerServer - if (allRefreshDropboxOptions.size != allRefreshSelect.items.size) { - allRefreshSelect.items = allRefreshDropboxOptions - } - if (curRefreshDropboxOptions.size != curRefreshSelect.items.size) { - curRefreshSelect.items = curRefreshDropboxOptions - } - if (turnCheckerSelect != null && turnCheckerDropboxOptions.size != turnCheckerSelect.items.size) { - turnCheckerSelect.items = turnCheckerDropboxOptions - } + for (refreshSelect in toUpdate) refreshSelect.update(false) settings.save() - }).row() + }).colspan(2).row() - add(connectionToServerButton.onClick { + tab.add(connectionToServerButton.onClick { val popup = Popup(screen).apply { addGoodSizedLabel("Awaiting response...").row() } @@ -123,13 +165,43 @@ fun multiplayerTab( popup.addGoodSizedLabel(if (success) "Success!" else "Failed!").row() popup.addCloseButton() } - }).row() + }).colspan(2).row() +} + +private fun addTurnCheckerOptions( + tab: Table, + optionsPopup: OptionsPopup +): RefreshSelect? { + // at the moment the notification service only exists on Android + if (Gdx.app.type != Application.ApplicationType.Android) return null + + val settings = optionsPopup.settings + + optionsPopup.addCheckbox(tab, "Enable out-of-game turn notifications", settings.multiplayer::turnCheckerEnabled) + + if (!settings.multiplayer.turnCheckerEnabled) return null + + val turnCheckerSelect = RefreshSelect( + "Out-of-game, update status of all games every:", + createRefreshOptions(ChronoUnit.SECONDS, 30), + createRefreshOptions(ChronoUnit.MINUTES, 1, 2, 5, 15), + GameSetting.MULTIPLAYER_TURN_CHECKER_DELAY, + settings + ) + turnCheckerSelect.addTo(tab) + + + optionsPopup.addCheckbox( + tab, "Show persistent notification for turn notifier service", + settings.multiplayer::turnCheckerPersistentNotificationEnabled + ) + + return turnCheckerSelect } private fun successfullyConnectedToServer(settings: GameSettings, action: (Boolean, String, Int?) -> Unit) { launchCrashHandling("TestIsAlive") { - SimpleHttp.sendGetRequest("${settings.multiplayer.server}/isalive") { - success, result, code -> + SimpleHttp.sendGetRequest("${settings.multiplayer.server}/isalive") { success, result, code -> postCrashHandlingRunnable { action(success, result, code) } @@ -137,6 +209,33 @@ private fun successfullyConnectedToServer(settings: GameSettings, action: (Boole } } +private class RefreshSelect( + labelText: String, + extraCustomServerOptions: List>, + dropboxOptions: List>, + setting: GameSetting, + settings: GameSettings +) { + private val customServerItems = (extraCustomServerOptions + dropboxOptions).toGdxArray() + private val dropboxItems = dropboxOptions.toGdxArray() + private val settingsSelect: SettingsSelect + + init { + val initialOptions = if (OnlineMultiplayer.usesCustomServer()) customServerItems else dropboxItems + settingsSelect = SettingsSelect(labelText, initialOptions, setting, settings) + } + + fun update(isCustomServer: Boolean) { + if (isCustomServer && settingsSelect.items.size != customServerItems.size) { + settingsSelect.replaceItems(customServerItems) + } else if (!isCustomServer && settingsSelect.items.size != dropboxItems.size) { + settingsSelect.replaceItems(dropboxItems) + } + } + + fun addTo(tab: Table) = settingsSelect.addTo(tab) +} + private fun fixTextFieldUrlOnType(TextField: TextField) { var text: String = TextField.text var cursor: Int = minOf(TextField.cursorPosition, text.length) @@ -171,84 +270,9 @@ private fun fixTextFieldUrlOnType(TextField: TextField) { } } -private class RefreshOptions(val delay: Duration, val label: String) { - override fun toString(): String = label - override fun equals(other: Any?): Boolean = other is RefreshOptions && delay == other.delay - override fun hashCode(): Int = delay.hashCode() -} - - -private val curRefreshDropboxOptions = - (listOf(10, 20, 30, 60).map { RefreshOptions(Duration.ofSeconds(it), "$it " + "Seconds".tr()) }).toGdxArray() - -private val curRefreshCustomServerOptions = - (listOf(3, 5).map { RefreshOptions(Duration.ofSeconds(it), "$it " + "Seconds".tr()) } + curRefreshDropboxOptions).toGdxArray() - -private val allRefreshDropboxOptions = - (listOf(1, 2, 5, 15).map { RefreshOptions(Duration.ofMinutes(it), "$it " + "Minutes".tr()) }).toGdxArray() - -private val allRefreshCustomServerOptions = - (listOf(15, 30).map { RefreshOptions(Duration.ofSeconds(it), "$it " + "Seconds".tr()) } + allRefreshDropboxOptions).toGdxArray() - -private val turnCheckerDropboxOptions = - (listOf(1, 2, 5, 15).map { RefreshOptions(Duration.ofMinutes(it), "$it " + "Minutes".tr()) }).toGdxArray() - -private val turnCheckerCustomServerOptions = - (listOf(30).map { RefreshOptions(Duration.ofSeconds(it), "$it " + "Seconds".tr()) } + allRefreshDropboxOptions).toGdxArray() - -private fun List.toGdxArray(): Array { - val arr = Array(size) - for (it in this) { - arr.add(it) - } - return arr -} - -private fun addRefreshSelect( - table: Table, - settings: GameSettings, - settingsProperty: KMutableProperty0, - label: Label, - dropboxOptions: Array, - customServerOptions: Array -): SelectBox { - table.add(label).left() - - val refreshSelectBox = SelectBox(table.skin) - val options = if (OnlineMultiplayer.usesCustomServer()) { - customServerOptions - } else { - dropboxOptions - } - refreshSelectBox.items = options - - refreshSelectBox.selected = options.firstOrNull() { it.delay == settingsProperty.get() } ?: options.first() - - table.add(refreshSelectBox).pad(10f).row() - - refreshSelectBox.onChange { - settingsProperty.set(refreshSelectBox.selected.delay) - settings.save() - } - - return refreshSelectBox -} - -private fun updateRefreshSelectOptions( - selectBox: SelectBox, - isCustomServer: Boolean, - dropboxOptions: Array, - customServerOptions: Array -) { - fun replaceItems(selectBox: SelectBox, options: Array) { - val prev = selectBox.selected - selectBox.items = options - selectBox.selected = prev - } - - if (isCustomServer && selectBox.items.size != customServerOptions.size) { - replaceItems(selectBox, customServerOptions) - } else if (!isCustomServer && selectBox.items.size != dropboxOptions.size) { - replaceItems(selectBox, dropboxOptions) +private fun createRefreshOptions(unit: ChronoUnit, vararg options: Long): List> { + return options.map { + val duration = Duration.of(it, unit) + SelectItem(duration.format(), duration) } } diff --git a/core/src/com/unciv/ui/options/OptionsPopup.kt b/core/src/com/unciv/ui/options/OptionsPopup.kt index 3d46252865..1bbe8c783a 100644 --- a/core/src/com/unciv/ui/options/OptionsPopup.kt +++ b/core/src/com/unciv/ui/options/OptionsPopup.kt @@ -3,17 +3,31 @@ package com.unciv.ui.options import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.scenes.scene2d.ui.SelectBox import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener +import com.badlogic.gdx.utils.Array import com.unciv.MainMenuScreen +import com.unciv.logic.event.EventBus +import com.unciv.models.UncivSound import com.unciv.models.metadata.BaseRuleset +import com.unciv.models.metadata.GameSetting +import com.unciv.models.metadata.GameSettings +import com.unciv.models.metadata.SettingsPropertyChanged +import com.unciv.models.metadata.SettingsPropertyUncivSoundChanged import com.unciv.models.ruleset.RulesetCache +import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.Popup import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.TabbedPager -import com.unciv.ui.utils.center -import com.unciv.ui.utils.toCheckBox +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.toCheckBox +import com.unciv.ui.utils.extensions.toGdxArray +import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.worldscreen.WorldScreen +import kotlin.reflect.KMutableProperty0 /** * The Options (Settings) Popup @@ -137,4 +151,80 @@ class OptionsPopup( table.add(checkbox).colspan(2).left().row() } + fun addCheckbox(table: Table, + text: String, + settingsProperty: KMutableProperty0, + updateWorld: Boolean = false, + action: (Boolean) -> Unit = {}) { + addCheckbox(table, text, settingsProperty.get(), updateWorld, action) + } + +} + +class SelectItem(val label: String, val value: T) { + override fun toString(): String = label.tr() + override fun equals(other: Any?): Boolean = other is SelectItem<*> && value == other.value + override fun hashCode(): Int = value.hashCode() +} + +/** + * For creating a SelectBox that is automatically backed by a [GameSettings] property. + * + * **Warning:** [T] has to be the same type as the [GameSetting.kClass] of the [GameSetting] argument. + * + * This will also automatically send [SettingsPropertyChanged] events. + */ +open class SettingsSelect( + labelText: String, + items: Iterable>, + private val setting: GameSetting, + settings: GameSettings +) { + private val settingsProperty: KMutableProperty0 = setting.getProperty(settings) + private val label = labelText.toLabel() + protected val refreshSelectBox = createSelectBox(items.toGdxArray(), settings) + val items by refreshSelectBox::items + + private fun createSelectBox(initialItems: Array>, settings: GameSettings): SelectBox> { + val selectBox = SelectBox>(BaseScreen.skin) + selectBox.items = initialItems + + selectBox.selected = initialItems.firstOrNull() { it.value == settingsProperty.get() } ?: items.first() + selectBox.onChange { + val newValue = selectBox.selected.value + settingsProperty.set(newValue) + sendChangeEvent(newValue) + settings.save() + } + + return selectBox + } + + fun onChange(listener: (event: ChangeListener.ChangeEvent?) -> Unit) { + refreshSelectBox.onChange(listener) + } + + fun addTo(table: Table) { + table.add(label).left() + table.add(refreshSelectBox).row() + } + + /** Maintains the currently selected item if possible, otherwise resets to the first item */ + fun replaceItems(options: Array>) { + val prev = refreshSelectBox.selected + refreshSelectBox.items = options + refreshSelectBox.selected = prev + } + + private fun sendChangeEvent(item: T) { + when (item) { + is UncivSound -> EventBus.send(object : SettingsPropertyUncivSoundChanged { + override val gameSetting = setting + override val value: UncivSound = settingsProperty.get() as UncivSound + }) + else -> EventBus.send(object : SettingsPropertyChanged { + override val gameSetting = setting + }) + } + } } diff --git a/core/src/com/unciv/ui/options/SoundTab.kt b/core/src/com/unciv/ui/options/SoundTab.kt index d6c564fcbd..b2786fbc9f 100644 --- a/core/src/com/unciv/ui/options/SoundTab.kt +++ b/core/src/com/unciv/ui/options/SoundTab.kt @@ -8,7 +8,13 @@ import com.unciv.models.translations.tr import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.WrappableLabel +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.floor fun soundTab( @@ -147,4 +153,4 @@ private fun addMusicCurrentlyPlaying(table: Table, screen: BaseScreen) { label.onClick(UncivSound.Silent) { screen.game.musicController.chooseTrack(flags = MusicTrackChooserFlags.none) } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/overviewscreen/CityOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/CityOverviewTable.kt index 503ccbba22..5f8aac6793 100644 --- a/core/src/com/unciv/ui/overviewscreen/CityOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/CityOverviewTable.kt @@ -16,8 +16,14 @@ import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import com.unciv.ui.cityscreen.CityScreen import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.roundToInt class CityOverviewTab( diff --git a/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt index 6a142307b0..5d03bd05f9 100644 --- a/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt @@ -14,8 +14,14 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.ui.images.ImageGetter import com.unciv.ui.trade.DiplomacyScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.roundToInt class DiplomacyOverviewTab ( diff --git a/core/src/com/unciv/ui/overviewscreen/EmpireOverviewTab.kt b/core/src/com/unciv/ui/overviewscreen/EmpireOverviewTab.kt index cdb2bdc233..bbf8207fe6 100644 --- a/core/src/com/unciv/ui/overviewscreen/EmpireOverviewTab.kt +++ b/core/src/com/unciv/ui/overviewscreen/EmpireOverviewTab.kt @@ -6,8 +6,8 @@ import com.badlogic.gdx.utils.Align import com.unciv.logic.civilization.CivilizationInfo import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.TabbedPager -import com.unciv.ui.utils.packIfNeeded -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.packIfNeeded +import com.unciv.ui.utils.extensions.toLabel abstract class EmpireOverviewTab ( val viewingPlayer: CivilizationInfo, diff --git a/core/src/com/unciv/ui/overviewscreen/ReligionOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/ReligionOverviewTable.kt index 412e37c1fa..12bc592769 100644 --- a/core/src/com/unciv/ui/overviewscreen/ReligionOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/ReligionOverviewTable.kt @@ -16,7 +16,12 @@ import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.max class ReligionOverviewTab( @@ -134,7 +139,7 @@ class ReligionOverviewTab( statsTable.add(religion.getReligionDisplayName().toLabel()).right().row() statsTable.add("Founding Civ:".toLabel()) val foundingCivName = - if (viewingPlayer.knows(religion.foundingCivName) || viewingPlayer.civName == religion.foundingCivName) + if (viewingPlayer.knows(religion.foundingCivName) || viewingPlayer.civName == religion.foundingCivName) religion.foundingCivName else Constants.unknownNationName statsTable.add(foundingCivName.toLabel()).right().row() @@ -142,7 +147,7 @@ class ReligionOverviewTab( val holyCity = gameInfo.getCities().firstOrNull { it.religion.religionThisIsTheHolyCityOf == religion.name } if (holyCity != null) { statsTable.add("Holy City:".toLabel()) - val cityName = + val cityName = if (viewingPlayer.exploredTiles.contains(holyCity.getCenterTile().position)) holyCity.name else Constants.unknownNationName diff --git a/core/src/com/unciv/ui/overviewscreen/ResourcesOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/ResourcesOverviewTable.kt index 088085e82b..3f10891bc4 100644 --- a/core/src/com/unciv/ui/overviewscreen/ResourcesOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/ResourcesOverviewTable.kt @@ -13,8 +13,13 @@ import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaCategories import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.addSeparatorVertical +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel class ResourcesOverviewTab( diff --git a/core/src/com/unciv/ui/overviewscreen/StatsOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/StatsOverviewTable.kt index b1518cf01e..7bf0273319 100644 --- a/core/src/com/unciv/ui/overviewscreen/StatsOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/StatsOverviewTable.kt @@ -9,7 +9,9 @@ import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.stats.Stat import com.unciv.models.stats.StatMap import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.roundToInt class StatsOverviewTab( diff --git a/core/src/com/unciv/ui/overviewscreen/TradesOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/TradesOverviewTable.kt index 469b57c7fb..0e04cc36e9 100644 --- a/core/src/com/unciv/ui/overviewscreen/TradesOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/TradesOverviewTable.kt @@ -5,8 +5,8 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.trade.Trade import com.unciv.logic.trade.TradeOffersList import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.addSeparator -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.toLabel class TradesOverviewTab( viewingPlayer: CivilizationInfo, diff --git a/core/src/com/unciv/ui/overviewscreen/UnitOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/UnitOverviewTable.kt index 1c4a349bc2..c064f2f7f8 100644 --- a/core/src/com/unciv/ui/overviewscreen/UnitOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/UnitOverviewTable.kt @@ -9,11 +9,20 @@ import com.unciv.Constants import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.ImageGetter import com.unciv.ui.pickerscreens.PromotionPickerScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.TabbedPager +import com.unciv.ui.utils.UnitGroup +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.brighten +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.worldscreen.unit.UnitActions import kotlin.math.abs @@ -202,7 +211,7 @@ class UnitOverviewTab( if (enable) Color.GREEN else Color.GREEN.darken(0.5f)) if (enable) upgradeIcon.onClick { showWorldScreenAt(unit) - Sounds.play(unitAction!!.uncivSound) + SoundPlayer.play(unitAction!!.uncivSound) unitAction.action!!() } add(upgradeIcon).size(28f) diff --git a/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt index 3c2df52be6..e44462fc11 100644 --- a/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt @@ -15,8 +15,8 @@ import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaCategories import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel class WonderOverviewTab( viewingPlayer: CivilizationInfo, diff --git a/core/src/com/unciv/ui/pickerscreens/DiplomaticVotePickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/DiplomaticVotePickerScreen.kt index 88d9a87ce9..4c5f2860a4 100644 --- a/core/src/com/unciv/ui/pickerscreens/DiplomaticVotePickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/DiplomaticVotePickerScreen.kt @@ -5,7 +5,7 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.UncivSound import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.onClick class DiplomaticVotePickerScreen(private val votingCiv: CivilizationInfo) : PickerScreen() { private var chosenCiv: String? = null @@ -13,7 +13,7 @@ class DiplomaticVotePickerScreen(private val votingCiv: CivilizationInfo) : Pick init { setDefaultCloseAction() rightSideButton.setText("Choose a civ to vote for".tr()) - + descriptionLabel.setText("Choose who should become the world leader and win a Diplomatic Victory!".tr()) val choosableCivs = votingCiv.gameInfo.civilizations.filter { it.isMajorCiv() && it != votingCiv && !it.isDefeated() } diff --git a/core/src/com/unciv/ui/pickerscreens/DiplomaticVoteResultScreen.kt b/core/src/com/unciv/ui/pickerscreens/DiplomaticVoteResultScreen.kt index 012eba1e38..e3a67af11e 100644 --- a/core/src/com/unciv/ui/pickerscreens/DiplomaticVoteResultScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/DiplomaticVoteResultScreen.kt @@ -6,23 +6,23 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.UncivSound import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.enable -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel class DiplomaticVoteResultScreen(val votesCast: HashMap, val viewingCiv: CivilizationInfo) : PickerScreen() { val gameInfo = viewingCiv.gameInfo - + init { closeButton.remove() - + addVote(viewingCiv.civName) - + for (civ in gameInfo.civilizations.filter { it.isMajorCiv() && it != viewingCiv }) addVote(civ.civName) for (civ in gameInfo.civilizations.filter { it.isCityState() }) addVote(civ.civName) - + rightSideButton.onClick(UncivSound.Click) { viewingCiv.addFlag(CivFlags.ShowDiplomaticVotingResults.name, -1) UncivGame.Current.resetToWorldScreen() @@ -30,24 +30,24 @@ class DiplomaticVoteResultScreen(val votesCast: HashMap, val vie rightSideButton.enable() rightSideButton.setText("Continue".tr()) } - + private fun addVote(civName: String) { val civ = gameInfo.civilizations.firstOrNull { it.civName == civName } if (civ == null || civ.isDefeated()) return - + topTable.add(ImageGetter.getNationIndicator(civ.nation, 30f)).pad(10f) topTable.add(civName.toLabel()).pad(20f) if (civName !in votesCast.keys) { topTable.add("Abstained".toLabel()).row() return } - + val votedCiv = gameInfo.civilizations.firstOrNull { it.civName == votesCast[civName] }!! if (votedCiv.isDefeated()) { topTable.add("Abstained".toLabel()).row() return } - + topTable.add("Voted for".toLabel()).pad(20f) topTable.add(ImageGetter.getNationIndicator(votedCiv.nation, 30f)).pad(10f) topTable.add(votedCiv.civName.toLabel()).row() diff --git a/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt index 1a003b0f05..f853a2b799 100644 --- a/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt @@ -6,7 +6,8 @@ import com.unciv.models.UncivSound import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick class GreatPersonPickerScreen(val civInfo:CivilizationInfo) : PickerScreen() { private var theChosenOne: BaseUnit? = null diff --git a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt index 987cab3890..03c1f6a930 100644 --- a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt @@ -14,9 +14,9 @@ import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.roundToInt class ImprovementPickerScreen( diff --git a/core/src/com/unciv/ui/pickerscreens/ModManagementOptions.kt b/core/src/com/unciv/ui/pickerscreens/ModManagementOptions.kt index 167eaa041c..881cb5226f 100644 --- a/core/src/com/unciv/ui/pickerscreens/ModManagementOptions.kt +++ b/core/src/com/unciv/ui/pickerscreens/ModManagementOptions.kt @@ -2,20 +2,30 @@ package com.unciv.ui.pickerscreens import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Touchable -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.ui.Image +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.models.ruleset.Ruleset import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.newgamescreen.TranslatedSelectBox -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.sign /** * Helper class for Mod Manager - filtering and sorting. - * + * * This isn't a UI Widget, but offers one: [expander] can be used to offer filtering and sorting options. * It holds the variables [sortInstalled] and [sortOnline] for the [modManagementScreen] and knows * how to sort collections of [ModUIData] by providing comparators. @@ -28,7 +38,7 @@ class ModManagementOptions(private val modManagementScreen: ModManagementScreen) val sortByDate = Comparator { mod1, mod2: ModUIData -> mod1.lastUpdated().compareTo(mod2.lastUpdated()) } val sortByDateDesc = Comparator { mod1, mod2: ModUIData -> mod2.lastUpdated().compareTo(mod1.lastUpdated()) } // comparators for stars or status - val sortByStars = Comparator { mod1, mod2: ModUIData -> + val sortByStars = Comparator { mod1, mod2: ModUIData -> 10 * (mod2.stargazers() - mod1.stargazers()) + mod1.name.compareTo(mod2.name, true).sign } val sortByStatus = Comparator { mod1, mod2: ModUIData -> diff --git a/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt b/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt index e52a724e05..af675a666a 100644 --- a/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt @@ -16,17 +16,17 @@ import com.unciv.models.translations.tr import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* import com.unciv.ui.pickerscreens.ModManagementOptions.SortType import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup -import com.unciv.ui.utils.UncivDateFormat.formatDate -import com.unciv.ui.utils.UncivDateFormat.parseDate +import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.* +import com.unciv.ui.utils.extensions.UncivDateFormat.formatDate +import com.unciv.ui.utils.extensions.UncivDateFormat.parseDate import kotlinx.coroutines.Job import kotlinx.coroutines.isActive import java.util.* -import kotlin.collections.HashMap import kotlin.math.max /** @@ -167,7 +167,7 @@ class ModManagementScreen( topTable.add(scrollOnlineMods) topTable.add(modActionTable) topTable.add().row() - topTable.add().expandY() // So short lists won't vertically center everything including headers + topTable.add().expandY() // So short lists won't vertically center everything including headers stage.addActor(optionsManager.expander) optionsManager.expanderChangeEvent = { @@ -404,7 +404,7 @@ class ModManagementScreen( postCrashHandlingRunnable { ToastPopup("[${repo.name}] Downloaded!", this@ModManagementScreen) RulesetCache.loadRulesets() - RulesetCache[repo.name]?.let { + RulesetCache[repo.name]?.let { installedModInfo[repo.name] = ModUIData(it) } refreshInstalledModTable() diff --git a/core/src/com/unciv/ui/pickerscreens/PickerPane.kt b/core/src/com/unciv/ui/pickerscreens/PickerPane.kt index ea17e079ed..64f0f96a7a 100644 --- a/core/src/com/unciv/ui/pickerscreens/PickerPane.kt +++ b/core/src/com/unciv/ui/pickerscreens/PickerPane.kt @@ -8,7 +8,13 @@ import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup import com.unciv.Constants import com.unciv.UncivGame import com.unciv.ui.images.IconTextButton -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton class PickerPane( disableScroll: Boolean = false, diff --git a/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt index f3502254b7..fbbbdbe7cd 100644 --- a/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt @@ -1,7 +1,7 @@ package com.unciv.ui.pickerscreens import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.onClick open class PickerScreen(disableScroll: Boolean = false) : BaseScreen() { diff --git a/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt index 14539329d4..604623abf7 100644 --- a/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt @@ -8,11 +8,16 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.Tutorial import com.unciv.models.UncivSound import com.unciv.models.ruleset.Policy -import com.unciv.models.ruleset.PolicyBranch import com.unciv.models.ruleset.Policy.PolicyBranchType +import com.unciv.models.ruleset.PolicyBranch import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.worldscreen.WorldScreen import kotlin.math.min diff --git a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt index 59d6764860..061f43d906 100644 --- a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt @@ -12,7 +12,11 @@ import com.unciv.models.ruleset.unit.Promotion import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.AskTextPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() { private var selectedPromotion: Promotion? = null @@ -49,7 +53,7 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() { val unitType = unit.type val promotionsForUnitType = unit.civInfo.gameInfo.ruleSet.unitPromotions.values.filter { - it.unitTypes.contains(unitType.name) || unit.promotions.promotions.contains(it.name) + it.unitTypes.contains(unitType.name) || unit.promotions.promotions.contains(it.name) } val unitAvailablePromotions = unit.promotions.getAvailablePromotions() @@ -63,7 +67,7 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() { icon = ImageGetter.getUnitIcon(unit.name).surroundWithCircle(80f), defaultText = unit.name, validate = { it != unit.name}, - actionOnOk = { userInput -> + actionOnOk = { userInput -> unit.instanceName = userInput this.game.setScreen(PromotionPickerScreen(unit)) } diff --git a/core/src/com/unciv/ui/pickerscreens/ReligionPickerScreenCommon.kt b/core/src/com/unciv/ui/pickerscreens/ReligionPickerScreenCommon.kt index fa9151cba8..83768df33c 100644 --- a/core/src/com/unciv/ui/pickerscreens/ReligionPickerScreenCommon.kt +++ b/core/src/com/unciv/ui/pickerscreens/ReligionPickerScreenCommon.kt @@ -17,7 +17,12 @@ import com.unciv.models.ruleset.BeliefType import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.MarkupRenderer -import com.unciv.ui.utils.* +import com.unciv.ui.utils.WrappableLabel +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel abstract class ReligionPickerScreenCommon( private val choosingCiv: CivilizationInfo, diff --git a/core/src/com/unciv/ui/pickerscreens/ReligiousBeliefsPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/ReligiousBeliefsPickerScreen.kt index cabb7e996a..59611145ae 100644 --- a/core/src/com/unciv/ui/pickerscreens/ReligiousBeliefsPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ReligiousBeliefsPickerScreen.kt @@ -13,7 +13,14 @@ import com.unciv.models.ruleset.BeliefType import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.AskTextPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.addSeparatorVertical +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.packIfNeeded +import com.unciv.ui.utils.extensions.surroundWithCircle class ReligiousBeliefsPickerScreen ( choosingCiv: CivilizationInfo, diff --git a/core/src/com/unciv/ui/pickerscreens/TechButton.kt b/core/src/com/unciv/ui/pickerscreens/TechButton.kt index 7e94bc4123..eda3e050a4 100644 --- a/core/src/com/unciv/ui/pickerscreens/TechButton.kt +++ b/core/src/com/unciv/ui/pickerscreens/TechButton.kt @@ -10,7 +10,13 @@ import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileResource import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.brighten +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel class TechButton(techName:String, private val techManager: TechManager, isWorldScreen: Boolean = true) : Table(BaseScreen.skin) { val text = "".toLabel().apply { setAlignment(Align.center) } @@ -81,7 +87,7 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS for (improvement in ruleset.tileImprovements.values.asSequence() .filter { - it.techRequired == techName + it.techRequired == techName || it.uniqueObjects.any { u -> u.allParams.contains(techName) } } .filter { it.uniqueTo == null || it.uniqueTo == civName } diff --git a/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt index f28ad7a9e5..bc25aee3f6 100644 --- a/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt @@ -17,9 +17,13 @@ import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* -import java.util.* -import kotlin.collections.ArrayList +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.extensions.addBorder +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel class TechPickerScreen( @@ -287,11 +291,11 @@ class TechPickerScreen( val label = "Research [${tempTechsToResearch[0]}]".tr() val techProgression = getTechProgressLabel(tempTechsToResearch) - + pick("${label}\n${techProgression}") setButtonsInfo() } - + private fun getTechProgressLabel(techs: List): String { val progress = techs.sumOf { tech -> civTech.researchOfTech(tech) } val techCost = techs.sumOf { tech -> civInfo.tech.costOfTech(tech) } diff --git a/core/src/com/unciv/ui/popup/AskNumberPopup.kt b/core/src/com/unciv/ui/popup/AskNumberPopup.kt index 0d6e35f61c..4c1e9b89b9 100644 --- a/core/src/com/unciv/ui/popup/AskNumberPopup.kt +++ b/core/src/com/unciv/ui/popup/AskNumberPopup.kt @@ -6,7 +6,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel /** Simple class for showing a prompt for a positive integer to the user * @param screen The previous screen the user was on @@ -32,7 +36,7 @@ class AskNumberPopup( actionOnOk: (input: Int) -> Unit = { }, ): Popup(screen) { /** Note for future developers: Why this class only accepts positive digits and not negative. - * + * * The problems is the minus sign. This might not seem like a large obstacle, but problems * arrive quickly. First is that our clean `DigitsOnlyFilter()` must be replaced with a check * that allows for adding a minus sign, but only when it is the first character. So far so good, @@ -44,9 +48,9 @@ class AskNumberPopup( * the number before overwriting it with the clamped variant. But now you reset your cursor * position every time you type a character. You might start trying to cache the cursor position * as well, but at that point you're basically rewriting the setText() function, and when I - * reached this point I decided to stop. - * - * P.S., if you do decide to go on this quest of adding minus signs, don't forget that + * reached this point I decided to stop. + * + * P.S., if you do decide to go on this quest of adding minus signs, don't forget that * `"-".toInt()` also crashes, so you need to exclude that before checking to clamp. */ @@ -58,30 +62,30 @@ class AskNumberPopup( val nameField = TextField(defaultValue, skin) nameField.textFieldFilter = TextField.TextFieldFilter { _, char -> char.isDigit() || char == '-' } - + fun isValidInt(input: String): Boolean { return input.toIntOrNull() != null } - - + + fun clampInBounds(input: String): String { val int = input.toIntOrNull() ?: return input - + if (bounds.first > int) { return bounds.first.toString() } if (bounds.last < int) return bounds.last.toString() - + return input - } - + } + nameField.onChange { nameField.text = clampInBounds(nameField.text) } - + val centerTable = Table(skin) - + fun addValueButton(value: Int) { centerTable.add( Button( @@ -96,19 +100,19 @@ class AskNumberPopup( } ).pad(5f) } - + for (value in amountButtons.reversed()) { addValueButton(-value) } centerTable.add(nameField).growX().pad(10f) - + add(centerTable).colspan(2).row() for (value in amountButtons) { addValueButton(value) } - + val errorLabel = errorText.toLabel() errorLabel.color = Color.RED @@ -123,7 +127,7 @@ class AskNumberPopup( } addCloseButton() equalizeLastTwoButtonWidths() - + keyboardFocus = nameField } } diff --git a/core/src/com/unciv/ui/popup/AskTextPopup.kt b/core/src/com/unciv/ui/popup/AskTextPopup.kt index e6cf674e13..3065f67dc2 100644 --- a/core/src/com/unciv/ui/popup/AskTextPopup.kt +++ b/core/src/com/unciv/ui/popup/AskTextPopup.kt @@ -5,7 +5,9 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel /** Simple class for showing a prompt for a string to the user * @param screen The previous screen the user was on @@ -15,7 +17,7 @@ import com.unciv.ui.utils.* * @param errorText Text that will be shown when an error is detected * @param maxLength The maximal amount of characters the user may input * @param validate Function that should return `true` when a valid input is entered, false otherwise - * @param actionOnOk Lambda that will be executed after pressing 'OK'. + * @param actionOnOk Lambda that will be executed after pressing 'OK'. * Gets the text the user inputted as a parameter. */ class AskTextPopup( @@ -28,26 +30,26 @@ class AskTextPopup( validate: (input: String) -> Boolean = { true }, actionOnOk: (input: String) -> Unit = {}, ) : Popup(screen) { - + val illegalChars = "[]{}\"\\<>" - + init { val wrapper = Table() wrapper.add(icon).padRight(10f) wrapper.add(label.toLabel()) add(wrapper).colspan(2).row() - + val nameField = TextField(defaultText, skin) nameField.textFieldFilter = TextField.TextFieldFilter { _, char -> char !in illegalChars} nameField.maxLength = maxLength - + add(nameField).growX().colspan(2).row() - + val errorLabel = errorText.toLabel() errorLabel.color = Color.RED addOKButton( - validate = { + validate = { val errorFound = nameField.text == "" || !validate(nameField.text) if (errorFound) add(errorLabel).colspan(2).center() !errorFound diff --git a/core/src/com/unciv/ui/popup/Popup.kt b/core/src/com/unciv/ui/popup/Popup.kt index dd110c3760..5203e07ac2 100644 --- a/core/src/com/unciv/ui/popup/Popup.kt +++ b/core/src/com/unciv/ui/popup/Popup.kt @@ -3,11 +3,25 @@ package com.unciv.ui.popup import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Touchable -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.ui.Button +import com.badlogic.gdx.scenes.scene2d.ui.Cell +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.KeyPressDispatcher +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton /** * Base class for all Popups, i.e. Tables that get rendered in the middle of a screen and on top of everything else @@ -220,7 +234,7 @@ fun BaseScreen.hasOpenPopups(): Boolean = stage.actors.any { it is Popup && it.i /** * Counts number of visible[Popup]s. - * + * * Used for key dispatcher precedence. */ fun BaseScreen.countOpenPopups() = stage.actors.count { it is Popup && it.isVisible } diff --git a/core/src/com/unciv/ui/popup/ToastPopup.kt b/core/src/com/unciv/ui/popup/ToastPopup.kt index 078c76a62f..42ee8b8593 100644 --- a/core/src/com/unciv/ui/popup/ToastPopup.kt +++ b/core/src/com/unciv/ui/popup/ToastPopup.kt @@ -1,9 +1,9 @@ package com.unciv.ui.popup import com.unciv.ui.crashhandling.launchCrashHandling -import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick import com.unciv.ui.crashhandling.postCrashHandlingRunnable +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.onClick import kotlinx.coroutines.delay /** diff --git a/core/src/com/unciv/ui/popup/YesNoPopup.kt b/core/src/com/unciv/ui/popup/YesNoPopup.kt index 545bfce7c3..c0b85177af 100644 --- a/core/src/com/unciv/ui/popup/YesNoPopup.kt +++ b/core/src/com/unciv/ui/popup/YesNoPopup.kt @@ -6,7 +6,7 @@ import com.unciv.Constants import com.unciv.UncivGame import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.toLabel /** Variant of [Popup] pre-populated with one label, plus yes and no buttons * @param question The text for the label diff --git a/core/src/com/unciv/ui/saves/LoadGameScreen.kt b/core/src/com/unciv/ui/saves/LoadGameScreen.kt index e96532dfe6..95550ced98 100644 --- a/core/src/com/unciv/ui/saves/LoadGameScreen.kt +++ b/core/src/com/unciv/ui/saves/LoadGameScreen.kt @@ -17,10 +17,16 @@ import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.pickerscreens.Github import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.io.FileNotFoundException -import java.util.concurrent.CancellationException class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() { private val copySavedGameToClipboardButton = getCopyExistingSaveToClipboardButton() diff --git a/core/src/com/unciv/ui/saves/LoadOrSaveScreen.kt b/core/src/com/unciv/ui/saves/LoadOrSaveScreen.kt index 96fe97cfc8..bf1a23c0ff 100644 --- a/core/src/com/unciv/ui/saves/LoadOrSaveScreen.kt +++ b/core/src/com/unciv/ui/saves/LoadOrSaveScreen.kt @@ -11,15 +11,15 @@ import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.UncivDateFormat.formatDate import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.enable -import com.unciv.ui.utils.onChange -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.pad -import com.unciv.ui.utils.toLabel -import com.unciv.ui.utils.toTextButton +import com.unciv.ui.utils.extensions.UncivDateFormat.formatDate +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.util.Date diff --git a/core/src/com/unciv/ui/saves/SaveGameScreen.kt b/core/src/com/unciv/ui/saves/SaveGameScreen.kt index bae27177e4..0c293da625 100644 --- a/core/src/com/unciv/ui/saves/SaveGameScreen.kt +++ b/core/src/com/unciv/ui/saves/SaveGameScreen.kt @@ -15,12 +15,11 @@ import com.unciv.ui.popup.ToastPopup import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.enable -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toLabel -import com.unciv.ui.utils.toTextButton -import java.util.concurrent.CancellationException +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves") { diff --git a/core/src/com/unciv/ui/saves/VerticalFileListScrollPane.kt b/core/src/com/unciv/ui/saves/VerticalFileListScrollPane.kt index 9b368f836e..35c698b05a 100644 --- a/core/src/com/unciv/ui/saves/VerticalFileListScrollPane.kt +++ b/core/src/com/unciv/ui/saves/VerticalFileListScrollPane.kt @@ -14,7 +14,7 @@ import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.AutoScrollPane import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.KeyPressDispatcher -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.onClick //todo key auto-repeat for navigation keys? diff --git a/core/src/com/unciv/ui/tilegroups/CityButton.kt b/core/src/com/unciv/ui/tilegroups/CityButton.kt index e6fe71f18d..1e197b6954 100644 --- a/core/src/com/unciv/ui/tilegroups/CityButton.kt +++ b/core/src/com/unciv/ui/tilegroups/CityButton.kt @@ -21,7 +21,14 @@ import com.unciv.ui.cityscreen.CityScreen import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.Popup import com.unciv.ui.trade.DiplomacyScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.extensions.brighten +import com.unciv.ui.utils.extensions.centerX +import com.unciv.ui.utils.extensions.centerY +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.max import kotlin.math.min @@ -506,4 +513,4 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab return // actions should only be for the CityButtonLayerGroup } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/tilegroups/TileGroup.kt b/core/src/com/unciv/ui/tilegroups/TileGroup.kt index 0399367f2f..a1590abd13 100644 --- a/core/src/com/unciv/ui/tilegroups/TileGroup.kt +++ b/core/src/com/unciv/ui/tilegroups/TileGroup.kt @@ -21,9 +21,12 @@ import com.unciv.models.helpers.UnitMovementMemoryType import com.unciv.ui.cityscreen.YieldGroup import com.unciv.ui.images.ImageAttempter import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* -import java.lang.IllegalStateException -import kotlin.math.* +import com.unciv.ui.utils.extensions.center +import kotlin.math.PI +import kotlin.math.atan +import kotlin.math.atan2 +import kotlin.math.pow +import kotlin.math.sqrt import kotlin.random.Random open class TileGroup( diff --git a/core/src/com/unciv/ui/tilegroups/TileGroupIcons.kt b/core/src/com/unciv/ui/tilegroups/TileGroupIcons.kt index ac8a3bbd6c..7f286813db 100644 --- a/core/src/com/unciv/ui/tilegroups/TileGroupIcons.kt +++ b/core/src/com/unciv/ui/tilegroups/TileGroupIcons.kt @@ -9,7 +9,12 @@ import com.unciv.UncivGame import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.UnitGroup +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.centerX +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.min /** Helper class for TileGroup, which was getting too full */ diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index ed52820d26..8c78b05453 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -7,9 +7,16 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.logic.civilization.* -import com.unciv.logic.civilization.diplomacy.* +import com.unciv.logic.civilization.AlertType +import com.unciv.logic.civilization.AssignedQuest +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.civilization.PopupAlert +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags +import com.unciv.logic.civilization.diplomacy.DiplomacyManager +import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.* +import com.unciv.logic.civilization.diplomacy.DiplomaticStatus +import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.trade.Trade import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeOffer @@ -28,8 +35,18 @@ import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.tilegroups.CityButton -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toPercent +import com.unciv.ui.utils.extensions.toTextButton import kotlin.math.floor import kotlin.math.roundToInt import com.unciv.ui.utils.AutoScrollPane as ScrollPane @@ -838,7 +855,7 @@ class DiplomacyScreen( && otherCivDiplomacyManager.hasModifier(DestroyedProtectedMinor)) continue - var text = when (valueOf(modifier.key)) { + var text = when (DiplomaticModifiers.valueOf(modifier.key)) { DeclaredWarOnUs -> "You declared war on us!" WarMongerer -> "Your warmongering ways are unacceptable to us." LiberatedCity -> "We applaud your liberation of conquered cities!" diff --git a/core/src/com/unciv/ui/trade/LeaderIntroTable.kt b/core/src/com/unciv/ui/trade/LeaderIntroTable.kt index a328c1e9d4..c3a84eeb75 100644 --- a/core/src/com/unciv/ui/trade/LeaderIntroTable.kt +++ b/core/src/com/unciv/ui/trade/LeaderIntroTable.kt @@ -4,14 +4,14 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.ui.utils.BaseScreen import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.toLabel /** * This is meant to be used for any kind of civ introduction - [DiplomacyScreen], * [AlertPopup][com.unciv.ui.worldscreen.AlertPopup] [types][com.unciv.logic.civilization.AlertType] WarDeclaration, FirstContact etc. - * + * * @param civInfo The civilization to display * @param hello Optional additional message */ @@ -20,9 +20,9 @@ class LeaderIntroTable ( hello: String = "" ): Table(BaseScreen.skin) { /** - * Build either a Table(icon, leaderName
hello) or + * Build either a Table(icon, leaderName
hello) or * a Table(Portrait, Table(leaderName, icon
hello)) - * + * * City states in vanilla have leaderName=="" - but don't test CS, test leaderName to allow modding CS to have portraits */ init { diff --git a/core/src/com/unciv/ui/trade/OfferColumnsTable.kt b/core/src/com/unciv/ui/trade/OfferColumnsTable.kt index 64b727ea9f..93f461ec67 100644 --- a/core/src/com/unciv/ui/trade/OfferColumnsTable.kt +++ b/core/src/com/unciv/ui/trade/OfferColumnsTable.kt @@ -9,7 +9,9 @@ import com.unciv.logic.trade.TradeType import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.AskNumberPopup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.surroundWithCircle /** This is the class that holds the 4 columns of the offers (ours/theirs/ offered/available) in trade */ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: DiplomacyScreen, val onChange: () -> Unit): Table(BaseScreen.skin) { @@ -24,15 +26,15 @@ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: Diplomac when (it.type) { TradeType.Gold -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.gold) TradeType.Gold_Per_Turn -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.statsForNextTurn.gold.toInt()) - else -> addOffer(it, tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers) + else -> addOffer(it, tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers) } } - + private val ourOffersTable = OffersListScroll("OurTrade") { when (it.type) { TradeType.Gold -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.gold) TradeType.Gold_Per_Turn -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.statsForNextTurn.gold.toInt()) - else -> addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers) + else -> addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers) } } private val theirOffersTable = OffersListScroll("TheirTrade") { @@ -92,7 +94,7 @@ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: Diplomac label = "Enter the amount of gold", icon = ImageGetter.getStatIcon("Gold").surroundWithCircle(80f), defaultValue = offer.amount.toString(), - amountButtons = + amountButtons = if (offer.type == TradeType.Gold) listOf(50, 500) else listOf(5, 15), bounds = IntRange(0, maxGold), @@ -106,4 +108,4 @@ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: Diplomac } ).open() } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/trade/OffersListScroll.kt b/core/src/com/unciv/ui/trade/OffersListScroll.kt index 7b7b065d95..ab1622847d 100644 --- a/core/src/com/unciv/ui/trade/OffersListScroll.kt +++ b/core/src/com/unciv/ui/trade/OffersListScroll.kt @@ -8,12 +8,25 @@ import com.unciv.UncivGame import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeOffersList import com.unciv.logic.trade.TradeType -import com.unciv.logic.trade.TradeType.* +import com.unciv.logic.trade.TradeType.Agreement +import com.unciv.logic.trade.TradeType.City +import com.unciv.logic.trade.TradeType.Gold +import com.unciv.logic.trade.TradeType.Gold_Per_Turn +import com.unciv.logic.trade.TradeType.Introduction +import com.unciv.logic.trade.TradeType.Luxury_Resource +import com.unciv.logic.trade.TradeType.Strategic_Resource +import com.unciv.logic.trade.TradeType.Technology +import com.unciv.logic.trade.TradeType.Treaty +import com.unciv.logic.trade.TradeType.WarDeclaration +import com.unciv.logic.trade.TradeType.values import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.translations.tr import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick import kotlin.math.min import com.unciv.ui.utils.AutoScrollPane as ScrollPane diff --git a/core/src/com/unciv/ui/trade/TradeTable.kt b/core/src/com/unciv/ui/trade/TradeTable.kt index c20f61bfc6..d5113f4e06 100644 --- a/core/src/com/unciv/ui/trade/TradeTable.kt +++ b/core/src/com/unciv/ui/trade/TradeTable.kt @@ -5,7 +5,10 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeRequest import com.unciv.models.translations.tr -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toTextButton class TradeTable(val otherCivilization: CivilizationInfo, stage: DiplomacyScreen): Table(BaseScreen.skin) { val currentPlayerCiv = otherCivilization.gameInfo.getCurrentPlayerCivilization() diff --git a/core/src/com/unciv/ui/tutorials/TutorialRender.kt b/core/src/com/unciv/ui/tutorials/TutorialRender.kt index b0c34f0841..10733cd326 100644 --- a/core/src/com/unciv/ui/tutorials/TutorialRender.kt +++ b/core/src/com/unciv/ui/tutorials/TutorialRender.kt @@ -6,7 +6,8 @@ import com.unciv.Constants import com.unciv.models.Tutorial import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.Popup -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode data class TutorialForRender(val tutorial: Tutorial, val texts: Array) diff --git a/core/src/com/unciv/ui/utils/ExpanderTab.kt b/core/src/com/unciv/ui/utils/ExpanderTab.kt index a895dee730..cc28fd8d1f 100644 --- a/core/src/com/unciv/ui/utils/ExpanderTab.kt +++ b/core/src/com/unciv/ui/utils/ExpanderTab.kt @@ -10,10 +10,12 @@ import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame import com.unciv.ui.images.ImageGetter +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel /** * A widget with a header that when clicked shows/hides a sub-Table. - * + * * @param title The header text, automatically translated. * @param fontSize Size applied to header text (only) * @param icon Optional icon - please use [Image][com.badlogic.gdx.scenes.scene2d.ui.Image] or [IconCircleGroup] @@ -21,7 +23,7 @@ import com.unciv.ui.images.ImageGetter * @param headerPad Default padding for the header Table. * @param expanderWidth If set initializes header width * @param persistenceID If specified, the ExpanderTab will remember its open/closed state for the duration of one app run - * @param onChange If specified, this will be called after the visual change for a change in [isOpen] completes (e.g. to react to changed size) + * @param onChange If specified, this will be called after the visual change for a change in [isOpen] completes (e.g. to react to changed size) * @param initContent Optional lambda with [innerTable] as parameter, to help initialize content. */ class ExpanderTab( diff --git a/core/src/com/unciv/ui/utils/LanguageTable.kt b/core/src/com/unciv/ui/utils/LanguageTable.kt index ec0600e6d8..b0578d909e 100644 --- a/core/src/com/unciv/ui/utils/LanguageTable.kt +++ b/core/src/com/unciv/ui/utils/LanguageTable.kt @@ -6,7 +6,8 @@ import com.unciv.UncivGame import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.images.ImageGetter -import java.util.ArrayList +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.toLabel /** Represents a row in the Language picker, used both in OptionsPopup and in LanguagePickerScreen */ internal class LanguageTable(val language:String, val percentComplete: Int): Table(){ diff --git a/core/src/com/unciv/ui/utils/MayaCalendar.kt b/core/src/com/unciv/ui/utils/MayaCalendar.kt index e9b97a3787..5c41f47426 100644 --- a/core/src/com/unciv/ui/utils/MayaCalendar.kt +++ b/core/src/com/unciv/ui/utils/MayaCalendar.kt @@ -9,6 +9,7 @@ import com.unciv.models.translations.tr import com.unciv.ui.popup.Popup import com.unciv.ui.utils.KeyCharAndCode.Companion.makeChar import com.unciv.ui.utils.KeyCharAndCode.Companion.toCode +import com.unciv.ui.utils.extensions.addSeparator import kotlin.math.abs object MayaCalendar { diff --git a/core/src/com/unciv/ui/utils/TabbedPager.kt b/core/src/com/unciv/ui/utils/TabbedPager.kt index f2ce1a2be6..5a0bfee711 100644 --- a/core/src/com/unciv/ui/utils/TabbedPager.kt +++ b/core/src/com/unciv/ui/utils/TabbedPager.kt @@ -2,8 +2,18 @@ package com.unciv.ui.utils import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.* -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.EventListener +import com.badlogic.gdx.scenes.scene2d.Group +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.InputListener +import com.badlogic.gdx.scenes.scene2d.ui.Button +import com.badlogic.gdx.scenes.scene2d.ui.Cell +import com.badlogic.gdx.scenes.scene2d.ui.Image +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextField +import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener import com.badlogic.gdx.utils.Align import com.unciv.Constants @@ -12,13 +22,19 @@ import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.Popup import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.packIfNeeded +import com.unciv.ui.utils.extensions.pad //TODO If keys are assigned, the widget is in a popup not filling stage width, and a button is // partially visible on the right end, the key tooltip will show outside the parent. /** * Implements a 'Tabs' widget where different pages can be switched by selecting a header button. - * + * * Each page is an Actor, passed to the Widget via [addPage]. Pages can be [removed][removePage], * [replaced][replacePage] or dynamically added after the Widget is already shown. @@ -26,10 +42,10 @@ import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip * The widget optionally supports "fixed content", an additional Actor that will be inserted above * the regular content of any page. It will scroll only horizontally, and its scroll position will * synchronize bidirectionally with the scrolling of the main content. - * + * * Pages can be disabled or secret - any 'secret' pages added require a later call to [askForPassword] * to activate them (or discard if the password is wrong). - * + * * The size parameters are lower and upper bounds of the page content area. The widget will always report * these bounds (plus header height) as layout properties min/max-Width/Height, and measure the content * area of added pages and set the reported pref-W/H to their maximum within these bounds. But, if a diff --git a/core/src/com/unciv/ui/utils/UncivSlider.kt b/core/src/com/unciv/ui/utils/UncivSlider.kt index ae42d24b27..b83b0ef6c0 100644 --- a/core/src/com/unciv/ui/utils/UncivSlider.kt +++ b/core/src/com/unciv/ui/utils/UncivSlider.kt @@ -5,16 +5,26 @@ import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.math.Interpolation import com.badlogic.gdx.math.Vector2 -import com.badlogic.gdx.scenes.scene2d.* +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.EventListener +import com.badlogic.gdx.scenes.scene2d.Group +import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.ui.Container +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane +import com.badlogic.gdx.scenes.scene2d.ui.Slider +import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Timer import com.unciv.Constants import com.unciv.models.UncivSound -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.images.IconCircleGroup +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.abs import kotlin.math.sign @@ -175,7 +185,7 @@ class UncivSlider ( } valueChanged() onChange?.invoke(slider.value) - Sounds.play(sound) + SoundPlayer.play(sound) } }) } diff --git a/core/src/com/unciv/ui/utils/UncivTooltip.kt b/core/src/com/unciv/ui/utils/UncivTooltip.kt index 557dd136c1..8c7d301841 100644 --- a/core/src/com/unciv/ui/utils/UncivTooltip.kt +++ b/core/src/com/unciv/ui/utils/UncivTooltip.kt @@ -3,22 +3,30 @@ package com.unciv.ui.utils import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.math.Interpolation import com.badlogic.gdx.math.Vector2 -import com.badlogic.gdx.scenes.scene2d.* +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.Group +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.InputListener +import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.ui.Container +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.Tooltip import com.badlogic.gdx.utils.Align import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter +import com.unciv.ui.utils.extensions.toLabel /** * A **Replacement** for Gdx [Tooltip], placement does not follow the mouse. - * + * * Usage: [group][Group].addTooltip([text][String], size) builds a [Label] as tip actor and attaches it to your [Group]. * * @param target The widget the tooltip will be added to - take care this is the same for which addListener is called * @param content The actor to display as Tooltip * @param targetAlign Point on the [target] widget to align the Tooltip to - * @param tipAlign Point on the Tooltip to align with the given point on the [target] + * @param tipAlign Point on the Tooltip to align with the given point on the [target] * @param offset Additional offset for Tooltip position after alignment * @param animate Use show/hide animations * @param forceContentSize Force virtual [content] width/height for alignment calculation @@ -157,9 +165,9 @@ class UncivTooltip ( companion object { /** * Add a [Label]-based Tooltip with a rounded-corner background to a [Table] or other [Group]. - * + * * Tip is positioned over top right corner, slightly overshooting the receiver widget, longer tip [text]s will extend to the left. - * + * * @param text Automatically translated tooltip text * @param size _Vertical_ size of the entire Tooltip including background * @param always override requirement: presence of physical keyboard @@ -180,7 +188,7 @@ class UncivTooltip ( val background = ImageGetter.getRoundedEdgeRectangle(Color.LIGHT_GRAY) // This controls text positioning relative to the background. - // The minute fiddling makes both single caps and longer text look centered. + // The minute fiddling makes both single caps and longer text look centered. @Suppress("SpellCheckingInspection") val skewPadDescenders = if (",;gjpqy".any { it in text }) 0f else 2.5f val horizontalPad = if (text.length > 1) 10f else 6f diff --git a/core/src/com/unciv/ui/utils/UnitGroup.kt b/core/src/com/unciv/ui/utils/UnitGroup.kt index d2dd419e72..58458a3c45 100644 --- a/core/src/com/unciv/ui/utils/UnitGroup.kt +++ b/core/src/com/unciv/ui/utils/UnitGroup.kt @@ -8,6 +8,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image import com.unciv.UncivGame import com.unciv.logic.map.MapUnit import com.unciv.ui.images.ImageGetter +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.surroundWithCircle class UnitGroup(val unit: MapUnit, val size: Float): Group() { var blackSpinningCircle: Image? = null @@ -94,4 +96,4 @@ class UnitGroup(val unit: MapUnit, val size: Float): Group() { blackSpinningCircle = spinningCircle } } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/utils/extensions/CollectionExtensions.kt b/core/src/com/unciv/ui/utils/extensions/CollectionExtensions.kt new file mode 100644 index 0000000000..92f6f21c3a --- /dev/null +++ b/core/src/com/unciv/ui/utils/extensions/CollectionExtensions.kt @@ -0,0 +1,70 @@ +package com.unciv.ui.utils.extensions + +import com.badlogic.gdx.utils.Array +import kotlin.random.Random + +/** Get one random element of a given List. + * + * The probability for each element is proportional to the value of its corresponding element in the [weights] List. + */ +fun List.randomWeighted(weights: List, random: Random = Random): T { + if (this.isEmpty()) throw NoSuchElementException("Empty list.") + if (this.size != weights.size) throw UnsupportedOperationException("Weights size does not match this list size.") + + val totalWeight = weights.sum() + val randDouble = random.nextDouble() + var sum = 0f + + for (i in weights.indices) { + sum += weights[i] / totalWeight + if (randDouble <= sum) + return this[i] + } + return this.last() +} + +/** Gets a clone of an [ArrayList] with an additional item + * + * Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed + */ +fun ArrayList.withItem(item:T): ArrayList { + val newArrayList = ArrayList(this) + newArrayList.add(item) + return newArrayList +} + +/** Gets a clone of a [HashSet] with an additional item + * + * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed + */ +fun HashSet.withItem(item:T): HashSet { + val newHashSet = HashSet(this) + newHashSet.add(item) + return newHashSet +} + +/** Gets a clone of an [ArrayList] without a given item + * + * Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed + */ +fun ArrayList.withoutItem(item:T): ArrayList { + val newArrayList = ArrayList(this) + newArrayList.remove(item) + return newArrayList +} + +/** Gets a clone of a [HashSet] without a given item + * + * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed + */ +fun HashSet.withoutItem(item:T): HashSet { + val newHashSet = HashSet(this) + newHashSet.remove(item) + return newHashSet +} + +fun Iterable.toGdxArray(): Array { + val arr = if (this is Collection) Array(size) else Array() + for (it in this) arr.add(it) + return arr +} diff --git a/core/src/com/unciv/ui/utils/extensions/FormattingExtensions.kt b/core/src/com/unciv/ui/utils/extensions/FormattingExtensions.kt new file mode 100644 index 0000000000..8d9333e343 --- /dev/null +++ b/core/src/com/unciv/ui/utils/extensions/FormattingExtensions.kt @@ -0,0 +1,94 @@ +package com.unciv.ui.utils.extensions + +import com.unciv.models.translations.tr +import java.text.SimpleDateFormat +import java.time.Duration +import java.time.temporal.ChronoUnit +import java.util.* + +/** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ +fun String.toPercent() = toFloat().toPercent() + +/** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ +fun Int.toPercent() = toFloat().toPercent() + +/** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ +fun Float.toPercent() = 1 + this/100 + +/** Convert a [resource name][this] into "Consumes [amount] $resource" string (untranslated, using separate templates for 1 and other amounts) */ +//todo some day... remove and use just one translatable where this is called +fun String.getConsumesAmountString(amount: Int) = if (amount == 1) "Consumes 1 [$this]" else "Consumes [$amount] [$this]" + +/** Formats the [Duration] into a translated string */ +fun Duration.format(): String { + val sb = StringBuilder() + var firstPartAlreadyAdded = false + for ((unit, part) in toParts()) { + if (part == 0L) continue + + if (firstPartAlreadyAdded) { + sb.append(", ") + } + sb.append("[${part}] $unit") + firstPartAlreadyAdded = true + } + return sb.toString() +} + +private fun Duration.toParts(): SortedMap { + return buildMap { + val secondsPart = seconds % 60 + val minutePart = toMinutes() % 60 + val hourPart = toHours() % 24 + put(ChronoUnit.SECONDS, secondsPart) + put(ChronoUnit.MINUTES, minutePart) + put(ChronoUnit.HOURS, hourPart) + put(ChronoUnit.DAYS, toDays()) + }.toSortedMap(compareByDescending { it }) +} + +/** Formats the [Duration] into a translated string, but only showing the most significant time unit */ +fun Duration.formatShort(): String { + val parts = toParts() + for ((unit, part) in parts) { + if (part > 2) return "[${part}] $unit".tr() + } + return "[${parts[ChronoUnit.SECONDS]}] ${ChronoUnit.SECONDS}".tr() +} +/** + * Standardize date formatting so dates are presented in a consistent style and all decisions + * to change date handling are encapsulated here + */ +object UncivDateFormat { + private val standardFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) + + /** Format a date to ISO format with minutes */ + fun Date.formatDate(): String = standardFormat.format(this) + // Previously also used: + //val updateString = "{Updated}: " +DateFormat.getDateInstance(DateFormat.SHORT).format(date) + + // Everything under java.time is from Java 8 onwards, meaning older phones that use Java 7 won't be able to handle it :/ + // So we're forced to use ancient Java 6 classes instead of the newer and nicer LocalDateTime.parse :( + // Direct solution from https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date + + @Suppress("SpellCheckingInspection") + private val utcFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US) + + /** Parse an UTC date as passed by online API's + * example: `"2021-04-11T14:43:33Z".parseDate()` + */ + fun String.parseDate(): Date = utcFormat.parse(this) +} + +/** For filters containing '{', apply the [predicate] to each part inside "{}" and aggregate using [operation]; + * otherwise return `null` for Elvis chaining of the individual filter. */ +fun String.filterCompositeLogic(predicate: (String) -> T?, operation: (T, T) -> T): T? { + val elements: List = removePrefix("{").removeSuffix("}").split("} {") + .mapNotNull(predicate) + if (elements.isEmpty()) return null + return elements.reduce(operation) +} +/** If a filter string contains '{', apply the [predicate] to each part inside "{}" then 'and' (`&&`) them together; + * otherwise return `null` for Elvis chaining of the individual filter. */ +fun String.filterAndLogic(predicate: (String) -> Boolean): Boolean? = + if (contains('{')) filterCompositeLogic(predicate) { a, b -> a && b } else null diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/extensions/Scene2dExtensions.kt similarity index 54% rename from core/src/com/unciv/ui/utils/ExtensionFunctions.kt rename to core/src/com/unciv/ui/utils/extensions/Scene2dExtensions.kt index 6b306fe8ca..2cd7c37d1b 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/extensions/Scene2dExtensions.kt @@ -1,26 +1,31 @@ -package com.unciv.ui.utils +package com.unciv.ui.utils.extensions -import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.* -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.Group +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.Stage +import com.badlogic.gdx.scenes.scene2d.Touchable +import com.badlogic.gdx.scenes.scene2d.ui.Button +import com.badlogic.gdx.scenes.scene2d.ui.Cell +import com.badlogic.gdx.scenes.scene2d.ui.CheckBox +import com.badlogic.gdx.scenes.scene2d.ui.Image +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.badlogic.gdx.scenes.scene2d.utils.ClickListener import com.badlogic.gdx.utils.Align import com.unciv.Constants -import com.unciv.ui.crashhandling.CrashScreen -import com.unciv.UncivGame import com.unciv.models.UncivSound import com.unciv.models.translations.tr -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter -import java.text.SimpleDateFormat -import java.time.Duration -import java.time.Instant -import java.util.* -import kotlin.random.Random +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts /** * Collection of extension functions mostly for libGdx widgets @@ -69,7 +74,7 @@ fun Actor.center(parent: Stage) { centerX(parent); centerY(parent) } fun Actor.onClickEvent(sound: UncivSound = UncivSound.Click, function: (event: InputEvent?, x: Float, y: Float) -> Unit) { this.addListener(object : ClickListener() { override fun clicked(event: InputEvent?, x: Float, y: Float) { - launchCrashHandling("Sound") { Sounds.play(sound) } + launchCrashHandling("Sound") { SoundPlayer.play(sound) } function(event, x, y) } }) @@ -166,55 +171,6 @@ fun Image.setSize(size: Float) { setSize(size, size) } -/** Gets a clone of an [ArrayList] with an additional item - * - * Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed - */ -fun ArrayList.withItem(item:T): ArrayList { - val newArrayList = ArrayList(this) - newArrayList.add(item) - return newArrayList -} - -/** Gets a clone of a [HashSet] with an additional item - * - * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed - */ -fun HashSet.withItem(item:T): HashSet { - val newHashSet = HashSet(this) - newHashSet.add(item) - return newHashSet -} - -/** Gets a clone of an [ArrayList] without a given item - * - * Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed - */ -fun ArrayList.withoutItem(item:T): ArrayList { - val newArrayList = ArrayList(this) - newArrayList.remove(item) - return newArrayList -} - -/** Gets a clone of a [HashSet] without a given item - * - * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed - */ -fun HashSet.withoutItem(item:T): HashSet { - val newHashSet = HashSet(this) - newHashSet.remove(item) - return newHashSet -} - -/** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ -fun String.toPercent() = toFloat().toPercent() - -/** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ -fun Int.toPercent() = toFloat().toPercent() - -/** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ -fun Float.toPercent() = 1 + this/100 - /** Translate a [String] and make a [TextButton] widget from it */ fun String.toTextButton() = TextButton(this.tr(), BaseScreen.skin) @@ -273,121 +229,3 @@ fun WidgetGroup.packIfNeeded(): WidgetGroup { if (needsLayout()) pack() return this } - -/** Get one random element of a given List. - * - * The probability for each element is proportional to the value of its corresponding element in the [weights] List. - */ -fun List.randomWeighted(weights: List, random: Random = Random): T { - if (this.isEmpty()) throw NoSuchElementException("Empty list.") - if (this.size != weights.size) throw UnsupportedOperationException("Weights size does not match this list size.") - - val totalWeight = weights.sum() - val randDouble = random.nextDouble() - var sum = 0f - - for (i in weights.indices) { - sum += weights[i] / totalWeight - if (randDouble <= sum) - return this[i] - } - return this.last() -} - -/** - * Standardize date formatting so dates are presented in a consistent style and all decisions - * to change date handling are encapsulated here - */ -object UncivDateFormat { - private val standardFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) - - /** Format a date to ISO format with minutes */ - fun Date.formatDate(): String = standardFormat.format(this) - // Previously also used: - //val updateString = "{Updated}: " +DateFormat.getDateInstance(DateFormat.SHORT).format(date) - - // Everything under java.time is from Java 8 onwards, meaning older phones that use Java 7 won't be able to handle it :/ - // So we're forced to use ancient Java 6 classes instead of the newer and nicer LocalDateTime.parse :( - // Direct solution from https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date - - @Suppress("SpellCheckingInspection") - private val utcFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US) - - /** Parse an UTC date as passed by online API's - * example: `"2021-04-11T14:43:33Z".parseDate()` - */ - fun String.parseDate(): Date = utcFormat.parse(this) -} - -fun Duration.isLargerThan(other: Duration): Boolean { - return compareTo(other) > 0 -} -fun Instant.isLargerThan(other: Instant): Boolean { - return compareTo(other) > 0 -} - -/** - * Returns a wrapped version of a function that safely crashes the game to [CrashScreen] if an exception or error is thrown. - * - * In case an exception or error is thrown, the return will be null. Therefore the return type is always nullable. - * - * The game loop, threading, and event systems already use this to wrap nearly everything that can happen during the lifespan of the Unciv application. - * - * Therefore, it usually shouldn't be necessary to manually use this. See the note at the top of [CrashScreen].kt for details. - * - * @param postToMainThread Whether the [CrashScreen] should be opened by posting a runnable to the main thread, instead of directly. Set this to true if the function is going to run on any thread other than the main loop. - * @return Result from the function, or null if an exception is thrown. - * */ -fun (() -> R).wrapCrashHandling( - postToMainThread: Boolean = false -): () -> R? - = { - try { - this() - } catch (e: Throwable) { - if (postToMainThread) { - Gdx.app.postRunnable { - UncivGame.Current.setScreen(CrashScreen(e)) - } - } else UncivGame.Current.setScreen(CrashScreen(e)) - null - } - } - -/** - * Returns a wrapped a version of a Unit-returning function which safely crashes the game to [CrashScreen] if an exception or error is thrown. - * - * The game loop, threading, and event systems already use this to wrap nearly everything that can happen during the lifespan of the Unciv application. - * - * Therefore, it usually shouldn't be necessary to manually use this. See the note at the top of [CrashScreen].kt for details. - * - * @param postToMainThread Whether the [CrashScreen] should be opened by posting a runnable to the main thread, instead of directly. Set this to true if the function is going to run on any thread other than the main loop. - * */ -fun (() -> Unit).wrapCrashHandlingUnit( - postToMainThread: Boolean = false -): () -> Unit { - val wrappedReturning = this.wrapCrashHandling(postToMainThread) - // Don't instantiate a new lambda every time the return get called. - return { wrappedReturning() ?: Unit } -} - -/** For filters containing '{', apply the [predicate] to each part inside "{}" and aggregate using [operation]; - * otherwise return `null` for Elvis chaining of the individual filter. */ -fun String.filterCompositeLogic(predicate: (String) -> T?, operation: (T, T) -> T): T? { - val elements: List = removePrefix("{").removeSuffix("}").split("} {") - .mapNotNull(predicate) - if (elements.isEmpty()) return null - return elements.reduce(operation) -} -/** If a filter string contains '{', apply the [predicate] to each part inside "{}" then 'and' (`&&`) them together; - * otherwise return `null` for Elvis chaining of the individual filter. */ -fun String.filterAndLogic(predicate: (String) -> Boolean): Boolean? = - if (contains('{')) filterCompositeLogic(predicate) { a, b -> a && b } else null - - -/** Convert a [resource name][this] into "Consumes [amount] $resource" string (untranslated, using separate templates for 1 and other amounts) */ -//todo some day... remove and use just one translatable where this is called -fun String.getConsumesAmountString(amount: Int) = ( - if (amount == 1) "Consumes 1 [$this]" - else "Consumes [$amount] [$this]" - ) diff --git a/core/src/com/unciv/ui/utils/extensions/TimeExtensions.kt b/core/src/com/unciv/ui/utils/extensions/TimeExtensions.kt new file mode 100644 index 0000000000..521a5f9a50 --- /dev/null +++ b/core/src/com/unciv/ui/utils/extensions/TimeExtensions.kt @@ -0,0 +1,12 @@ +package com.unciv.ui.utils.extensions + +import java.time.Duration +import java.time.Instant + + +fun Duration.isLargerThan(other: Duration): Boolean { + return compareTo(other) > 0 +} +fun Instant.isLargerThan(other: Instant): Boolean { + return compareTo(other) > 0 +} diff --git a/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt b/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt index 9141d5d803..41c9d865c6 100644 --- a/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt +++ b/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt @@ -6,13 +6,17 @@ import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.models.translations.tr import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.Victory +import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.pickerscreens.PickerScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.worldscreen.WorldScreen class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() { diff --git a/core/src/com/unciv/ui/worldscreen/AlertPopup.kt b/core/src/com/unciv/ui/worldscreen/AlertPopup.kt index 0cb4435628..78c83ed7f7 100644 --- a/core/src/com/unciv/ui/worldscreen/AlertPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/AlertPopup.kt @@ -6,7 +6,10 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.logic.civilization.* +import com.unciv.logic.civilization.AlertType +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.civilization.LocationAction +import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.models.ruleset.unique.UniqueType @@ -17,18 +20,24 @@ import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.Popup import com.unciv.ui.trade.LeaderIntroTable -import com.unciv.ui.utils.* +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import java.util.* /** * [Popup] communicating events other than trade offers to the player. - * (e.g. First Contact, Wonder built, Tech researched,...) + * (e.g. First Contact, Wonder built, Tech researched,...) * * Called in [WorldScreen].update, which pulls them from viewingCiv.popupAlerts. - * + * * @param worldScreen The parent screen * @param popupAlert The [PopupAlert] entry to present - * + * * @see AlertType */ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popup(worldScreen) { diff --git a/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt b/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt index 8a00afe253..253e608451 100644 --- a/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt +++ b/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt @@ -7,9 +7,9 @@ import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.logic.civilization.Notification -import com.unciv.ui.utils.WrappableLabel import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.WrappableLabel +import com.unciv.ui.utils.extensions.onClick import kotlin.math.min import com.unciv.ui.utils.AutoScrollPane as ScrollPane diff --git a/core/src/com/unciv/ui/worldscreen/PlayerReadyScreen.kt b/core/src/com/unciv/ui/worldscreen/PlayerReadyScreen.kt index 12d975bbe5..ef0c6b999a 100644 --- a/core/src/com/unciv/ui/worldscreen/PlayerReadyScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/PlayerReadyScreen.kt @@ -7,7 +7,9 @@ import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel class PlayerReadyScreen(gameInfo: GameInfo, currentPlayerCiv: CivilizationInfo) : BaseScreen(){ init { diff --git a/core/src/com/unciv/ui/worldscreen/TradePopup.kt b/core/src/com/unciv/ui/worldscreen/TradePopup.kt index 79e7b1068e..d7b59887ba 100644 --- a/core/src/com/unciv/ui/worldscreen/TradePopup.kt +++ b/core/src/com/unciv/ui/worldscreen/TradePopup.kt @@ -10,7 +10,10 @@ import com.unciv.models.translations.tr import com.unciv.ui.popup.Popup import com.unciv.ui.trade.DiplomacyScreen import com.unciv.ui.trade.LeaderIntroTable -import com.unciv.ui.utils.* +import com.unciv.ui.utils.KeyCharAndCode +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.pad +import com.unciv.ui.utils.extensions.toLabel import kotlin.math.max import kotlin.math.min import com.unciv.ui.utils.AutoScrollPane as ScrollPane @@ -23,9 +26,9 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane /** * [Popup] communicating trade offers of others to the player. - * + * * Called in [WorldScreen].update, which checks if there are any in viewingCiv.tradeRequests. - * + * * @param worldScreen The parent screen */ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){ diff --git a/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt index a6c9d73cc8..45f439fa60 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt @@ -33,7 +33,7 @@ import com.unciv.models.UncivSound import com.unciv.models.helpers.MapArrowType import com.unciv.models.helpers.MiscArrowTypes import com.unciv.ui.UncivStage -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter @@ -43,12 +43,12 @@ import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.tilegroups.WorldTileGroup import com.unciv.ui.utils.UnitGroup import com.unciv.ui.utils.ZoomableScrollPane -import com.unciv.ui.utils.center -import com.unciv.ui.utils.colorFromRGB -import com.unciv.ui.utils.darken -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.surroundWithCircle -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.extensions.center +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle +import com.unciv.ui.utils.extensions.toLabel import com.unciv.utils.Log @@ -238,7 +238,7 @@ class WorldMapHolder( worldScreen.shouldUpdate = true val attacker = MapUnitCombatant(unit) if (!Battle.movePreparingAttack(attacker, attackableTile)) return - Sounds.play(attacker.getAttackSound()) + SoundPlayer.play(attacker.getAttackSound()) Battle.attackOrNuke(attacker, attackableTile) return } @@ -283,7 +283,7 @@ class WorldMapHolder( selectedUnit.movement.moveToTile(tileToMoveTo) if (selectedUnit.isExploring() || selectedUnit.isMoving()) selectedUnit.action = null // remove explore on manual move - Sounds.play(UncivSound.Whoosh) + SoundPlayer.play(UncivSound.Whoosh) if (selectedUnit.currentTile != targetTile) selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt() if (selectedUnit.currentMovement > 0) worldScreen.bottomUnitTable.selectUnit(selectedUnit) @@ -306,7 +306,7 @@ class WorldMapHolder( selectedUnit.action = null // remove explore on manual swap-move // Play something like a swish-swoosh - Sounds.play(UncivSound.Swap) + SoundPlayer.play(UncivSound.Swap) if (selectedUnit.currentMovement > 0) worldScreen.bottomUnitTable.selectUnit(selectedUnit) diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 87c0f8ec80..56f66506d4 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -57,16 +57,16 @@ import com.unciv.ui.trade.DiplomacyScreen import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.centerX -import com.unciv.ui.utils.colorFromRGB -import com.unciv.ui.utils.darken -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.enable -import com.unciv.ui.utils.isEnabled -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.setFontSize -import com.unciv.ui.utils.toLabel -import com.unciv.ui.utils.toTextButton +import com.unciv.ui.utils.extensions.centerX +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.enable +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.victoryscreen.VictoryScreen import com.unciv.ui.worldscreen.bottombar.BattleTable import com.unciv.ui.worldscreen.bottombar.TileInfoTable diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt b/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt index f28a00483b..58cd450849 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt @@ -25,13 +25,13 @@ import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.MayaCalendar import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip -import com.unciv.ui.utils.colorFromRGB -import com.unciv.ui.utils.darken -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.setFontColor -import com.unciv.ui.utils.setFontSize -import com.unciv.ui.utils.toLabel -import com.unciv.ui.utils.toTextButton +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontColor +import com.unciv.ui.utils.extensions.setFontSize +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.victoryscreen.VictoryScreen import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup import kotlin.math.abs diff --git a/core/src/com/unciv/ui/worldscreen/ZoomButtonPair.kt b/core/src/com/unciv/ui/worldscreen/ZoomButtonPair.kt index 982cd8dd45..6ed594731d 100644 --- a/core/src/com/unciv/ui/worldscreen/ZoomButtonPair.kt +++ b/core/src/com/unciv/ui/worldscreen/ZoomButtonPair.kt @@ -6,8 +6,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ZoomableScrollPane -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.setFontSize +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize class ZoomButtonPair(private val mapHolder: ZoomableScrollPane) : Table(BaseScreen.skin) { init { diff --git a/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt b/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt index a9f009383c..0d177da568 100644 --- a/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt +++ b/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt @@ -7,15 +7,27 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.UncivGame import com.unciv.logic.automation.BattleHelper import com.unciv.logic.automation.UnitAutomation -import com.unciv.logic.battle.* +import com.unciv.logic.battle.Battle +import com.unciv.logic.battle.BattleDamage +import com.unciv.logic.battle.CityCombatant +import com.unciv.logic.battle.ICombatant +import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.map.TileInfo import com.unciv.models.AttackableTile import com.unciv.models.UncivSound import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.UnitGroup +import com.unciv.ui.utils.extensions.addBorderAllowOpacity +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.worldscreen.bottombar.BattleTableHelpers.flashWoundedCombatants import com.unciv.ui.worldscreen.bottombar.BattleTableHelpers.getHealthBar @@ -254,7 +266,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() { //Gdx.graphics.requestRendering() // Use this if immediate rendering is required if (!canStillAttack) return - Sounds.play(attacker.getAttackSound()) + SoundPlayer.play(attacker.getAttackSound()) Battle.attackOrNuke(attacker, attackableTile) worldScreen.flashWoundedCombatants(attacker, damageToAttacker, defender, damageToDefender) diff --git a/core/src/com/unciv/ui/worldscreen/bottombar/TileInfoTable.kt b/core/src/com/unciv/ui/worldscreen/bottombar/TileInfoTable.kt index f3e21cd80c..b7be42c06c 100644 --- a/core/src/com/unciv/ui/worldscreen/bottombar/TileInfoTable.kt +++ b/core/src/com/unciv/ui/worldscreen/bottombar/TileInfoTable.kt @@ -10,7 +10,10 @@ import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.FormattedLine.IconDisplay import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.extensions.addBorderAllowOpacity +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.toLabel class TileInfoTable(private val viewingCiv :CivilizationInfo) : Table(BaseScreen.skin) { init { diff --git a/core/src/com/unciv/ui/worldscreen/minimap/MapOverlayToggleButton.kt b/core/src/com/unciv/ui/worldscreen/minimap/MapOverlayToggleButton.kt index 01fdc529e3..b2f09e772c 100644 --- a/core/src/com/unciv/ui/worldscreen/minimap/MapOverlayToggleButton.kt +++ b/core/src/com/unciv/ui/worldscreen/minimap/MapOverlayToggleButton.kt @@ -5,8 +5,8 @@ import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.ui.Image import com.unciv.UncivGame import com.unciv.ui.images.IconCircleGroup -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.surroundWithCircle +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle /** * Class that unifies the behaviour of the little green map overlay toggle buttons shown next to the minimap. diff --git a/core/src/com/unciv/ui/worldscreen/minimap/Minimap.kt b/core/src/com/unciv/ui/worldscreen/minimap/Minimap.kt index f681874dee..165a171896 100644 --- a/core/src/com/unciv/ui/worldscreen/minimap/Minimap.kt +++ b/core/src/com/unciv/ui/worldscreen/minimap/Minimap.kt @@ -11,6 +11,8 @@ import com.unciv.logic.map.MapShape import com.unciv.logic.map.MapSize import com.unciv.ui.images.ClippingImage import com.unciv.ui.images.ImageGetter +import com.unciv.ui.utils.* +import com.unciv.ui.utils.extensions.* import com.unciv.ui.worldscreen.WorldMapHolder import kotlin.math.max import kotlin.math.min diff --git a/core/src/com/unciv/ui/worldscreen/minimap/MinimapTile.kt b/core/src/com/unciv/ui/worldscreen/minimap/MinimapTile.kt index 2dfd893b37..5f5d1ff73d 100644 --- a/core/src/com/unciv/ui/worldscreen/minimap/MinimapTile.kt +++ b/core/src/com/unciv/ui/worldscreen/minimap/MinimapTile.kt @@ -11,8 +11,8 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.TileInfo import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.surroundWithCircle +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle import kotlin.math.PI import kotlin.math.atan diff --git a/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusButton.kt b/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusButton.kt index 2d32997382..3a3905d9ab 100644 --- a/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusButton.kt +++ b/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusButton.kt @@ -24,8 +24,8 @@ import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.setSize +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setSize import kotlinx.coroutines.delay import java.time.Duration import java.time.Instant diff --git a/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusPopup.kt b/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusPopup.kt index 45aab7bdd9..91a9623958 100644 --- a/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/status/MultiplayerStatusPopup.kt @@ -8,7 +8,7 @@ import com.unciv.ui.multiplayer.MultiplayerHelpers import com.unciv.ui.pickerscreens.PickerPane import com.unciv.ui.popup.Popup import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.extensions.onClick class MultiplayerStatusPopup( screen: BaseScreen, diff --git a/core/src/com/unciv/ui/worldscreen/status/NextTurnButton.kt b/core/src/com/unciv/ui/worldscreen/status/NextTurnButton.kt index c9b6e6fb44..901568d30d 100644 --- a/core/src/com/unciv/ui/worldscreen/status/NextTurnButton.kt +++ b/core/src/com/unciv/ui/worldscreen/status/NextTurnButton.kt @@ -4,7 +4,11 @@ import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.models.translations.tr -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyPressDispatcher +import com.unciv.ui.utils.extensions.isEnabled +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontSize class NextTurnButton( keyPressDispatcher: KeyPressDispatcher diff --git a/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt b/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt index 72ef5b11b3..276f11c204 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt @@ -6,8 +6,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.logic.map.MapUnit import com.unciv.ui.images.ImageGetter -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.pad +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.pad import com.unciv.ui.worldscreen.WorldMapHolder class IdleUnitButton ( diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index c90b97692a..fdde7e1943 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -26,7 +26,7 @@ import com.unciv.ui.pickerscreens.ImprovementPickerScreen import com.unciv.ui.pickerscreens.PromotionPickerScreen import com.unciv.ui.popup.YesNoPopup import com.unciv.ui.popup.hasOpenPopups -import com.unciv.ui.utils.toPercent +import com.unciv.ui.utils.extensions.toPercent import com.unciv.ui.worldscreen.WorldScreen import kotlin.math.min import kotlin.random.Random diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt index 3608d71563..333f4d1004 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt @@ -6,12 +6,14 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.UncivGame import com.unciv.logic.map.MapUnit import com.unciv.models.UnitAction -import com.unciv.ui.audio.Sounds +import com.unciv.ui.audio.SoundPlayer import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.images.IconTextButton -import com.unciv.ui.utils.* +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.KeyPressDispatcher.Companion.keyboardAvailable import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.extensions.disable +import com.unciv.ui.utils.extensions.onClick import com.unciv.ui.worldscreen.WorldScreen class UnitActionsTable(val worldScreen: WorldScreen) : Table() { @@ -44,7 +46,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() { actionButton.onClick(unitAction.uncivSound, action) if (key != KeyCharAndCode.UNKNOWN) worldScreen.keyPressDispatcher[key] = { - launchCrashHandling("UnitSound") { Sounds.play(unitAction.uncivSound) } + launchCrashHandling("UnitSound") { SoundPlayer.play(unitAction.uncivSound) } action() worldScreen.mapHolder.removeUnitActionOverlay() } diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt index 2e5d301e64..61869d68bc 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt @@ -17,7 +17,12 @@ import com.unciv.ui.civilopedia.CivilopediaCategories import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.images.ImageGetter import com.unciv.ui.pickerscreens.PromotionPickerScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.UnitGroup +import com.unciv.ui.utils.extensions.addSeparator +import com.unciv.ui.utils.extensions.darken +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.worldscreen.WorldScreen class UnitTable(val worldScreen: WorldScreen) : Table(){ @@ -163,17 +168,17 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ unitDescriptionTable.add("XP") unitDescriptionTable.add(unit.promotions.XP.toString() + "/" + unit.promotions.xpForNextPromotion()) } - + if (unit.canDoReligiousAction(Constants.spreadReligionAbilityCount)) { unitDescriptionTable.add(ImageGetter.getStatIcon("Faith")).size(20f) unitDescriptionTable.add(unit.getActionString(Constants.spreadReligionAbilityCount)) } - + if (unit.canDoReligiousAction(Constants.removeHeresyAbilityCount)) { unitDescriptionTable.add(ImageGetter.getImage("OtherIcons/Remove Heresy")).size(20f) unitDescriptionTable.add(unit.getActionString(Constants.removeHeresyAbilityCount)) } - + if (unit.baseUnit.religiousStrength > 0) { unitDescriptionTable.add(ImageGetter.getStatIcon("ReligiousStrength")).size(20f) unitDescriptionTable.add((unit.baseUnit.religiousStrength - unit.religiousStrengthLost).toString()) diff --git a/docs/Credits.md b/docs/Credits.md index e5322b35c8..55f9b00194 100644 --- a/docs/Credits.md +++ b/docs/Credits.md @@ -742,6 +742,8 @@ Sounds are from FreeSound.org unless otherwise noted and are either Creative Com - [elephant 44](https://freesound.org/people/y89312/sounds/139875/) by y89312 for Naruesan's Elephant sound - Excerpt from [Missile Strike](https://freesound.org/people/BaDoink/sounds/570690/) by BaDoink for guided missile - Excerpt from [FireBurning_v2.wav](https://freesound.org/people/pcaeldries/sounds/30322/) by pcaeldries for 'remove heresy' action of inquisitor ([License](http://creativecommons.org/licenses/by/3.0/)) +- [Up Chime 2](https://freesound.org/people/FoolBoyMedia/sounds/352666/) by FoolBoyMedia for notifications +- [dingaling](https://freesound.org/people/morrisjm/sounds/268756/) by morrisjm (based on [Calling_Bell_02.wav](https://freesound.org/people/RSilveira_88/sounds/216306/) by RSilveira_88) for notifications ## Music