Merge lp:~widelands-dev/widelands/campaignselect_box into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 9067
Proposed branch: lp:~widelands-dev/widelands/campaignselect_box
Merge into: lp:widelands
Diff against target: 2711 lines (+1143/-949)
39 files modified
data/campaigns/atl01.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/bar01.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/bar02.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/campaigns.lua (+127/-141)
data/campaigns/dummy.wmf/elemental (+1/-0)
data/campaigns/emp01.wmf/scripting/mission_thread.lua (+1/-1)
data/campaigns/emp02.wmf/scripting/mission_thread.lua (+1/-2)
data/campaigns/emp03.wmf/scripting/mission_thread.lua (+1/-1)
data/campaigns/emp04.wmf/scripting/mission_thread.lua (+1/-3)
data/campaigns/tutorials.lua (+12/-28)
src/base/i18n.cc (+9/-0)
src/base/i18n.h (+11/-2)
src/graphic/text_layout.cc (+1/-0)
src/graphic/text_layout.h (+2/-0)
src/logic/CMakeLists.txt (+0/-11)
src/logic/filesystem_constants.h (+2/-1)
src/scripting/CMakeLists.txt (+0/-1)
src/scripting/lua_game.cc (+10/-30)
src/scripting/lua_game.h (+1/-2)
src/ui_basic/table.cc (+3/-3)
src/ui_basic/table.h (+2/-8)
src/ui_fsmenu/CMakeLists.txt (+12/-1)
src/ui_fsmenu/campaign_select.cc (+44/-397)
src/ui_fsmenu/campaign_select.h (+10/-90)
src/ui_fsmenu/campaigndetails.cc (+82/-0)
src/ui_fsmenu/campaigndetails.h (+41/-0)
src/ui_fsmenu/campaigns.cc (+198/-158)
src/ui_fsmenu/campaigns.h (+59/-13)
src/ui_fsmenu/scenario_select.cc (+230/-0)
src/ui_fsmenu/scenario_select.h (+65/-0)
src/ui_fsmenu/scenariodetails.cc (+74/-0)
src/ui_fsmenu/scenariodetails.h (+41/-0)
src/wlapplication.cc (+12/-9)
src/wui/CMakeLists.txt (+2/-1)
src/wui/load_or_save_game.cc (+4/-6)
src/wui/mapauthordata.h (+72/-0)
src/wui/mapdata.cc (+7/-7)
src/wui/mapdata.h (+1/-27)
src/wui/mapdetails.h (+1/-0)
To merge this branch: bzr merge lp:~widelands-dev/widelands/campaignselect_box
Reviewer Review Type Date Requested Status
Toni Förster Approve
Klaus Halfmann review compile, testplay Approve
Review via email: mp+360901@code.launchpad.net

Commit message

Redesigned the campaign/scenario selection screens to use Box layout

- Tables entries can now be greyed out
- Fixed fullscreen switching
- Converted campaigns definition to Lua
- Mark scenarios as solved rather than as unlocked
- Automatic conversion of legacy campvis file contents if the new file
  doesn't exist yet so that players will not lose their progress
- Files to be included in the "load savegame" screens are now determined
  by file extension

To post a comment you must log in.
8400. By GunChleoc

Small code improvements

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4335. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/467787988.
Appveyor build 4130. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4130.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Findings:
  compiles locally on OSX.
  src/logic/campaign_visibility.h/.c was move to src/ui_fsmenu/campaigns.h/c
  campaigns.conf -> campaigns.ua (to speed upp reading, I assume)
  Many reefactorings around campains and scenarios
  Travis fails with
    Could not find ICU include directory ?
    Debug build fails with
     ...doc/sphinx/source/autogen_ai_hints.rst:117:
        Definition list ends without a blank line; unexpected unindent.
   AppVeyor / Windows fails witth
     Could not find the following static Boost libraries:
          boost_unit_test_framework
          boost_regex
          boost_system

Questions:
  What is the this mark_scenario_as_solved(..) function about?

I will contiune checking how and if you fixed the relateed bugs.

review: Needs Fixing (review, compile)
Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

* Bug #627361: Show available campaigns and missions greyed out
  Fine for me except:

  - after finishing the last available mission widelands should present a message like
  "Thank you for playing all available missions of widelands."

  Mhh, All excpet frisisans show "unimplemened" as last Scenario,
  but it cannto be played.
  how shall I reeach this this?

campaigns.conf is tee scucessor for campvis ?

Bug #1377660: Fullscreen Menu overhaul

  This is a kind of wishlist dating back to 2014 (!)

Bug #1634750: Convert campaigns.conf and tutorials.conf to Lua

  Yep, that it

#1799809: Resize UI when toggling between fullscreen and windows mode

  Metaserver lobby - does not work for me
  Begin Network game - does not work for me
  Map Select - works for me

  which oone did you tackle with this brach?

Revision history for this message
kaputtnik (franku) wrote :

Klaus you may need to merge trunk to get travis and appveyor get work without errors again.

8401. By Klaus Halfman \<<email address hidden>\>

Merged trunk

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4402. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/479000710.
Appveyor build 4193. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4193.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Travis has only one (Timeout) Problem with GCC_VERSION="4.8" BUILD_TYPE="Debug"

appveyor. has Issue in Configuration: Debug

No artifacts found matching 'Widelands-_widelands_dev_widelands_campaignselect_box-4193-Debug-x64.exe' path
strip -sv %APPVEYOR_BUILD_FOLDER%\build\src\widelands.exe

No idea what that is about.

So for me this is OK now.

Anyone else?

review: Approve (review compile, testplay)
Revision history for this message
Toni Förster (stonerl) wrote :

Appveyor wasn't able to upload the artifact, but it built without problems

review: Approve
8402. By kaputtnik

merged trunk

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4407. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/481978815.
Appveyor build 4198. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4198.

8403. By GunChleoc

Fix marking Barbarians1 as solved

8404. By GunChleoc

Merged trunk.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the reviews!

This is for Build 21, so let's not merge it yet.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4449. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/491209077.
Appveyor build 4237. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4237.

8405. By GunChleoc

Merged trunk.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4732. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/521841214.
Appveyor build 4517. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4517.

8406. By GunChleoc

Merged trunk.

Revision history for this message
GunChleoc (gunchleoc) wrote :

@bunnybot merge

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4768. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/523572522.
Appveyor build 4552. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_campaignselect_box-4552.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/campaigns/atl01.wmf/scripting/mission_thread.lua'
2--- data/campaigns/atl01.wmf/scripting/mission_thread.lua 2018-11-16 06:41:28 +0000
3+++ data/campaigns/atl01.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
4@@ -233,8 +233,7 @@
5
6 -- Success
7 msg_boxes(scenario_won)
8- p1:reveal_scenario("atlanteans01")
9- p1:reveal_campaign("campsect3")
10+ p1:mark_scenario_as_solved("atl01.wmf")
11 end
12
13
14
15=== modified file 'data/campaigns/bar01.wmf/scripting/mission_thread.lua'
16--- data/campaigns/bar01.wmf/scripting/mission_thread.lua 2019-01-08 07:57:22 +0000
17+++ data/campaigns/bar01.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
18@@ -327,8 +327,7 @@
19 end
20
21 message_box_objective(plr, msg_mission_complete)
22- plr:reveal_scenario("barbariantut01")
23- plr:reveal_campaign("campsect1")
24+ plr:mark_scenario_as_solved("bar01.wmf")
25 end
26
27
28
29=== modified file 'data/campaigns/bar02.wmf/scripting/mission_thread.lua'
30--- data/campaigns/bar02.wmf/scripting/mission_thread.lua 2018-01-26 09:00:04 +0000
31+++ data/campaigns/bar02.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
32@@ -376,8 +376,7 @@
33
34 campaign_message_box(story_msg_7)
35
36- p1:reveal_scenario("barbariantut02")
37- p1:reveal_campaign("campsect1")
38+ p1:mark_scenario_as_solved("bar02.wmf")
39 end
40
41 run(initial_message_and_small_food_economy)
42
43=== renamed file 'data/campaigns/campaigns.conf' => 'data/campaigns/campaigns.lua'
44--- data/campaigns/campaigns.conf 2018-08-13 16:44:58 +0000
45+++ data/campaigns/campaigns.lua 2019-04-23 16:17:37 +0000
46@@ -1,141 +1,127 @@
47-##########################################
48-# Campaign configuration - file #
49-##########################################
50-
51-
52-
53-#####
54-# Section "global"
55-#
56-# version = Version-number of this file - used to check, whether campvis-
57-# file needs to be updated. Higher the value, if you added a map
58-# or campaign or if you changed a visibility-value.
59-# campname?? = Name of the Campaign with the number ??.
60-# campsect?? = Name of section, to be loaded, if this campaign is selected.
61-# campdiff?? = Level of difficulty 1-3 (easy to hard) (this is only a visua-
62-# lisation, it has no influences on the real difficulty).
63-# campdesc?? = Description of the campaign
64-# campvisi?? = Is campaign visible by default? (1=yes, 0=no)
65-# * cnewvisi?? = the name of a campaign or a scenario that must be visible, to show
66-# this entry as well - this string unhides the map, if the player
67-# completed the requirements with an older cconfig version.
68-#####
69-
70-[global]
71-version = 8
72-# Barbarians Introduction
73-campname0=_"The Second Empire"
74-campsect0=barbariantut
75-camptribe0=_"Barbarians"
76-campdiff0=1
77-campdiffdescr0=_"Easy. Introduces the Barbarians"
78-campdesc0=_"When Chat’Karuth died, he was an old man, father to three strong and ambitious sons, and warlord to an army that could match any enemy willing to rise against the ancient forests. Though at the end of his glorious reign, Chat’Karuth chose his eldest son, Thron, to succeed him as the tribe’s warlord – a decision that left his two brothers unsatisfied. The old warlord knew that. As his father instructed him, Thron left the capital of Al’thunran, the home of the Throne Among the Trees, and withdrew his forces to the high hills where he buried the corpse of his father. There he swore to the gods and his father’s spirit that he’d return to re-established order. While his brothers have raged blind war against Thron and the few forces he left to secure the borders of Al’thunran, the young warlord seeks to reunite his ambitious brothers and force the tribes to march once again under a common banner."
79-campvisi0=1
80-# Empire Introduction
81-campname1=_"The Months of Exile"
82-campsect1=empiretut
83-camptribe1=_"Empire"
84-campdiff1=1
85-campdiffdescr1=_"Easy. Introduces the Empire"
86-campdesc1=_"Six months ago, Lutius – a young general of the Empire – was sent with 150 soldiers to the frontier beyond the northern forests where Barbarian tribes were crossing onto land held by the Empire. His task was to defend the Empire’s land. At first, everything was calm. He even talked to a few Barbarian children and thought about a peaceful life – side by side with this archaic folk. He began to feel safer and his army began to drop their attention off the potential enemy. That was their undoing. One night in March his unprepared army was attacked by 100 Barbarian footmen and was completely scattered. Only with his bare life he and a handful of his soldiers survived."
87-campvisi1=0
88-# Atlantean Introduction
89-campname2=_"The Run for the Fire"
90-campsect2=atlanteans
91-camptribe2=_"Atlanteans"
92-campdiff2=2
93-campdiffdescr2=_"Challenging. Introduces the Atlanteans"
94-campdesc2=_"When their God lost faith in the Atlanteans and drowned their island, one woman’s struggle for justice and a second chance for her people would become the stuff of legends. Leading the remaining Atlanteans into a new future in a new part of the World, Jundlina became the most powerful human of her time, but at a high cost: her humanity and soul."
95-campvisi2=0
96-cnewvisi2=empiretut01
97-# Frisian Introduction
98-campname3=_"From Water to Ice"
99-campsect3=frisians
100-camptribe3=_"Frisians"
101-campdiff3=3
102-campdiffdescr3=_"For advanced players. Introduces the Frisians"
103-campdesc3=_"Living off the ocean is a constant struggle, and even more so for the inhabitants of the Frisian North Sea shore. Was the last storm flood, the most devastating one in human memory, really nothing more than yet another example for the hardships all Frisians have to face – or a sign from the gods that a tribe that only just settled here must seek out an entirely new home?"
104-campvisi3=0
105-cnewvisi3=atlanteans01
106-
107-
108-
109-#####
110-# Sections of the campaign - maps
111-# Naming MUST be the name of the campaign-section + "??" where ?? is an increasing number.
112-#
113-# name = name of the map.
114-# * newvisi = the name of a campaign or a scenario that must be visible, to show
115-# this entry as well - this string unhides the map, if the player
116-# completed the requirements with an older cconfig version.
117-# visible = is this map visible(1), or does it need another map to be played first(0).
118-# path = path to the map.
119-#####
120-
121-[barbariantut00]
122-name=_"A Place to Call Home"
123-visible=1
124-path="campaigns/bar01.wmf"
125-
126-[barbariantut01]
127-name=_"This Land is Our Land"
128-visible=0
129-path="campaigns/bar02.wmf"
130-
131-[barbariantut02]
132-name=_"Not yet implemented"
133-visible=0
134-path="campaigns/dummy.wmf"
135-
136-[empiretut00]
137-name=_"The Strands of Malac’ Mor"
138-visible=1
139-path="campaigns/emp01.wmf"
140-
141-[empiretut01]
142-name=_"An Outpost for Exile"
143-visible=0
144-path="campaigns/emp02.wmf"
145-
146-[empiretut02]
147-name=_"Neptune’s Revenge"
148-visible=0
149-path="campaigns/emp03.wmf"
150-
151-[empiretut03]
152-name=_"Surprise, Surprise!"
153-visible=0
154-path="campaigns/emp04.wmf"
155-
156-[empiretut04]
157-name=_"Not yet implemented"
158-newvisi="campsect2"
159-visible=0
160-path="campaigns/dummy.wmf"
161-
162-
163-[atlanteans00]
164-name=_"From Nemesis to Genesis"
165-visible=1
166-path="campaigns/atl01.wmf"
167-
168-[atlanteans01]
169-name=_"Not yet implemented"
170-visible=0
171-path="campaigns/dummy.wmf"
172-
173-
174-[frisians00]
175-name=_"The Great Stormflood"
176-visible=1
177-path="campaigns/fri01.wmf"
178-
179-[frisians01]
180-name=_"Colder than Ice"
181-visible=0
182-path="campaigns/fri02.wmf"
183-
184-[frisians02]
185-name=_"Not yet implemented"
186-visible=0
187-path="campaigns/dummy.wmf"
188+--##########################################
189+--# Campaign configuration - file #
190+--##########################################
191+
192+return {
193+ --##########################################
194+ --# Descriptions of difficulty levels #
195+ --##########################################
196+ difficulties = {
197+ {
198+ -- This will be prefixed to any text that you might add in each
199+ -- campaign's difficulty description.
200+ -- TRANSLATORS: The difficulty level of a campign
201+ descname = _"Easy.",
202+ -- An image to represent the difficulty level
203+ image = "images/ui_fsmenu/easy.png",
204+ },
205+ {
206+ -- TRANSLATORS: The difficulty level of a campign
207+ descname = _"Medium.",
208+ image = "images/ui_fsmenu/medium.png",
209+ },
210+ {
211+ -- TRANSLATORS: The difficulty level of a campign
212+ descname = _"Hard.",
213+ image = "images/ui_fsmenu/hard.png",
214+ },
215+ {
216+ -- TRANSLATORS: The difficulty level of a campign
217+ descname = _"Challenging.",
218+ image = "images/ui_fsmenu/challenging.png",
219+ },
220+ },
221+
222+ --##########################################
223+ --# The campaigns themselves #
224+ --##########################################
225+ campaigns = {
226+ {
227+ -- **** Barbarians Introduction ****
228+ -- The name the user sees on screen
229+ -- TRANSLATORS: The name of a Barbarian campign
230+ descname = _"The Second Empire",
231+ -- The internal name of the tribe that the user will be playing
232+ tribe = "barbarians",
233+ -- The difficulty of this campaign.
234+ -- Start counting at 1 in the "difficulties" table above
235+ -- TRANSLATORS: A short description of a campign
236+ difficulty = { level=1, description=_"Introduces the Barbarians." },
237+ -- An introduction story
238+ -- TRANSLATORS: A long description of a campign
239+ description = _"When Chat’Karuth died, he was an old man, father to three strong and ambitious sons, and warlord to an army that could match any enemy willing to rise against the ancient forests. Though at the end of his glorious reign, Chat’Karuth chose his eldest son, Thron, to succeed him as the tribe’s warlord – a decision that left his two brothers unsatisfied. The old warlord knew that. As his father instructed him, Thron left the capital of Al’thunran, the home of the Throne Among the Trees, and withdrew his forces to the high hills where he buried the corpse of his father. There he swore to the gods and his father’s spirit that he’d return to re-established order. While his brothers have raged blind war against Thron and the few forces he left to secure the borders of Al’thunran, the young warlord seeks to reunite his ambitious brothers and force the tribes to march once again under a common banner.",
240+ -- The campaign's scenarios. The first scenario is always visible if
241+ -- the campaign itself is visible.
242+ -- Paths to the scenarios are relative to data/campaigns
243+ -- Once a scenario has been marked as solved by calling
244+ -- `player:mark_scenario_as_solved`, the next scenario in the list will
245+ -- become visible.
246+ -- Also, campaigns that have a prerequisite scenarios will be unlocked
247+ -- when any of the referenced scenarios has been solved.
248+ scenarios = {
249+ "bar01.wmf",
250+ "bar02.wmf",
251+ "dummy.wmf"
252+ }
253+ },
254+ {
255+ -- **** Empire Introduction ****
256+ -- TRANSLATORS: The name of an Empire campign
257+ descname = _"The Months of Exile",
258+ tribe = "empire",
259+ -- TRANSLATORS: A short description of a campign
260+ difficulty = { level=2, description=_"Introduces the Empire." },
261+ -- TRANSLATORS: A long description of a campign
262+ description = _"Six months ago, Lutius – a young general of the Empire – was sent with 150 soldiers to the frontier beyond the northern forests where Barbarian tribes were crossing onto land held by the Empire. His task was to defend the Empire’s land. At first, everything was calm. He even talked to a few Barbarian children and thought about a peaceful life – side by side with this archaic folk. He began to feel safer and his army began to drop their attention off the potential enemy. That was their undoing. One night in March his unprepared army was attacked by 100 Barbarian footmen and was completely scattered. Only with his bare life he and a handful of his soldiers survived.",
263+ -- If `prerequisites` is present, the campaign is greyed out by default.
264+ -- The campaign will become unlocked when any of the referenced scenarios
265+ -- have been solved.
266+ prerequisites = {
267+ "bar01.wmf",
268+ },
269+ scenarios = {
270+ "emp01.wmf",
271+ "emp02.wmf",
272+ "emp03.wmf",
273+ "emp04.wmf",
274+ "dummy.wmf"
275+ }
276+ },
277+ {
278+ -- **** Atlantean Introduction ****
279+ -- TRANSLATORS: The name of an Atlantean campign
280+ descname = _"The Run for the Fire",
281+ tribe = "atlanteans",
282+ -- TRANSLATORS: A short description of a campign
283+ difficulty = { level=3, description=_"Introduces the Atlanteans." },
284+ -- TRANSLATORS: A long description of a campign
285+ description = _"When their God lost faith in the Atlanteans and drowned their island, one woman’s struggle for justice and a second chance for her people would become the stuff of legends. Leading the remaining Atlanteans into a new future in a new part of the World, Jundlina became the most powerful human of her time, but at a high cost: her humanity and soul.",
286+ prerequisites = {
287+ "emp02.wmf",
288+ },
289+ scenarios = {
290+ "atl01.wmf",
291+ "dummy.wmf"
292+ }
293+ },
294+ {
295+ -- **** Frisian Introduction ****
296+ -- TRANSLATORS: The name of a Frisian campign
297+ descname = _"From Water to Ice",
298+ tribe = "frisians",
299+ -- TRANSLATORS: A short description of a campign
300+ difficulty = { level=4, description=_"Introduces the Frisians." },
301+ -- TRANSLATORS: A long description of a campign
302+ description = _"Living off the ocean is a constant struggle, and even more so for the inhabitants of the Frisian North Sea shore. Was the last storm flood, the most devastating one in human memory, really nothing more than yet another example for the hardships all Frisians have to face – or a sign from the gods that a tribe that only just settled here must seek out an entirely new home?",
303+ prerequisites = {
304+ "emp04.wmf",
305+ "atl01.wmf",
306+ },
307+ scenarios = {
308+ "fri01.wmf",
309+ "fri02.wmf",
310+ "dummy.wmf"
311+ }
312+ }
313+ }
314+}
315
316=== modified file 'data/campaigns/dummy.wmf/elemental'
317--- data/campaigns/dummy.wmf/elemental 2017-12-17 19:06:02 +0000
318+++ data/campaigns/dummy.wmf/elemental 2019-04-23 16:17:37 +0000
319@@ -8,6 +8,7 @@
320 name=_"Not yet implemented"
321 # TRANSLATORS: Author for dummy map
322 author=_"Nobody"
323+# TRANSLATORS: Description for greyed out dummy scenario displayed in campaign screen
324 descr=_"Sorry, this map is not yet implemented."
325 hint=
326 tags=
327
328=== modified file 'data/campaigns/emp01.wmf/scripting/mission_thread.lua'
329--- data/campaigns/emp01.wmf/scripting/mission_thread.lua 2017-06-25 12:53:48 +0000
330+++ data/campaigns/emp01.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
331@@ -89,7 +89,7 @@
332 sleep(25000) -- Sleep a while
333
334 campaign_message_box(diary_page_4)
335- p1:reveal_scenario("empiretut01")
336+ p1:mark_scenario_as_solved("emp01.wmf")
337 end
338
339 -- Show a funny message when the player has build 10 blockhouses
340
341=== modified file 'data/campaigns/emp02.wmf/scripting/mission_thread.lua'
342--- data/campaigns/emp02.wmf/scripting/mission_thread.lua 2018-11-19 08:09:41 +0000
343+++ data/campaigns/emp02.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
344@@ -265,8 +265,7 @@
345 campaign_message_box(seven_days_later)
346 campaign_message_box(diary_page_11)
347
348- p1:reveal_scenario("empiretut02")
349- p1:reveal_campaign("campsect2")
350+ p1:mark_scenario_as_solved("emp02.wmf")
351 end
352
353 run(building_materials)
354
355=== modified file 'data/campaigns/emp03.wmf/scripting/mission_thread.lua'
356--- data/campaigns/emp03.wmf/scripting/mission_thread.lua 2018-09-13 21:00:11 +0000
357+++ data/campaigns/emp03.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
358@@ -265,7 +265,7 @@
359 sleep(25000)
360 campaign_message_box(diary_page_5)
361
362- p1:reveal_scenario("empiretut03")
363+ p1:mark_scenario_as_solved("emp03.wmf")
364 end
365
366 -- After discovery of Barbarian ruins, we should hurry to build a full training capability
367
368=== modified file 'data/campaigns/emp04.wmf/scripting/mission_thread.lua'
369--- data/campaigns/emp04.wmf/scripting/mission_thread.lua 2018-11-16 06:41:28 +0000
370+++ data/campaigns/emp04.wmf/scripting/mission_thread.lua 2019-04-23 16:17:37 +0000
371@@ -389,9 +389,7 @@
372 sleep(25000)
373 campaign_message_box(diary_page_4)
374
375- p1:reveal_campaign("campsect2")
376- p1:reveal_campaign("campsect3")
377- p1:reveal_scenario("empiretut04")
378+ p1:mark_scenario_as_solved("emp04.wmf")
379 end
380
381 -- another production chain that is ineffective and needs to be corrected
382
383=== renamed file 'data/campaigns/tutorials.conf' => 'data/campaigns/tutorials.lua'
384--- data/campaigns/tutorials.conf 2018-08-13 16:44:58 +0000
385+++ data/campaigns/tutorials.lua 2019-04-23 16:17:37 +0000
386@@ -1,28 +1,12 @@
387-##########################################
388-# Tutorials configuration - file #
389-##########################################
390-
391-
392-#####
393-# Sections of the tutorial - maps
394-# Naming MUST be the name of the tutorial-section + "??" where ?? is an increasing number.
395-#
396-# name = name of the map.
397-# path = path to the map.
398-#####
399-
400-[tutorials00]
401-name=_"Basic Control"
402-path="campaigns/tutorial01_basic_control.wmf"
403-
404-[tutorials01]
405-name=_"Warfare"
406-path="campaigns/tutorial02_warfare.wmf"
407-
408-[tutorials02]
409-name=_"Seafaring"
410-path="campaigns/tutorial03_seafaring.wmf"
411-
412-[tutorials03]
413-name=_"Economy"
414-path="campaigns/tutorial04_economy.wmf"
415+--##########################################
416+--# Tutorials configuration - file #
417+--##########################################
418+
419+return {
420+ -- The tutorial scenarios in the order that they will appear on screen
421+ -- The paths are relative to data/campaigns
422+ "tutorial01_basic_control.wmf",
423+ "tutorial02_warfare.wmf",
424+ "tutorial03_seafaring.wmf",
425+ "tutorial04_economy.wmf"
426+}
427
428=== renamed file 'data/images/ui_fsmenu/hard.png' => 'data/images/ui_fsmenu/challenging.png'
429=== added file 'data/images/ui_fsmenu/easy.png'
430Binary files data/images/ui_fsmenu/easy.png 1970-01-01 00:00:00 +0000 and data/images/ui_fsmenu/easy.png 2019-04-23 16:17:37 +0000 differ
431=== renamed file 'data/images/ui_fsmenu/challenging.png' => 'data/images/ui_fsmenu/hard.png'
432=== renamed file 'data/images/ui_fsmenu/easy.png' => 'data/images/ui_fsmenu/medium.png'
433=== modified file 'src/base/i18n.cc'
434--- src/base/i18n.cc 2019-02-23 11:00:49 +0000
435+++ src/base/i18n.cc 2019-04-23 16:17:37 +0000
436@@ -335,6 +335,7 @@
437 }
438
439 std::string localize_list(const std::vector<std::string>& items, ConcatenateWith listtype) {
440+ i18n::Textdomain td("widelands");
441 std::string result;
442 for (std::vector<std::string>::const_iterator it = items.begin(); it != items.end(); ++it) {
443 if (it == items.begin()) {
444@@ -365,4 +366,12 @@
445 }
446 return result;
447 }
448+
449+std::string join_sentences(const std::string& sentence1, const std::string& sentence2) {
450+ i18n::Textdomain td("widelands");
451+ /** TRANSLATORS: Put 2 sentences one after the other. Languages using Chinese script probably
452+ * want to lose the blank space here. */
453+ return (boost::format(pgettext("sentence_separator", "%1% %2%")) % sentence1 % sentence2).str();
454+}
455+
456 } // namespace i18n
457
458=== modified file 'src/base/i18n.h'
459--- src/base/i18n.h 2019-02-23 11:00:49 +0000
460+++ src/base/i18n.h 2019-04-23 16:17:37 +0000
461@@ -58,10 +58,19 @@
462 void set_localedir(const std::string&);
463 const std::string& get_localedir();
464
465-// Localize a list of 'items'. The last 2 items are concatenated with "and" or
466-// "or", depending on 'concatenate_with'.
467 enum class ConcatenateWith { AND, OR, AMPERSAND, COMMA };
468+/**
469+ * Localize a list of 'items'. The last 2 items are concatenated with "and" or
470+ * "or" etc, depending on 'concatenate_with'.
471+ */
472 std::string localize_list(const std::vector<std::string>& items, ConcatenateWith concatenate_with);
473+
474+/**
475+ * Joins 2 sentences together. Use this rather than manually concatenating
476+ * a blank space, because some languages don't use blank spaces.
477+ */
478+std::string join_sentences(const std::string& sentence1, const std::string& sentence2);
479+
480 } // namespace i18n
481
482 #endif // end of include guard: WL_BASE_I18N_H
483
484=== modified file 'src/graphic/text_layout.cc'
485--- src/graphic/text_layout.cc 2019-02-23 11:00:49 +0000
486+++ src/graphic/text_layout.cc 2019-04-23 16:17:37 +0000
487@@ -222,6 +222,7 @@
488 }
489 NEVER_HERE();
490 }
491+
492 std::string as_content(const std::string& txt, UI::PanelStyle style) {
493 switch (style) {
494 case UI::PanelStyle::kFsMenu:
495
496=== modified file 'src/graphic/text_layout.h'
497--- src/graphic/text_layout.h 2019-02-23 11:00:49 +0000
498+++ src/graphic/text_layout.h 2019-04-23 16:17:37 +0000
499@@ -105,9 +105,11 @@
500 bool noescape = false);
501
502 /**
503+ * Heading in menu info texts
504 * 'is_first' omits the vertical gap before the line.
505 */
506 std::string as_heading(const std::string& txt, UI::PanelStyle style, bool is_first = false);
507+/// Paragraph in menu info texts
508 std::string as_content(const std::string& txt, UI::PanelStyle style);
509
510 /**
511
512=== modified file 'src/logic/CMakeLists.txt'
513--- src/logic/CMakeLists.txt 2019-02-27 17:19:00 +0000
514+++ src/logic/CMakeLists.txt 2019-04-23 16:17:37 +0000
515@@ -56,17 +56,6 @@
516 wui
517 )
518
519-wl_library(logic_campaign_visibility
520- SRCS
521- campaign_visibility.cc
522- campaign_visibility.h
523- DEPENDS
524- base_exceptions
525- io_filesystem
526- logic_filesystem_constants
527- profile
528-)
529-
530 wl_library(logic_constants
531 SRCS
532 widelands.cc
533
534=== modified file 'src/logic/filesystem_constants.h'
535--- src/logic/filesystem_constants.h 2019-02-23 11:00:49 +0000
536+++ src/logic/filesystem_constants.h 2019-04-23 16:17:37 +0000
537@@ -34,6 +34,7 @@
538
539 /// Filesystem names for maps
540 const std::string kMapsDir = "maps";
541+const std::string kCampaignsDir = "campaigns";
542 const std::string kWidelandsMapExtension = ".wmf";
543 const std::string kS2MapExtension1 = ".swd";
544 const std::string kS2MapExtension2 = ".wld";
545@@ -61,7 +62,7 @@
546
547 /// Filesystem names and intervals for savegames
548 const std::string kSaveDir = "save";
549-const std::string kCampVisFile = "save/campvis";
550+const std::string kCampVisFile = "save/campaigns.conf";
551 const std::string kSavegameExtension = ".wgf";
552 const std::string kAutosavePrefix = "wl_autosave";
553 // Default autosave interval in minutes
554
555=== modified file 'src/scripting/CMakeLists.txt'
556--- src/scripting/CMakeLists.txt 2018-09-14 08:44:42 +0000
557+++ src/scripting/CMakeLists.txt 2019-04-23 16:17:37 +0000
558@@ -109,7 +109,6 @@
559 io_fileread
560 io_filesystem
561 logic
562- logic_campaign_visibility
563 logic_constants
564 logic_filesystem_constants
565 logic_game_controller
566
567=== modified file 'src/scripting/lua_game.cc'
568--- src/scripting/lua_game.cc 2019-04-09 16:43:49 +0000
569+++ src/scripting/lua_game.cc 2019-04-23 16:17:37 +0000
570@@ -25,7 +25,7 @@
571
572 #include "economy/economy.h"
573 #include "economy/flag.h"
574-#include "logic/campaign_visibility.h"
575+#include "logic/filesystem_constants.h"
576 #include "logic/game_controller.h"
577 #include "logic/map_objects/tribes/tribe_descr.h"
578 #include "logic/message.h"
579@@ -93,8 +93,7 @@
580 METHOD(LuaPlayer, add_objective),
581 METHOD(LuaPlayer, reveal_fields),
582 METHOD(LuaPlayer, hide_fields),
583- METHOD(LuaPlayer, reveal_scenario),
584- METHOD(LuaPlayer, reveal_campaign),
585+ METHOD(LuaPlayer, mark_scenario_as_solved),
586 METHOD(LuaPlayer, get_ships),
587 METHOD(LuaPlayer, get_buildings),
588 METHOD(LuaPlayer, get_suitability),
589@@ -623,42 +622,23 @@
590 }
591
592 /* RST
593- .. method:: reveal_scenario(name)
594+ .. method:: mark_scenario_as_solved(name)
595
596- This reveals a scenario inside a campaign. This only works for the
597+ Marks a campaign scenario as solved. Reads the scenario definition in data/campaigns/campaigns.lua
598+ to check which scenario and/or campaign should be revealed as a result. This only works for the
599 interactive player and most likely also only in single player games.
600
601- :arg name: name of the scenario to reveal
602+ :arg name: name of the scenario to be marked as solved
603 :type name: :class:`string`
604 */
605 // UNTESTED
606-int LuaPlayer::reveal_scenario(lua_State* L) {
607+int LuaPlayer::mark_scenario_as_solved(lua_State* L) {
608 if (get_game(L).get_ipl()->player_number() != player_number())
609 report_error(L, "Can only be called for interactive player!");
610
611- CampaignVisibilitySave cvs;
612- cvs.set_map_visibility(luaL_checkstring(L, 2), true);
613-
614- return 0;
615-}
616-
617-/* RST
618- .. method:: reveal_campaign(name)
619-
620- This reveals a campaign. This only works for the
621- interactive player and most likely also only in single player games.
622-
623- :arg name: name of the campaign to reveal
624- :type name: :class:`string`
625-*/
626-// UNTESTED
627-int LuaPlayer::reveal_campaign(lua_State* L) {
628- if (get_game(L).get_ipl()->player_number() != player_number()) {
629- report_error(L, "Can only be called for interactive player!");
630- }
631-
632- CampaignVisibilitySave cvs;
633- cvs.set_campaign_visibility(luaL_checkstring(L, 2), true);
634+ Profile campvis(kCampVisFile.c_str());
635+ campvis.pull_section("scenarios").set_bool(luaL_checkstring(L, 2), true);
636+ campvis.write(kCampVisFile.c_str(), false);
637
638 return 0;
639 }
640
641=== modified file 'src/scripting/lua_game.h'
642--- src/scripting/lua_game.h 2019-02-23 11:00:49 +0000
643+++ src/scripting/lua_game.h 2019-04-23 16:17:37 +0000
644@@ -89,8 +89,7 @@
645 int add_objective(lua_State* L);
646 int reveal_fields(lua_State* L);
647 int hide_fields(lua_State* L);
648- int reveal_scenario(lua_State* L);
649- int reveal_campaign(lua_State* L);
650+ int mark_scenario_as_solved(lua_State* L);
651 int get_ships(lua_State* L);
652 int get_buildings(lua_State* L);
653 int get_suitability(lua_State* L);
654
655=== modified file 'src/ui_basic/table.cc'
656--- src/ui_basic/table.cc 2019-02-23 11:00:49 +0000
657+++ src/ui_basic/table.cc 2019-04-23 16:17:37 +0000
658@@ -300,8 +300,8 @@
659 curx += curw;
660 continue;
661 }
662- std::shared_ptr<const UI::RenderedText> rendered_text =
663- UI::g_fh->render(as_uifont(richtext_escape(entry_string)));
664+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh->render(
665+ as_uifont(richtext_escape(entry_string), UI_FONT_SIZE_SMALL, er.get_color()));
666
667 // Fix text alignment for BiDi languages if the entry contains an RTL character. We want
668 // this always on, e.g. for mixed language savegame filenames.
669@@ -718,7 +718,7 @@
670 return ea.get_string(column) < eb.get_string(column);
671 }
672
673-Table<void*>::EntryRecord::EntryRecord(void* const e) : entry_(e) {
674+Table<void*>::EntryRecord::EntryRecord(void* const e) : entry_(e), clr(UI_FONT_CLR_FG) {
675 }
676
677 void Table<void*>::EntryRecord::set_picture(uint8_t const col,
678
679=== modified file 'src/ui_basic/table.h'
680--- src/ui_basic/table.h 2019-02-23 11:00:49 +0000
681+++ src/ui_basic/table.h 2019-04-23 16:17:37 +0000
682@@ -88,7 +88,7 @@
683 void remove(uint32_t);
684 void remove_entry(Entry);
685
686- EntryRecord& add(void* const entry, const bool select_this = false);
687+ EntryRecord& add(void* const entry, bool const select_this = false);
688
689 uint32_t size() const;
690 bool empty() const;
691@@ -142,13 +142,8 @@
692 return entry_;
693 }
694 void set_color(const RGBColor& c) {
695- use_clr = true;
696 clr = c;
697 }
698-
699- bool use_color() const {
700- return use_clr;
701- }
702 RGBColor get_color() const {
703 return clr;
704 }
705@@ -156,7 +151,6 @@
706 private:
707 friend class Table<void*>;
708 void* entry_;
709- bool use_clr;
710 RGBColor clr;
711 struct Data {
712 const Image* d_picture;
713@@ -214,7 +208,7 @@
714 void remove(uint32_t);
715 void remove_entry(const void* const entry);
716
717- EntryRecord& add(void* entry = nullptr, bool select = false);
718+ EntryRecord& add(void* entry = nullptr, bool const select_this = false);
719
720 uint32_t size() const {
721 return entry_records_.size();
722
723=== modified file 'src/ui_fsmenu/CMakeLists.txt'
724--- src/ui_fsmenu/CMakeLists.txt 2018-02-13 16:52:12 +0000
725+++ src/ui_fsmenu/CMakeLists.txt 2019-04-23 16:17:37 +0000
726@@ -126,10 +126,18 @@
727
728 wl_library(ui_fsmenu_maploading
729 SRCS
730+ campaigndetails.cc
731+ campaigndetails.h
732+ campaigns.cc
733+ campaigns.h
734 campaign_select.cc
735 campaign_select.h
736 mapselect.cc
737 mapselect.h
738+ scenariodetails.cc
739+ scenariodetails.h
740+ scenario_select.cc
741+ scenario_select.h
742 DEPENDS
743 base_exceptions
744 base_i18n
745@@ -137,13 +145,16 @@
746 graphic
747 graphic_fonthandler
748 graphic_text_constants
749+ graphic_surface
750 io_filesystem
751- logic_campaign_visibility
752 logic_filesystem_constants
753 logic_game_controller
754 logic_game_settings
755+ logic_tribe_basic_info
756 map_io_map_loader
757 profile
758+ scripting_lua_interface
759+ scripting_lua_table
760 ui_basic
761 ui_fsmenu_base
762 ui_fsmenu_loading_common
763
764=== modified file 'src/ui_fsmenu/campaign_select.cc'
765--- src/ui_fsmenu/campaign_select.cc 2019-02-23 11:00:49 +0000
766+++ src/ui_fsmenu/campaign_select.cc 2019-04-23 16:17:37 +0000
767@@ -27,67 +27,28 @@
768 #include "base/wexception.h"
769 #include "graphic/graphic.h"
770 #include "graphic/text_constants.h"
771-#include "logic/campaign_visibility.h"
772-#include "map_io/widelands_map_loader.h"
773+#include "logic/filesystem_constants.h"
774 #include "profile/profile.h"
775-
776-/*
777- * UI 1 - Selection of Campaign
778- *
779- */
780+#include "scripting/lua_interface.h"
781+#include "scripting/lua_table.h"
782
783 /**
784 * CampaignSelect UI
785 * Loads a list of all visible campaigns
786 */
787-FullscreenMenuCampaignSelect::FullscreenMenuCampaignSelect()
788+FullscreenMenuCampaignSelect::FullscreenMenuCampaignSelect(Campaigns* campvis)
789 : FullscreenMenuLoadMapOrGame(),
790- table_(this, tablex_, tabley_, tablew_, tableh_, UI::PanelStyle::kFsMenu),
791+ table_(this, 0, 0, 0, 0, UI::PanelStyle::kFsMenu),
792
793 // Main Title
794- title_(this, get_w() / 2, tabley_ / 3, _("Choose a campaign"), UI::Align::kCenter),
795+ title_(this, 0, 0, _("Choose a campaign"), UI::Align::kCenter),
796
797 // Campaign description
798- label_campname_(this, right_column_x_, tabley_),
799- ta_campname_(this,
800- right_column_x_ + indent_,
801- get_y_from_preceding(label_campname_) + padding_,
802- get_right_column_w(right_column_x_) - indent_,
803- label_height_,
804- UI::PanelStyle::kFsMenu),
805-
806- label_tribename_(this, right_column_x_, get_y_from_preceding(ta_campname_) + 2 * padding_),
807- ta_tribename_(this,
808- right_column_x_ + indent_,
809- get_y_from_preceding(label_tribename_) + padding_,
810- get_right_column_w(right_column_x_ + indent_),
811- label_height_,
812- UI::PanelStyle::kFsMenu),
813-
814- label_difficulty_(this, right_column_x_, get_y_from_preceding(ta_tribename_) + 2 * padding_),
815- ta_difficulty_(this,
816- right_column_x_ + indent_,
817- get_y_from_preceding(label_difficulty_) + padding_,
818- get_right_column_w(right_column_x_ + indent_),
819- 2 * label_height_ - padding_,
820- UI::PanelStyle::kFsMenu),
821-
822- label_description_(this,
823- right_column_x_,
824- get_y_from_preceding(ta_difficulty_) + 2 * padding_,
825- _("Description:")),
826- ta_description_(this,
827- right_column_x_ + indent_,
828- get_y_from_preceding(label_description_) + padding_,
829- get_right_column_w(right_column_x_ + indent_),
830- buty_ - get_y_from_preceding(label_description_) - 4 * padding_,
831- UI::PanelStyle::kFsMenu) {
832+ campaign_details_(this),
833+ campaigns_(campvis) {
834 title_.set_fontsize(UI_FONT_SIZE_BIG);
835 back_.set_tooltip(_("Return to the main menu"));
836 ok_.set_tooltip(_("Play this campaign"));
837- ta_campname_.set_tooltip(_("The name of this campaign"));
838- ta_tribename_.set_tooltip(_("The tribe you will be playing"));
839- ta_difficulty_.set_tooltip(_("The difficulty of this campaign"));
840
841 ok_.sigclicked.connect(
842 boost::bind(&FullscreenMenuCampaignSelect::clicked_ok, boost::ref(*this)));
843@@ -99,7 +60,7 @@
844
845 /** TRANSLATORS: Campaign difficulty table header */
846 table_.add_column(45, _("Diff."), _("Difficulty"));
847- table_.add_column(100, _("Tribe"), _("Tribe Name"));
848+ table_.add_column(130, _("Tribe"), _("Tribe Name"));
849 table_.add_column(
850 0, _("Campaign Name"), _("Campaign Name"), UI::Align::kLeft, UI::TableColumnType::kFlexible);
851 table_.set_column_compare(
852@@ -107,10 +68,19 @@
853 table_.set_sort_column(0);
854 table_.focus();
855 fill_table();
856+ layout();
857 }
858
859 void FullscreenMenuCampaignSelect::layout() {
860- // TODO(GunChleoc): Implement when we have box layout for the details.
861+ FullscreenMenuLoadMapOrGame::layout();
862+ title_.set_pos(Vector2i(0, tabley_ / 3));
863+ title_.set_size(get_w(), title_.get_h());
864+ table_.set_size(tablew_, tableh_);
865+ table_.set_pos(Vector2i(tablex_, tabley_));
866+ campaign_details_.set_size(get_right_column_w(right_column_x_), tableh_ - buth_ - 4 * padding_);
867+ campaign_details_.set_desired_size(
868+ get_right_column_w(right_column_x_), tableh_ - buth_ - 4 * padding_);
869+ campaign_details_.set_pos(Vector2i(right_column_x_, tabley_));
870 }
871
872 /**
873@@ -120,385 +90,62 @@
874 if (!table_.has_selection()) {
875 return;
876 }
877- get_campaign();
878+ const CampaignData& campaign_data = *campaigns_->get_campaign(table_.get_selected());
879+ if (!campaign_data.visible) {
880+ return;
881+ }
882 end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kOk);
883 }
884
885-int32_t FullscreenMenuCampaignSelect::get_campaign() {
886- return campaign;
887+size_t FullscreenMenuCampaignSelect::get_campaign_index() const {
888+ return table_.get_selected();
889 }
890
891-/// Pictorial descriptions of difficulty levels.
892-static char const* const difficulty_picture_filenames[] = {
893- "images/novalue.png", "images/ui_fsmenu/easy.png", "images/ui_fsmenu/challenging.png",
894- "images/ui_fsmenu/hard.png"};
895-
896 bool FullscreenMenuCampaignSelect::set_has_selection() {
897- bool has_selection = table_.has_selection();
898+ const bool has_selection = table_.has_selection();
899 ok_.set_enabled(has_selection);
900-
901- if (!has_selection) {
902- label_campname_.set_text(std::string());
903- label_tribename_.set_text(std::string());
904- label_difficulty_.set_text(std::string());
905- label_description_.set_text(std::string());
906-
907- ta_campname_.set_text(std::string());
908- ta_tribename_.set_text(std::string());
909- ta_difficulty_.set_text(std::string());
910- ta_description_.set_text(std::string());
911-
912- } else {
913- label_campname_.set_text(_("Campaign Name:"));
914- label_tribename_.set_text(_("Tribe:"));
915- label_difficulty_.set_text(_("Difficulty:"));
916- label_description_.set_text(_("Description:"));
917- }
918 return has_selection;
919 }
920
921 void FullscreenMenuCampaignSelect::entry_selected() {
922 if (set_has_selection()) {
923- const CampaignListData& campaign_data = campaigns_data_[table_.get_selected()];
924- campaign = campaign_data.index;
925-
926- ta_campname_.set_text(campaign_data.name);
927- ta_tribename_.set_text(campaign_data.tribename);
928- ta_difficulty_.set_text(campaign_data.difficulty_description);
929- ta_description_.set_text(campaign_data.description);
930+ const CampaignData& campaign_data = *campaigns_->get_campaign(table_.get_selected());
931+ ok_.set_enabled(campaign_data.visible);
932+ campaign_details_.update(campaign_data);
933 }
934- ta_description_.scroll_to_top();
935 }
936
937 /**
938 * fill the campaign list
939 */
940 void FullscreenMenuCampaignSelect::fill_table() {
941- campaigns_data_.clear();
942 table_.clear();
943
944- // Read in the campaign config
945- Profile prof("campaigns/campaigns.conf", nullptr, "maps");
946- Section& s = prof.get_safe_section("global");
947-
948- // Read in campvis-file
949- CampaignVisibilitySave cvs;
950- Profile campvis(cvs.get_path().c_str());
951- Section& c = campvis.get_safe_section("campaigns");
952-
953- // Predefine variables, used in while-loop
954- uint32_t i = 0;
955- std::string csection = (boost::format("campsect%u") % i).str();
956- std::string cname;
957- std::string ctribename;
958- std::string cdifficulty;
959- std::string cdiff_descr;
960- std::string cdescription;
961-
962- while (s.get_string(csection.c_str())) {
963-
964- cname = (boost::format("campname%u") % i).str();
965- ctribename = (boost::format("camptribe%u") % i).str();
966- cdifficulty = (boost::format("campdiff%u") % i).str();
967- cdiff_descr = (boost::format("campdiffdescr%u") % i).str();
968- cdescription = (boost::format("campdesc%u") % i).str();
969-
970- // Only list visible campaigns
971- if (c.get_bool(csection.c_str())) {
972-
973- uint32_t difficulty = s.get_int(cdifficulty.c_str());
974- if (sizeof(difficulty_picture_filenames) / sizeof(*difficulty_picture_filenames) <=
975- difficulty) {
976- difficulty = 0;
977- }
978-
979- CampaignListData campaign_data;
980-
981- campaign_data.index = i;
982-
983- {
984- i18n::Textdomain td("maps");
985- campaign_data.name = _(s.get_string(cname.c_str(), ""));
986- campaign_data.tribename = _(s.get_string(ctribename.c_str(), ""));
987- campaign_data.difficulty = difficulty;
988- campaign_data.difficulty_description = _(s.get_string(cdiff_descr.c_str(), ""));
989- campaign_data.description = _(s.get_string(cdescription.c_str(), ""));
990- }
991-
992- campaigns_data_.push_back(campaign_data);
993-
994- UI::Table<uintptr_t>::EntryRecord& tableEntry = table_.add(i);
995- tableEntry.set_picture(0, g_gr->images().get(difficulty_picture_filenames[difficulty]));
996- tableEntry.set_string(1, campaign_data.tribename);
997- tableEntry.set_string(2, campaign_data.name);
998+ for (size_t i = 0; i < campaigns_->no_of_campaigns(); ++i) {
999+ const CampaignData& campaign_data = *campaigns_->get_campaign(i);
1000+
1001+ UI::Table<uintptr_t const>::EntryRecord& tableEntry = table_.add(i);
1002+ tableEntry.set_picture(0, campaign_data.difficulty_image);
1003+ tableEntry.set_string(1, campaign_data.tribename);
1004+ tableEntry.set_string(2, campaign_data.descname);
1005+ if (!campaign_data.visible) {
1006+ tableEntry.set_color(UI_FONT_CLR_DISABLED);
1007 }
1008-
1009- // Increase counter & csection
1010- ++i;
1011- csection = (boost::format("campsect%u") % i).str();
1012-
1013- } // while (s.get_string(csection.c_str()))
1014- table_.sort();
1015+ }
1016
1017 if (table_.size()) {
1018+ table_.sort();
1019 table_.select(0);
1020 }
1021 set_has_selection();
1022 }
1023
1024 bool FullscreenMenuCampaignSelect::compare_difficulty(uint32_t rowa, uint32_t rowb) {
1025- const CampaignListData& r1 = campaigns_data_[table_[rowa]];
1026- const CampaignListData& r2 = campaigns_data_[table_[rowb]];
1027+ const CampaignData& r1 = *campaigns_->get_campaign(table_[rowa]);
1028+ const CampaignData& r2 = *campaigns_->get_campaign(table_[rowb]);
1029
1030- if (r1.difficulty < r2.difficulty) {
1031+ if (r1.difficulty_level < r2.difficulty_level) {
1032 return true;
1033 }
1034- return r1.index < r2.index;
1035-}
1036-
1037-/*
1038- * UI 2 - Selection of a map
1039- *
1040- */
1041-
1042-/**
1043- * CampaignMapSelect UI.
1044- *
1045- * Loads a list of all visible maps of selected campaign and let's the user
1046- * choose one.
1047- */
1048-FullscreenMenuCampaignMapSelect::FullscreenMenuCampaignMapSelect(bool is_tutorial)
1049- : FullscreenMenuLoadMapOrGame(),
1050- table_(this, tablex_, tabley_, tablew_, tableh_, UI::PanelStyle::kFsMenu),
1051-
1052- // Main title
1053- title_(this,
1054- get_w() / 2,
1055- tabley_ / 3,
1056- is_tutorial ? _("Choose a tutorial") : _("Choose a scenario"),
1057- UI::Align::kCenter),
1058- subtitle_(this,
1059- get_w() / 6,
1060- get_y_from_preceding(title_) + 6 * padding_,
1061- get_w() * 2 / 3,
1062- 4 * label_height_,
1063- UI::PanelStyle::kFsMenu,
1064- "",
1065- UI::Align::kCenter),
1066-
1067- // Map description
1068- label_mapname_(this, right_column_x_, tabley_),
1069- ta_mapname_(this,
1070- right_column_x_ + indent_,
1071- get_y_from_preceding(label_mapname_) + padding_,
1072- get_right_column_w(right_column_x_ + indent_),
1073- label_height_,
1074- UI::PanelStyle::kFsMenu),
1075-
1076- label_author_(this, right_column_x_, get_y_from_preceding(ta_mapname_) + 2 * padding_),
1077- ta_author_(this,
1078- right_column_x_ + indent_,
1079- get_y_from_preceding(label_author_) + padding_,
1080- get_right_column_w(right_column_x_ + indent_),
1081- 2 * label_height_,
1082- UI::PanelStyle::kFsMenu),
1083-
1084- label_description_(this, right_column_x_, get_y_from_preceding(ta_author_) + padding_),
1085- ta_description_(this,
1086- right_column_x_ + indent_,
1087- get_y_from_preceding(label_description_) + padding_,
1088- get_right_column_w(right_column_x_ + indent_),
1089- buty_ - get_y_from_preceding(label_description_) - 4 * padding_,
1090- UI::PanelStyle::kFsMenu),
1091-
1092- is_tutorial_(is_tutorial) {
1093- title_.set_fontsize(UI_FONT_SIZE_BIG);
1094- back_.set_tooltip(_("Return to the main menu"));
1095- if (is_tutorial_) {
1096- ok_.set_tooltip(_("Play this tutorial"));
1097- ta_mapname_.set_tooltip(_("The name of this tutorial"));
1098- ta_description_.set_tooltip(_("What you will learn in this tutorial"));
1099- } else {
1100- ok_.set_tooltip(_("Play this scenario"));
1101- ta_mapname_.set_tooltip(_("The name of this scenario"));
1102- }
1103-
1104- ok_.sigclicked.connect(
1105- boost::bind(&FullscreenMenuCampaignMapSelect::clicked_ok, boost::ref(*this)));
1106- back_.sigclicked.connect(
1107- boost::bind(&FullscreenMenuCampaignMapSelect::clicked_back, boost::ref(*this)));
1108- table_.selected.connect(boost::bind(&FullscreenMenuCampaignMapSelect::entry_selected, this));
1109- table_.double_clicked.connect(
1110- boost::bind(&FullscreenMenuCampaignMapSelect::clicked_ok, boost::ref(*this)));
1111-
1112- std::string number_tooltip;
1113- std::string name_tooltip;
1114- if (is_tutorial_) {
1115- number_tooltip = _("The order in which the tutorials should be played");
1116- name_tooltip = _("Tutorial Name");
1117- } else {
1118- number_tooltip = _("The number of this scenario in the campaign");
1119- name_tooltip = _("Scenario Name");
1120- }
1121-
1122- /** TRANSLATORS: Campaign scenario number table header */
1123- table_.add_column(35, _("#"), number_tooltip);
1124- table_.add_column(
1125- 0, name_tooltip, name_tooltip, UI::Align::kLeft, UI::TableColumnType::kFlexible);
1126- table_.set_sort_column(0);
1127-
1128- table_.focus();
1129-}
1130-
1131-void FullscreenMenuCampaignMapSelect::layout() {
1132- // TODO(GunChleoc): Implement when we have box layout for the details.
1133- table_.layout();
1134-}
1135-
1136-std::string FullscreenMenuCampaignMapSelect::get_map() {
1137- return campmapfile;
1138-}
1139-
1140-// Telling this class what campaign we have and since we know what campaign we have, fill it.
1141-void FullscreenMenuCampaignMapSelect::set_campaign(uint32_t const i) {
1142- campaign = i;
1143- fill_table();
1144-}
1145-
1146-bool FullscreenMenuCampaignMapSelect::set_has_selection() {
1147- bool has_selection = table_.has_selection();
1148- ok_.set_enabled(has_selection);
1149-
1150- if (!has_selection) {
1151- label_mapname_.set_text(std::string());
1152- label_author_.set_text(std::string());
1153- label_description_.set_text(std::string());
1154-
1155- ta_mapname_.set_text(std::string());
1156- ta_author_.set_text(std::string());
1157- ta_description_.set_text(std::string());
1158-
1159- } else {
1160- is_tutorial_ ? label_mapname_.set_text(_("Tutorial:")) :
1161- label_mapname_.set_text(_("Scenario:"));
1162- label_description_.set_text(_("Description:"));
1163- }
1164- return has_selection;
1165-}
1166-
1167-void FullscreenMenuCampaignMapSelect::entry_selected() {
1168- if (set_has_selection()) {
1169- const CampaignScenarioData& scenario_data = scenarios_data_[table_.get_selected()];
1170- campmapfile = scenario_data.path;
1171- Widelands::Map map;
1172-
1173- std::unique_ptr<Widelands::MapLoader> ml(map.get_correct_loader(campmapfile));
1174- if (!ml) {
1175- throw wexception(_("Invalid path to file in campaigns.conf: %s"), campmapfile.c_str());
1176- }
1177-
1178- map.set_filename(campmapfile);
1179- ml->preload_map(true);
1180-
1181- // Localizing this, because some author fields now have "edited by" text.
1182- MapAuthorData authors(_(map.get_author()));
1183-
1184- ta_author_.set_text(authors.get_names());
1185- if (is_tutorial_) {
1186- ta_author_.set_tooltip(ngettext("The designer of this tutorial",
1187- "The designers of this tutorial", authors.get_number()));
1188- } else {
1189- ta_author_.set_tooltip(ngettext("The designer of this scenario",
1190- "The designers of this scenario", authors.get_number()));
1191- }
1192- label_author_.set_text(ngettext("Author:", "Authors:", authors.get_number()));
1193-
1194- {
1195- i18n::Textdomain td("maps");
1196- ta_mapname_.set_text(_(map.get_name()));
1197- ta_description_.set_text(_(map.get_description()));
1198- }
1199- ta_description_.scroll_to_top();
1200-
1201- // The dummy scenario can't be played, so we disable the OK button.
1202- if (campmapfile == "campaigns/dummy.wmf") {
1203- ok_.set_enabled(false);
1204- }
1205- }
1206-}
1207-
1208-/**
1209- * fill the campaign-map list
1210- */
1211-void FullscreenMenuCampaignMapSelect::fill_table() {
1212- // read in the campaign config
1213- std::unique_ptr<Profile> prof;
1214- std::string campsection;
1215- if (is_tutorial_) {
1216- prof.reset(new Profile("campaigns/tutorials.conf", nullptr, "maps"));
1217-
1218- // Set subtitle of the page
1219- const std::string subtitle1 = _("Pick a tutorial from the list, then hit \"OK\".");
1220- const std::string subtitle2 =
1221- _("You can see a description of the currently selected tutorial on the right.");
1222- subtitle_.set_text((boost::format("%s\n%s") % subtitle1 % subtitle2).str());
1223-
1224- // Get section of campaign-maps
1225- campsection = "tutorials";
1226-
1227- } else {
1228- prof.reset(new Profile("campaigns/campaigns.conf", nullptr, "maps"));
1229-
1230- Section& global_s = prof->get_safe_section("global");
1231-
1232- // Set subtitle of the page
1233- const char* campaign_tribe =
1234- _(global_s.get_string((boost::format("camptribe%u") % campaign).str().c_str()));
1235- const char* campaign_name;
1236- {
1237- i18n::Textdomain td("maps");
1238- campaign_name =
1239- _(global_s.get_string((boost::format("campname%u") % campaign).str().c_str()));
1240- }
1241- subtitle_.set_text((boost::format("%s — %s") % campaign_tribe % campaign_name).str());
1242-
1243- // Get section of campaign-maps
1244- campsection = global_s.get_string((boost::format("campsect%u") % campaign).str().c_str());
1245- }
1246-
1247- // Create the entry we use to load the section of the map
1248- uint32_t i = 0;
1249- std::string mapsection = campsection + (boost::format("%02i") % i).str();
1250-
1251- // Read in campvis-file
1252- CampaignVisibilitySave cvs;
1253- Profile campvis(cvs.get_path().c_str());
1254- Section& c = campvis.get_safe_section("campmaps");
1255-
1256- // Add all visible entries to the list.
1257- while (Section* const s = prof->get_section(mapsection)) {
1258- if (is_tutorial_ || c.get_bool(mapsection.c_str())) {
1259-
1260- CampaignScenarioData scenario_data;
1261- scenario_data.index = i + 1;
1262- scenario_data.name = s->get_string("name", "");
1263- scenario_data.path = s->get_string("path");
1264- scenarios_data_.push_back(scenario_data);
1265-
1266- UI::Table<uintptr_t>::EntryRecord& tableEntry = table_.add(i);
1267- tableEntry.set_string(0, (boost::format("%u") % scenario_data.index).str());
1268- tableEntry.set_picture(
1269- 1, g_gr->images().get("images/ui_basic/ls_wlmap.png"), scenario_data.name);
1270- }
1271-
1272- // Increase counter & mapsection
1273- ++i;
1274- mapsection = campsection + (boost::format("%02i") % i).str();
1275- }
1276- table_.sort();
1277-
1278- if (table_.size()) {
1279- table_.select(0);
1280- }
1281- set_has_selection();
1282+ return table_[rowa] < table_[rowb];
1283 }
1284
1285=== modified file 'src/ui_fsmenu/campaign_select.h'
1286--- src/ui_fsmenu/campaign_select.h 2019-02-23 11:00:49 +0000
1287+++ src/ui_fsmenu/campaign_select.h 2019-04-23 16:17:37 +0000
1288@@ -20,26 +20,22 @@
1289 #ifndef WL_UI_FSMENU_CAMPAIGN_SELECT_H
1290 #define WL_UI_FSMENU_CAMPAIGN_SELECT_H
1291
1292-#include "ui_basic/button.h"
1293-#include "ui_basic/multilinetextarea.h"
1294+#include <vector>
1295+
1296 #include "ui_basic/table.h"
1297 #include "ui_basic/textarea.h"
1298-#include "ui_fsmenu/base.h"
1299+#include "ui_fsmenu/campaigndetails.h"
1300+#include "ui_fsmenu/campaigns.h"
1301 #include "ui_fsmenu/load_map_or_game.h"
1302
1303 /*
1304- * Fullscreen Menu for all Campaigns
1305- */
1306-
1307-/*
1308- * UI 1 - Selection of Campaign
1309- *
1310+ * Fullscreen Menu for selecting a campaign
1311 */
1312 class FullscreenMenuCampaignSelect : public FullscreenMenuLoadMapOrGame {
1313 public:
1314- FullscreenMenuCampaignSelect();
1315+ FullscreenMenuCampaignSelect(Campaigns* campvis);
1316
1317- int32_t get_campaign();
1318+ size_t get_campaign_index() const;
1319
1320 protected:
1321 void clicked_ok() override;
1322@@ -52,90 +48,14 @@
1323 /// Updates buttons and text labels and returns whether a table entry is selected.
1324 bool set_has_selection();
1325
1326- /**
1327- * Data about a campaign that we're interested in.
1328- */
1329- struct CampaignListData {
1330- uint32_t index;
1331- std::string name;
1332- std::string tribename;
1333- uint32_t difficulty;
1334- std::string difficulty_description;
1335- std::string description;
1336-
1337- CampaignListData() : index(0), difficulty(0) {
1338- }
1339- };
1340-
1341 bool compare_difficulty(uint32_t, uint32_t);
1342
1343 UI::Table<uintptr_t const> table_;
1344
1345 UI::Textarea title_;
1346- UI::Textarea label_campname_;
1347- UI::MultilineTextarea ta_campname_;
1348- UI::Textarea label_tribename_;
1349- UI::MultilineTextarea ta_tribename_;
1350- UI::Textarea label_difficulty_;
1351- UI::MultilineTextarea ta_difficulty_;
1352- UI::Textarea label_description_;
1353- UI::MultilineTextarea ta_description_;
1354-
1355- std::vector<CampaignListData> campaigns_data_;
1356-
1357- /// Variables used for exchange between the two Campaign UIs and
1358- /// Game::run_campaign
1359- int32_t campaign;
1360-};
1361-/*
1362- * UI 2 - Selection of a map
1363- *
1364- */
1365-class FullscreenMenuCampaignMapSelect : public FullscreenMenuLoadMapOrGame {
1366-public:
1367- explicit FullscreenMenuCampaignMapSelect(bool is_tutorial = false);
1368-
1369- std::string get_map();
1370- void set_campaign(uint32_t);
1371-
1372-protected:
1373- void entry_selected() override;
1374- void fill_table() override;
1375-
1376-private:
1377- void layout() override;
1378-
1379- /// Updates buttons and text labels and returns whether a table entry is selected.
1380- bool set_has_selection();
1381- /**
1382- * Data about a campaign scenario that we're interested in.
1383- */
1384- struct CampaignScenarioData {
1385- uint32_t index;
1386- std::string name;
1387- std::string path;
1388-
1389- CampaignScenarioData() : index(0) {
1390- }
1391- };
1392-
1393- UI::Table<uintptr_t const> table_;
1394-
1395- UI::Textarea title_;
1396- UI::MultilineTextarea subtitle_;
1397- UI::Textarea label_mapname_;
1398- UI::MultilineTextarea ta_mapname_;
1399- UI::Textarea label_author_;
1400- UI::MultilineTextarea ta_author_;
1401- UI::Textarea label_description_;
1402- UI::MultilineTextarea ta_description_;
1403-
1404- uint32_t campaign;
1405- std::string campmapfile;
1406-
1407- std::vector<CampaignScenarioData> scenarios_data_;
1408-
1409- bool is_tutorial_;
1410+ CampaignDetails campaign_details_;
1411+
1412+ Campaigns* campaigns_;
1413 };
1414
1415 #endif // end of include guard: WL_UI_FSMENU_CAMPAIGN_SELECT_H
1416
1417=== added file 'src/ui_fsmenu/campaigndetails.cc'
1418--- src/ui_fsmenu/campaigndetails.cc 1970-01-01 00:00:00 +0000
1419+++ src/ui_fsmenu/campaigndetails.cc 2019-04-23 16:17:37 +0000
1420@@ -0,0 +1,82 @@
1421+/*
1422+ * Copyright (C) 2017 by the Widelands Development Team
1423+ *
1424+ * This program is free software; you can redistribute it and/or
1425+ * modify it under the terms of the GNU General Public License
1426+ * as published by the Free Software Foundation; either version 2
1427+ * of the License, or (at your option) any later version.
1428+ *
1429+ * This program is distributed in the hope that it will be useful,
1430+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1431+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1432+ * GNU General Public License for more details.
1433+ *
1434+ * You should have received a copy of the GNU General Public License
1435+ * along with this program; if not, write to the Free Software
1436+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1437+ */
1438+
1439+#include "ui_fsmenu/campaigndetails.h"
1440+
1441+#include <boost/format.hpp>
1442+
1443+#include "base/i18n.h"
1444+#include "graphic/text_constants.h"
1445+#include "ui_basic/scrollbar.h"
1446+
1447+CampaignDetails::CampaignDetails(Panel* parent)
1448+ : UI::Box(parent, 0, 0, UI::Box::Vertical),
1449+ name_label_(this,
1450+ 0,
1451+ 0,
1452+ UI::Scrollbar::kSize,
1453+ 0,
1454+ UI::PanelStyle::kFsMenu,
1455+ "",
1456+ UI::Align::kLeft,
1457+ UI::MultilineTextarea::ScrollMode::kNoScrolling),
1458+ descr_(this, 0, 0, UI::Scrollbar::kSize, 0, UI::PanelStyle::kFsMenu) {
1459+
1460+ constexpr int kPadding = 4;
1461+ add(&name_label_, UI::Box::Resizing::kFullSize);
1462+ add_space(kPadding);
1463+ add(&descr_, UI::Box::Resizing::kExpandBoth);
1464+}
1465+
1466+void CampaignDetails::update(const CampaignData& campaigndata) {
1467+ name_label_.set_text((boost::format("<rt>%s%s</rt>") %
1468+ /** TRANSLATORS: Header for campaign name */
1469+ as_heading(_("Campaign"), UI::PanelStyle::kFsMenu, true) %
1470+ as_content(campaigndata.descname, UI::PanelStyle::kFsMenu))
1471+ .str());
1472+
1473+ std::string description = "";
1474+
1475+ if (campaigndata.visible) {
1476+ /** TRANSLATORS: Header for campaign tribe */
1477+ description = (boost::format("%s%s") % as_heading(_("Tribe"), UI::PanelStyle::kFsMenu) %
1478+ as_content(campaigndata.tribename, UI::PanelStyle::kFsMenu))
1479+ .str();
1480+ description =
1481+ /** TRANSLATORS: Header for campaign difficulty */
1482+ (boost::format("%s%s") % description % as_heading(_("Difficulty"), UI::PanelStyle::kFsMenu)).str();
1483+ description = (boost::format("%s%s") % description %
1484+ as_content(campaigndata.difficulty_description, UI::PanelStyle::kFsMenu))
1485+ .str();
1486+
1487+ description =
1488+ /** TRANSLATORS: Header for campaign description */
1489+ (boost::format("%s%s") % description % as_heading(_("Description"), UI::PanelStyle::kFsMenu))
1490+ .str();
1491+ description = (boost::format("%s%s") % description %
1492+ as_content(campaigndata.description, UI::PanelStyle::kFsMenu))
1493+ .str();
1494+ }
1495+ description =
1496+ (boost::format("%s%s") % description % as_content(campaigndata.description, UI::PanelStyle::kFsMenu))
1497+ .str();
1498+
1499+ description = (boost::format("<rt>%s</rt>") % description).str();
1500+ descr_.set_text(description);
1501+ descr_.scroll_to_top();
1502+}
1503
1504=== added file 'src/ui_fsmenu/campaigndetails.h'
1505--- src/ui_fsmenu/campaigndetails.h 1970-01-01 00:00:00 +0000
1506+++ src/ui_fsmenu/campaigndetails.h 2019-04-23 16:17:37 +0000
1507@@ -0,0 +1,41 @@
1508+/*
1509+ * Copyright (C) 2017 by the Widelands Development Team
1510+ *
1511+ * This program is free software; you can redistribute it and/or
1512+ * modify it under the terms of the GNU General Public License
1513+ * as published by the Free Software Foundation; either version 2
1514+ * of the License, or (at your option) any later version.
1515+ *
1516+ * This program is distributed in the hope that it will be useful,
1517+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1518+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1519+ * GNU General Public License for more details.
1520+ *
1521+ * You should have received a copy of the GNU General Public License
1522+ * along with this program; if not, write to the Free Software
1523+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1524+ *
1525+ */
1526+
1527+#ifndef WL_UI_FSMENU_CAMPAIGNDETAILS_H
1528+#define WL_UI_FSMENU_CAMPAIGNDETAILS_H
1529+
1530+#include "ui_basic/box.h"
1531+#include "ui_basic/multilinetextarea.h"
1532+#include "ui_fsmenu/campaigns.h"
1533+
1534+/**
1535+ * Show a Box with information about a campaign.
1536+ */
1537+class CampaignDetails : public UI::Box {
1538+public:
1539+ explicit CampaignDetails(Panel* parent);
1540+
1541+ void update(const CampaignData& campaigndata);
1542+
1543+private:
1544+ UI::MultilineTextarea name_label_;
1545+ UI::MultilineTextarea descr_;
1546+};
1547+
1548+#endif // end of include guard: WL_UI_FSMENU_CAMPAIGNDETAILS_H
1549
1550=== renamed file 'src/logic/campaign_visibility.cc' => 'src/ui_fsmenu/campaigns.cc'
1551--- src/logic/campaign_visibility.cc 2019-02-23 11:00:49 +0000
1552+++ src/ui_fsmenu/campaigns.cc 2019-04-23 16:17:37 +0000
1553@@ -17,166 +17,206 @@
1554 *
1555 */
1556
1557-#include "logic/campaign_visibility.h"
1558-
1559-#include <cstdio>
1560-#include <cstdlib>
1561-
1562-#include <sys/stat.h>
1563-
1564-#include "base/wexception.h"
1565+#include "ui_fsmenu/campaigns.h"
1566+
1567+#include <map>
1568+#include <memory>
1569+
1570+#include "base/log.h"
1571+#include "graphic/graphic.h"
1572 #include "io/filesystem/filesystem.h"
1573 #include "logic/filesystem_constants.h"
1574+#include "logic/map_objects/tribes/tribe_basic_info.h"
1575 #include "profile/profile.h"
1576-
1577-/**
1578- * Get the path of campaign visibility save-file
1579- */
1580-std::string CampaignVisibilitySave::get_path() {
1581- g_fs->ensure_directory_exists(kSaveDir); // Make sure save directory exists
1582-
1583- // check if campaigns visibility-save is available
1584+#include "scripting/lua_interface.h"
1585+
1586+namespace {
1587+const std::string kCampVisFileLegacy = "save/campvis";
1588+}
1589+
1590+Campaigns::Campaigns() {
1591+ // Load solved scenarios
1592+ std::unique_ptr<Profile> campvis;
1593 if (!(g_fs->file_exists(kCampVisFile))) {
1594- make_campvis(kCampVisFile);
1595- }
1596-
1597- // check if campaigns visibility-save is up to date
1598- Profile ca(kCampVisFile.c_str());
1599-
1600- // 1st version of campvis had no global section
1601- if (!ca.get_section("global"))
1602- update_campvis(kCampVisFile);
1603- else {
1604- Section& ca_s = ca.get_safe_section("global");
1605- Profile cc("campaigns/campaigns.conf");
1606- Section& cc_s = cc.get_safe_section("global");
1607- if (cc_s.get_int("version") > ca_s.get_int("version"))
1608- update_campvis(kCampVisFile);
1609- }
1610-
1611- return kCampVisFile;
1612-}
1613-
1614-/**
1615- * Create the campaign visibility save-file of the user
1616- */
1617-void CampaignVisibilitySave::make_campvis(const std::string& savepath) {
1618- // Only prepare campvis-file -> data will be written via update_campvis
1619- Profile campvis(savepath.c_str());
1620- campvis.pull_section("global");
1621- campvis.pull_section("campaigns");
1622- campvis.pull_section("campmaps");
1623- campvis.write(savepath.c_str(), true);
1624-
1625- update_campvis(savepath);
1626-}
1627-
1628-/**
1629- * Update the campaign visibility save-file of the user
1630- */
1631-void CampaignVisibilitySave::update_campvis(const std::string& savepath) {
1632- // Variable declaration
1633- int32_t i = 0;
1634- int32_t imap = 0;
1635- char csection[24];
1636- char number[12];
1637- std::string mapsection;
1638- std::string cms;
1639-
1640- // Prepare campaigns.conf and campvis
1641- Profile cconfig("campaigns/campaigns.conf");
1642- Section& cconf_s = cconfig.get_safe_section("global");
1643- Profile campvisr(savepath.c_str());
1644- Profile campvisw(savepath.c_str());
1645-
1646- // Write down global section
1647- campvisw.pull_section("global").set_int("version", cconf_s.get_int("version", 1));
1648-
1649- // Write down visibility of campaigns
1650- Section& campv_c = campvisr.get_safe_section("campaigns");
1651- Section& campv_m = campvisr.get_safe_section("campmaps");
1652- {
1653- Section& vis = campvisw.pull_section("campaigns");
1654- sprintf(csection, "campsect%i", i);
1655- char cvisible[24];
1656- char cnewvisi[24];
1657- while (cconf_s.get_string(csection)) {
1658- sprintf(cvisible, "campvisi%i", i);
1659- sprintf(cnewvisi, "cnewvisi%i", i);
1660- bool visible = cconf_s.get_bool(cvisible) || campv_c.get_bool(csection);
1661- if (!visible) {
1662- const char* newvisi = cconf_s.get_string(cnewvisi, "");
1663- if (sizeof(newvisi) > 1) {
1664- visible = campv_m.get_bool(newvisi, false) || campv_c.get_bool(newvisi, false);
1665- }
1666- }
1667- vis.set_bool(csection, visible);
1668- ++i;
1669- sprintf(csection, "campsect%i", i);
1670- }
1671- }
1672-
1673- // Write down visibility of campaign maps
1674- Section& vis = campvisw.pull_section("campmaps");
1675- i = 0;
1676-
1677- sprintf(csection, "campsect%i", i);
1678- while (cconf_s.get_string(csection)) {
1679- mapsection = cconf_s.get_string(csection);
1680-
1681- cms = mapsection;
1682- sprintf(number, "%02i", imap);
1683- cms += number;
1684-
1685- while (Section* const s = cconfig.get_section(cms.c_str())) {
1686- bool visible = s->get_bool("visible") || campv_m.get_bool(cms.c_str());
1687- if (!visible) {
1688- const char* newvisi = s->get_string("newvisi", "");
1689- if (sizeof(newvisi) > 1) {
1690- visible = campv_m.get_bool(newvisi, false) || campv_c.get_bool(newvisi, false);
1691- }
1692- }
1693- vis.set_bool(cms.c_str(), visible);
1694-
1695- ++imap;
1696- cms = mapsection;
1697- sprintf(number, "%02i", imap);
1698- cms += number;
1699- }
1700-
1701- ++i;
1702- sprintf(csection, "campsect%i", i);
1703- imap = 0;
1704- }
1705- campvisw.write(savepath.c_str(), true);
1706-}
1707-
1708-/**
1709- * Set an campaign entry in campvis visible or invisible.
1710- * If it doesn't exist, create it.
1711- * \param entry entry to be changed
1712- * \param visible should the map be visible?
1713- */
1714-void CampaignVisibilitySave::set_campaign_visibility(const std::string& entry, bool visible) {
1715- std::string savepath = get_path();
1716- Profile campvis(savepath.c_str());
1717-
1718- campvis.pull_section("campaigns").set_bool(entry.c_str(), visible);
1719-
1720- campvis.write(savepath.c_str(), false);
1721-}
1722-
1723-/**
1724- * Set an campaignmap entry in campvis visible or invisible.
1725- * If it doesn't exist, create it.
1726- * \param entry entry to be changed
1727- * \param visible should the map be visible?
1728- */
1729-void CampaignVisibilitySave::set_map_visibility(const std::string& entry, bool visible) {
1730- std::string savepath = get_path();
1731- Profile campvis(savepath.c_str());
1732-
1733- campvis.pull_section("campmaps").set_bool(entry.c_str(), visible);
1734-
1735- campvis.write(savepath.c_str(), false);
1736+ // There is no campaigns.conf file - create one.
1737+ campvis.reset(new Profile(kCampVisFile.c_str()));
1738+ campvis->pull_section("scenarios");
1739+ campvis->write(kCampVisFile.c_str(), true);
1740+ if (g_fs->file_exists(kCampVisFileLegacy)) {
1741+ update_legacy_campvis();
1742+ }
1743+ }
1744+ campvis.reset(new Profile(kCampVisFile.c_str()));
1745+ Section& campvis_scenarios = campvis->get_safe_section("scenarios");
1746+
1747+ // Now load the campaign info
1748+ LuaInterface lua;
1749+ std::unique_ptr<LuaTable> table(lua.run_script("campaigns/campaigns.lua"));
1750+
1751+ // Read difficulty images
1752+ std::unique_ptr<LuaTable> difficulties_table(table->get_table("difficulties"));
1753+ std::vector<std::pair<const std::string, const Image*>> difficulty_levels;
1754+ for (const auto& difficulty_level_table :
1755+ difficulties_table->array_entries<std::unique_ptr<LuaTable>>()) {
1756+ difficulty_levels.push_back(
1757+ std::make_pair(_(difficulty_level_table->get_string("descname")),
1758+ g_gr->images().get(difficulty_level_table->get_string("image"))));
1759+ }
1760+
1761+ // Read the campaigns themselves
1762+ std::unique_ptr<LuaTable> campaigns_table(table->get_table("campaigns"));
1763+ i18n::Textdomain td("maps");
1764+
1765+ for (const auto& campaign_table : campaigns_table->array_entries<std::unique_ptr<LuaTable>>()) {
1766+ CampaignData* campaign_data = new CampaignData();
1767+ campaign_data->descname = _(campaign_table->get_string("descname"));
1768+ campaign_data->tribename =
1769+ Widelands::get_tribeinfo(campaign_table->get_string("tribe")).descname;
1770+ campaign_data->description = _(campaign_table->get_string("description"));
1771+ if (campaign_table->has_key("prerequisites")) {
1772+ for (const std::string& prerequisite :
1773+ campaign_table->get_table("prerequisites")->array_entries<std::string>()) {
1774+ campaign_data->prerequisites.insert(prerequisite);
1775+ }
1776+ }
1777+
1778+ campaign_data->visible = false;
1779+
1780+ // Collect difficulty information
1781+ std::unique_ptr<LuaTable> difficulty_table(campaign_table->get_table("difficulty"));
1782+ campaign_data->difficulty_level = difficulty_table->get_int("level");
1783+ campaign_data->difficulty_image =
1784+ difficulty_levels.at(campaign_data->difficulty_level - 1).second;
1785+ campaign_data->difficulty_description =
1786+ difficulty_levels.at(campaign_data->difficulty_level - 1).first;
1787+ const std::string difficulty_description = _(difficulty_table->get_string("description"));
1788+ if (!difficulty_description.empty()) {
1789+ campaign_data->difficulty_description =
1790+ i18n::join_sentences(campaign_data->difficulty_description, difficulty_description);
1791+ }
1792+
1793+ // Scenarios
1794+ std::unique_ptr<LuaTable> scenarios_table(campaign_table->get_table("scenarios"));
1795+ for (const std::string& path : scenarios_table->array_entries<std::string>()) {
1796+ ScenarioData* scenario_data = new ScenarioData();
1797+ scenario_data->path = path;
1798+ if (campvis_scenarios.get_bool(scenario_data->path.c_str(), false)) {
1799+ solved_scenarios_.insert(scenario_data->path);
1800+ }
1801+
1802+ scenario_data->is_tutorial = false;
1803+ scenario_data->playable = scenario_data->path != "dummy.wmf";
1804+ scenario_data->visible = false;
1805+ campaign_data->scenarios.push_back(
1806+ std::unique_ptr<ScenarioData>(std::move(scenario_data)));
1807+ }
1808+
1809+ campaigns_.push_back(std::unique_ptr<CampaignData>(std::move(campaign_data)));
1810+ }
1811+
1812+ // Finally, calculate the visibility
1813+ update_visibility_info();
1814+}
1815+
1816+void Campaigns::update_visibility_info() {
1817+ for (auto& campaign : campaigns_) {
1818+ if (campaign->prerequisites.empty()) {
1819+ // A campaign is visible if it has no prerequisites
1820+ campaign->visible = true;
1821+ } else {
1822+ // A campaign is visible if one of its prerequisites has been fulfilled
1823+ for (const std::string prerequisite : campaign->prerequisites) {
1824+ if (solved_scenarios_.count(prerequisite) == 1) {
1825+ campaign->visible = true;
1826+ break;
1827+ }
1828+ }
1829+ }
1830+ if (!campaign->visible) {
1831+ // A campaign is also visible if one of its scenarios has been solved
1832+ for (size_t i = 0; i < campaign->scenarios.size(); ++i) {
1833+ auto& scenario = campaign->scenarios.at(i);
1834+ if (solved_scenarios_.count(scenario->path) == 1) {
1835+ campaign->visible = true;
1836+ break;
1837+ }
1838+ }
1839+ }
1840+ // Now set scenario visibility
1841+ if (campaign->visible) {
1842+ for (size_t i = 0; i < campaign->scenarios.size(); ++i) {
1843+ auto& scenario = campaign->scenarios.at(i);
1844+ if (i == 0) {
1845+ // The first scenario in a visible campaign is always visible
1846+ scenario->visible = true;
1847+ } else {
1848+ // A scenario is visible if its predecessor was solved
1849+ scenario->visible =
1850+ solved_scenarios_.count(campaign->scenarios.at(i - 1)->path) == 1;
1851+ }
1852+ if (!scenario->visible) {
1853+ // If a scenario is invisible, subsequent scenarios are also invisible
1854+ break;
1855+ }
1856+ }
1857+ }
1858+ }
1859+}
1860+
1861+/**
1862+ * Handle legacy campvis file
1863+ */
1864+// TODO(GunChleoc): Remove after Build 22
1865+void Campaigns::update_legacy_campvis() {
1866+ Profile legacy_campvis(kCampVisFileLegacy.c_str());
1867+ if (legacy_campvis.get_section("campmaps") == nullptr) {
1868+ return;
1869+ }
1870+
1871+ log("Converting legacy campvis\n");
1872+
1873+ using LegacyList = std::vector<std::pair<std::string, std::string>>;
1874+
1875+ std::vector<LegacyList> legacy_scenarios;
1876+
1877+ legacy_scenarios.push_back(
1878+ {{"fri02.wmf", "frisians01"}, {"fri01.wmf", "frisians00"}, {"atl01.wmf", "atlanteans00"}});
1879+
1880+ legacy_scenarios.push_back(
1881+ {{"fri02.wmf", "frisians01"}, {"fri01.wmf", "frisians00"}, {"emp04.wmf", "empiretut03"}});
1882+
1883+ legacy_scenarios.push_back({{"atl02.wmf", "atlanteans01"},
1884+ {"atl01.wmf", "atlanteans00"},
1885+ {"emp02.wmf", "empiretut01"},
1886+ {"emp01.wmf", "empiretut00"}});
1887+
1888+ legacy_scenarios.push_back({
1889+ {"emp04.wmf", "empiretut03"},
1890+ {"emp03.wmf", "empiretut02"},
1891+ {"emp02.wmf", "empiretut01"},
1892+ {"emp01.wmf", "empiretut00"},
1893+ {"bar02.wmf", "barbariantut01"},
1894+ {"bar01.wmf", "barbariantut00"},
1895+ });
1896+
1897+ Section& campvis_scenarios = legacy_campvis.get_safe_section("campmaps");
1898+ std::set<std::string> solved_legacy_scenarios;
1899+ for (const auto& legacy_list : legacy_scenarios) {
1900+ bool set_solved = false;
1901+ for (const auto& legacy_scenario : legacy_list) {
1902+ if (set_solved) {
1903+ solved_legacy_scenarios.insert(legacy_scenario.first);
1904+ }
1905+ set_solved = campvis_scenarios.get_bool(legacy_scenario.second.c_str(), false);
1906+ }
1907+ }
1908+
1909+ // Now write everything
1910+ Profile write_campvis(kCampVisFile.c_str());
1911+ Section& write_scenarios = write_campvis.pull_section("scenarios");
1912+ for (const auto& scenario : solved_legacy_scenarios) {
1913+ write_scenarios.set_bool(scenario.c_str(), true);
1914+ }
1915+
1916+ write_campvis.write(kCampVisFile.c_str(), true);
1917 }
1918
1919=== renamed file 'src/logic/campaign_visibility.h' => 'src/ui_fsmenu/campaigns.h'
1920--- src/logic/campaign_visibility.h 2019-02-23 11:00:49 +0000
1921+++ src/ui_fsmenu/campaigns.h 2019-04-23 16:17:37 +0000
1922@@ -17,22 +17,68 @@
1923 *
1924 */
1925
1926-#ifndef WL_LOGIC_CAMPAIGN_VISIBILITY_H
1927-#define WL_LOGIC_CAMPAIGN_VISIBILITY_H
1928+#ifndef WL_UI_FSMENU_CAMPAIGNS_H
1929+#define WL_UI_FSMENU_CAMPAIGNS_H
1930
1931-#include <cstring>
1932+#include <memory>
1933 #include <string>
1934-
1935-#include <stdint.h>
1936-
1937-struct CampaignVisibilitySave {
1938- std::string get_path();
1939- void set_campaign_visibility(const std::string&, bool);
1940- void set_map_visibility(const std::string&, bool);
1941+#include <unordered_set>
1942+#include <vector>
1943+
1944+#include "graphic/image.h"
1945+#include "scripting/lua_table.h"
1946+#include "wui/mapauthordata.h"
1947+
1948+/**
1949+ * Data about a campaign or tutorial scenario that we're interested in.
1950+ */
1951+struct ScenarioData {
1952+ std::string path;
1953+ std::string descname;
1954+ std::string description;
1955+ MapAuthorData authors;
1956+ bool is_tutorial;
1957+ bool playable;
1958+ bool visible;
1959+
1960+ ScenarioData() = default;
1961+};
1962+
1963+/**
1964+ * Data about a campaign that we're interested in.
1965+ */
1966+struct CampaignData {
1967+ std::string descname;
1968+ std::string tribename;
1969+ uint32_t difficulty_level;
1970+ const Image* difficulty_image;
1971+ std::string difficulty_description;
1972+ std::string description;
1973+ std::set<std::string> prerequisites;
1974+ bool visible;
1975+ std::vector<std::unique_ptr<ScenarioData>> scenarios;
1976+
1977+ CampaignData() = default;
1978+};
1979+
1980+struct Campaigns {
1981+ Campaigns();
1982+
1983+ size_t no_of_campaigns() const {
1984+ return campaigns_.size();
1985+ }
1986+
1987+ CampaignData* get_campaign(size_t campaign_index) const {
1988+ assert(campaign_index < campaigns_.size());
1989+ return campaigns_.at(campaign_index).get();
1990+ }
1991
1992 private:
1993- void make_campvis(const std::string&);
1994- void update_campvis(const std::string&);
1995+ void update_visibility_info();
1996+ static void update_legacy_campvis();
1997+
1998+ std::vector<std::unique_ptr<CampaignData>> campaigns_;
1999+ std::unordered_set<std::string> solved_scenarios_;
2000 };
2001
2002-#endif // end of include guard: WL_LOGIC_CAMPAIGN_VISIBILITY_H
2003+#endif // end of include guard: WL_UI_FSMENU_CAMPAIGNS_H
2004
2005=== added file 'src/ui_fsmenu/scenario_select.cc'
2006--- src/ui_fsmenu/scenario_select.cc 1970-01-01 00:00:00 +0000
2007+++ src/ui_fsmenu/scenario_select.cc 2019-04-23 16:17:37 +0000
2008@@ -0,0 +1,230 @@
2009+/*
2010+ * Copyright (C) 2002-2017 by the Widelands Development Team
2011+ *
2012+ * This program is free software; you can redistribute it and/or
2013+ * modify it under the terms of the GNU General Public License
2014+ * as published by the Free Software Foundation; either version 2
2015+ * of the License, or (at your option) any later version.
2016+ *
2017+ * This program is distributed in the hope that it will be useful,
2018+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2019+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2020+ * GNU General Public License for more details.
2021+ *
2022+ * You should have received a copy of the GNU General Public License
2023+ * along with this program; if not, write to the Free Software
2024+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2025+ *
2026+ */
2027+
2028+#include "ui_fsmenu/scenario_select.h"
2029+
2030+#include <memory>
2031+
2032+#include <boost/format.hpp>
2033+
2034+#include "base/i18n.h"
2035+#include "base/wexception.h"
2036+#include "graphic/graphic.h"
2037+#include "graphic/text_constants.h"
2038+#include "logic/filesystem_constants.h"
2039+#include "map_io/widelands_map_loader.h"
2040+#include "profile/profile.h"
2041+#include "scripting/lua_interface.h"
2042+#include "scripting/lua_table.h"
2043+#include "ui_basic/scrollbar.h"
2044+#include "ui_fsmenu/campaigns.h"
2045+
2046+/**
2047+ * FullscreenMenuScenarioSelect UI.
2048+ *
2049+ * Loads a list of all visible maps of selected campaign or all tutorials and
2050+ * lets the user choose one.
2051+ */
2052+FullscreenMenuScenarioSelect::FullscreenMenuScenarioSelect(CampaignData* camp)
2053+ : FullscreenMenuLoadMapOrGame(),
2054+ is_tutorial_(camp == nullptr),
2055+ table_(this, tablex_, tabley_, tablew_, tableh_, UI::PanelStyle::kFsMenu),
2056+ header_box_(this, 0, 0, UI::Box::Vertical),
2057+
2058+ // Main title
2059+ title_(&header_box_,
2060+ 0,
2061+ 0,
2062+ is_tutorial_ ? _("Choose a tutorial") : _("Choose a scenario"),
2063+ UI::Align::kCenter),
2064+ subtitle_(&header_box_,
2065+ 0,
2066+ 0,
2067+ UI::Scrollbar::kSize,
2068+ 0,
2069+ UI::PanelStyle::kFsMenu,
2070+ "",
2071+ UI::Align::kCenter,
2072+ UI::MultilineTextarea::ScrollMode::kNoScrolling),
2073+ scenario_details_(this),
2074+ campaign_(camp) {
2075+ title_.set_fontsize(UI_FONT_SIZE_BIG);
2076+
2077+ // Set subtitle of the page
2078+ if (campaign_ == nullptr) {
2079+ const std::string subtitle1 = _("Pick a tutorial from the list, then hit “OK”.");
2080+ const std::string subtitle2 =
2081+ _("You can see a description of the currently selected tutorial on the right.");
2082+ subtitle_.set_text((boost::format("%s\n%s") % subtitle1 % subtitle2).str());
2083+ } else {
2084+ subtitle_.set_text(
2085+ (boost::format("%s — %s") % campaign_->tribename % campaign_->descname).str());
2086+ }
2087+
2088+ header_box_.add_inf_space();
2089+ header_box_.add_inf_space();
2090+ header_box_.add_inf_space();
2091+ header_box_.add(&title_, UI::Box::Resizing::kFullSize);
2092+ header_box_.add_inf_space();
2093+ header_box_.add(&subtitle_, UI::Box::Resizing::kFullSize);
2094+ header_box_.add_inf_space();
2095+ header_box_.add_inf_space();
2096+ header_box_.add_inf_space();
2097+
2098+ back_.set_tooltip(is_tutorial_ ? _("Return to the main menu") :
2099+ _("Return to campaign selection"));
2100+ ok_.set_tooltip(is_tutorial_ ? _("Play this tutorial") : _("Play this scenario"));
2101+
2102+ ok_.sigclicked.connect(
2103+ boost::bind(&FullscreenMenuScenarioSelect::clicked_ok, boost::ref(*this)));
2104+ back_.sigclicked.connect(
2105+ boost::bind(&FullscreenMenuScenarioSelect::clicked_back, boost::ref(*this)));
2106+ table_.selected.connect(boost::bind(&FullscreenMenuScenarioSelect::entry_selected, this));
2107+ table_.double_clicked.connect(
2108+ boost::bind(&FullscreenMenuScenarioSelect::clicked_ok, boost::ref(*this)));
2109+
2110+ std::string number_tooltip;
2111+ std::string name_tooltip;
2112+ if (is_tutorial_) {
2113+ number_tooltip = _("The order in which the tutorials should be played");
2114+ name_tooltip = _("Tutorial Name");
2115+ } else {
2116+ number_tooltip = _("The number of this scenario in the campaign");
2117+ name_tooltip = _("Scenario Name");
2118+ }
2119+
2120+ /** TRANSLATORS: Campaign scenario number table header */
2121+ table_.add_column(35, _("#"), number_tooltip);
2122+ table_.add_column(
2123+ 0, name_tooltip, name_tooltip, UI::Align::kLeft, UI::TableColumnType::kFlexible);
2124+ table_.set_sort_column(0);
2125+ table_.focus();
2126+ fill_table();
2127+ layout();
2128+}
2129+
2130+void FullscreenMenuScenarioSelect::layout() {
2131+ FullscreenMenuLoadMapOrGame::layout();
2132+ header_box_.set_size(get_w(), tabley_);
2133+ table_.set_size(tablew_, tableh_);
2134+ table_.set_pos(Vector2i(tablex_, tabley_));
2135+ scenario_details_.set_size(get_right_column_w(right_column_x_), tableh_ - buth_ - 4 * padding_);
2136+ scenario_details_.set_pos(Vector2i(right_column_x_, tabley_));
2137+}
2138+
2139+std::string FullscreenMenuScenarioSelect::get_map() {
2140+ if (set_has_selection()) {
2141+ return g_fs->FileSystem::fix_cross_file(kCampaignsDir + "/" +
2142+ scenarios_data_.at(table_.get_selected()).path);
2143+ }
2144+ return "";
2145+}
2146+
2147+bool FullscreenMenuScenarioSelect::set_has_selection() {
2148+ const bool has_selection = table_.has_selection();
2149+ ok_.set_enabled(has_selection);
2150+ return has_selection;
2151+}
2152+
2153+void FullscreenMenuScenarioSelect::clicked_ok() {
2154+ if (!table_.has_selection()) {
2155+ return;
2156+ }
2157+ const ScenarioData& scenario_data = scenarios_data_[table_.get_selected()];
2158+ if (!scenario_data.playable) {
2159+ return;
2160+ }
2161+ end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kOk);
2162+}
2163+
2164+void FullscreenMenuScenarioSelect::entry_selected() {
2165+ if (set_has_selection()) {
2166+ const ScenarioData& scenario_data = scenarios_data_[table_.get_selected()];
2167+ scenario_details_.update(scenario_data);
2168+
2169+ // The dummy scenario can't be played, so we disable the OK button.
2170+ ok_.set_enabled(scenario_data.playable);
2171+ }
2172+}
2173+
2174+/**
2175+ * fill the campaign-map list
2176+ */
2177+void FullscreenMenuScenarioSelect::fill_table() {
2178+ if (is_tutorial_) {
2179+ // Load the tutorials
2180+ LuaInterface lua;
2181+ std::unique_ptr<LuaTable> table(lua.run_script("campaigns/tutorials.lua"));
2182+ for (const std::string& path : table->array_entries<std::string>()) {
2183+ ScenarioData scenario_data;
2184+ scenario_data.path = path;
2185+ scenario_data.playable = true;
2186+ scenario_data.is_tutorial = true;
2187+ scenarios_data_.push_back(scenario_data);
2188+ }
2189+ } else {
2190+ // Load the current campaign
2191+ for (auto& scenario_data : campaign_->scenarios) {
2192+ if (scenario_data->visible) {
2193+ scenario_data->is_tutorial = false;
2194+ scenario_data->playable = scenario_data->path != "dummy.wmf";
2195+ scenarios_data_.push_back(*scenario_data.get());
2196+ } else {
2197+ break;
2198+ }
2199+ }
2200+ }
2201+
2202+ for (size_t i = 0; i < scenarios_data_.size(); ++i) {
2203+ // Get details info from maps
2204+ ScenarioData* scenario_data = &scenarios_data_.at(i);
2205+ const std::string full_path =
2206+ g_fs->FileSystem::fix_cross_file(kCampaignsDir + "/" + scenario_data->path);
2207+ Widelands::Map map;
2208+ std::unique_ptr<Widelands::MapLoader> ml(map.get_correct_loader(full_path));
2209+ if (!ml) {
2210+ throw wexception(
2211+ _("Invalid path to file in campaigns.lua of tutorials.lua: %s"), full_path.c_str());
2212+ }
2213+
2214+ map.set_filename(full_path);
2215+ ml->preload_map(true);
2216+
2217+ {
2218+ i18n::Textdomain td("maps");
2219+ scenario_data->authors.set_authors(map.get_author());
2220+ scenario_data->descname = _(map.get_name());
2221+ scenario_data->description = _(map.get_description());
2222+ }
2223+
2224+ // Now add to table
2225+ UI::Table<uintptr_t>::EntryRecord& te = table_.add(i);
2226+ te.set_string(0, (boost::format("%d") % (i + 1)).str());
2227+ te.set_picture(
2228+ 1, g_gr->images().get("images/ui_basic/ls_wlmap.png"), scenario_data->descname);
2229+ if (!scenario_data->playable) {
2230+ te.set_color(UI_FONT_CLR_DISABLED);
2231+ }
2232+ }
2233+
2234+ if (!table_.empty()) {
2235+ table_.select(0);
2236+ }
2237+ entry_selected();
2238+}
2239
2240=== added file 'src/ui_fsmenu/scenario_select.h'
2241--- src/ui_fsmenu/scenario_select.h 1970-01-01 00:00:00 +0000
2242+++ src/ui_fsmenu/scenario_select.h 2019-04-23 16:17:37 +0000
2243@@ -0,0 +1,65 @@
2244+/*
2245+ * Copyright (C) 2002-2017 by the Widelands Development Team
2246+ *
2247+ * This program is free software; you can redistribute it and/or
2248+ * modify it under the terms of the GNU General Public License
2249+ * as published by the Free Software Foundation; either version 2
2250+ * of the License, or (at your option) any later version.
2251+ *
2252+ * This program is distributed in the hope that it will be useful,
2253+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2254+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2255+ * GNU General Public License for more details.
2256+ *
2257+ * You should have received a copy of the GNU General Public License
2258+ * along with this program; if not, write to the Free Software
2259+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2260+ *
2261+ */
2262+
2263+#ifndef WL_UI_FSMENU_SCENARIO_SELECT_H
2264+#define WL_UI_FSMENU_SCENARIO_SELECT_H
2265+
2266+#include "ui_basic/box.h"
2267+#include "ui_basic/multilinetextarea.h"
2268+#include "ui_basic/table.h"
2269+#include "ui_basic/textarea.h"
2270+#include "ui_fsmenu/load_map_or_game.h"
2271+#include "ui_fsmenu/scenariodetails.h"
2272+
2273+/*
2274+ * Fullscreen Menu for selecting a campaign or tutorial scenario
2275+ */
2276+class FullscreenMenuScenarioSelect : public FullscreenMenuLoadMapOrGame {
2277+public:
2278+ // If camp is not set, we'll be loading the tutorials
2279+ explicit FullscreenMenuScenarioSelect(CampaignData* camp = nullptr);
2280+
2281+ std::string get_map();
2282+
2283+protected:
2284+ void clicked_ok() override;
2285+ void entry_selected() override;
2286+ void fill_table() override;
2287+
2288+private:
2289+ void layout() override;
2290+
2291+ /// Updates buttons and text labels and returns whether a table entry is selected.
2292+ bool set_has_selection();
2293+
2294+ bool is_tutorial_;
2295+ UI::Table<uintptr_t const> table_;
2296+
2297+ UI::Box header_box_;
2298+
2299+ UI::Textarea title_;
2300+ UI::MultilineTextarea subtitle_;
2301+ ScenarioDetails scenario_details_;
2302+
2303+ CampaignData* campaign_;
2304+
2305+ std::vector<ScenarioData> scenarios_data_;
2306+};
2307+
2308+#endif // end of include guard: WL_UI_FSMENU_SCENARIO_SELECT_H
2309
2310=== added file 'src/ui_fsmenu/scenariodetails.cc'
2311--- src/ui_fsmenu/scenariodetails.cc 1970-01-01 00:00:00 +0000
2312+++ src/ui_fsmenu/scenariodetails.cc 2019-04-23 16:17:37 +0000
2313@@ -0,0 +1,74 @@
2314+/*
2315+ * Copyright (C) 2017 by the Widelands Development Team
2316+ *
2317+ * This program is free software; you can redistribute it and/or
2318+ * modify it under the terms of the GNU General Public License
2319+ * as published by the Free Software Foundation; either version 2
2320+ * of the License, or (at your option) any later version.
2321+ *
2322+ * This program is distributed in the hope that it will be useful,
2323+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2324+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2325+ * GNU General Public License for more details.
2326+ *
2327+ * You should have received a copy of the GNU General Public License
2328+ * along with this program; if not, write to the Free Software
2329+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2330+ */
2331+
2332+#include "ui_fsmenu/scenariodetails.h"
2333+
2334+#include <boost/format.hpp>
2335+
2336+#include "base/i18n.h"
2337+#include "graphic/text_constants.h"
2338+#include "ui_basic/scrollbar.h"
2339+
2340+ScenarioDetails::ScenarioDetails(Panel* parent)
2341+ : UI::Box(parent, 0, 0, UI::Box::Vertical),
2342+ name_label_(this,
2343+ 0,
2344+ 0,
2345+ UI::Scrollbar::kSize,
2346+ 0,
2347+ UI::PanelStyle::kFsMenu,
2348+ "",
2349+ UI::Align::kLeft,
2350+ UI::MultilineTextarea::ScrollMode::kNoScrolling),
2351+ descr_(this, 0, 0, UI::Scrollbar::kSize, 0, UI::PanelStyle::kFsMenu) {
2352+
2353+ constexpr int kPadding = 4;
2354+ add(&name_label_, UI::Box::Resizing::kFullSize);
2355+ add_space(kPadding);
2356+ add(&descr_, UI::Box::Resizing::kExpandBoth);
2357+}
2358+
2359+void ScenarioDetails::update(const ScenarioData& scenariodata) {
2360+ name_label_.set_text(
2361+ (boost::format("<rt>%s%s</rt>") %
2362+ as_heading(scenariodata.is_tutorial ? _("Tutorial") : _("Scenario"), UI::PanelStyle::kFsMenu, true) %
2363+ as_content(scenariodata.descname, UI::PanelStyle::kFsMenu))
2364+ .str());
2365+
2366+ if (scenariodata.playable) {
2367+ std::string description =
2368+ (boost::format("%s%s") %
2369+ as_heading(
2370+ ngettext("Author", "Authors", scenariodata.authors.get_number()), UI::PanelStyle::kFsMenu) %
2371+ as_content(scenariodata.authors.get_names(), UI::PanelStyle::kFsMenu))
2372+ .str();
2373+
2374+ description =
2375+ (boost::format("%s%s") % description % as_heading(_("Description"), UI::PanelStyle::kFsMenu))
2376+ .str();
2377+ description = (boost::format("%s%s") % description %
2378+ as_content(scenariodata.description, UI::PanelStyle::kFsMenu))
2379+ .str();
2380+
2381+ description = (boost::format("<rt>%s</rt>") % description).str();
2382+ descr_.set_text(description);
2383+ } else {
2384+ descr_.set_text("");
2385+ }
2386+ descr_.scroll_to_top();
2387+}
2388
2389=== added file 'src/ui_fsmenu/scenariodetails.h'
2390--- src/ui_fsmenu/scenariodetails.h 1970-01-01 00:00:00 +0000
2391+++ src/ui_fsmenu/scenariodetails.h 2019-04-23 16:17:37 +0000
2392@@ -0,0 +1,41 @@
2393+/*
2394+ * Copyright (C) 2017 by the Widelands Development Team
2395+ *
2396+ * This program is free software; you can redistribute it and/or
2397+ * modify it under the terms of the GNU General Public License
2398+ * as published by the Free Software Foundation; either version 2
2399+ * of the License, or (at your option) any later version.
2400+ *
2401+ * This program is distributed in the hope that it will be useful,
2402+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2403+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2404+ * GNU General Public License for more details.
2405+ *
2406+ * You should have received a copy of the GNU General Public License
2407+ * along with this program; if not, write to the Free Software
2408+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2409+ *
2410+ */
2411+
2412+#ifndef WL_UI_FSMENU_SCENARIODETAILS_H
2413+#define WL_UI_FSMENU_SCENARIODETAILS_H
2414+
2415+#include "ui_basic/box.h"
2416+#include "ui_basic/multilinetextarea.h"
2417+#include "ui_fsmenu/campaigns.h"
2418+
2419+/**
2420+ * Show a Box with information about a campaign or tutorial scenario.
2421+ */
2422+class ScenarioDetails : public UI::Box {
2423+public:
2424+ explicit ScenarioDetails(Panel* parent);
2425+
2426+ void update(const ScenarioData& scenariodata);
2427+
2428+private:
2429+ UI::MultilineTextarea name_label_;
2430+ UI::MultilineTextarea descr_;
2431+};
2432+
2433+#endif // end of include guard: WL_UI_FSMENU_SCENARIODETAILS_H
2434
2435=== modified file 'src/wlapplication.cc'
2436--- src/wlapplication.cc 2019-03-09 12:13:02 +0000
2437+++ src/wlapplication.cc 2019-04-23 16:17:37 +0000
2438@@ -82,6 +82,7 @@
2439 #include "ui_basic/progresswindow.h"
2440 #include "ui_fsmenu/about.h"
2441 #include "ui_fsmenu/campaign_select.h"
2442+#include "ui_fsmenu/campaigns.h"
2443 #include "ui_fsmenu/internet_lobby.h"
2444 #include "ui_fsmenu/intro.h"
2445 #include "ui_fsmenu/launch_spg.h"
2446@@ -91,6 +92,7 @@
2447 #include "ui_fsmenu/multiplayer.h"
2448 #include "ui_fsmenu/netsetup_lan.h"
2449 #include "ui_fsmenu/options.h"
2450+#include "ui_fsmenu/scenario_select.h"
2451 #include "ui_fsmenu/singleplayer.h"
2452 #include "wlapplication_messages.h"
2453 #include "wui/game_tips.h"
2454@@ -1141,8 +1143,7 @@
2455 Widelands::Game game;
2456 std::string filename;
2457 // Start UI for the tutorials.
2458- FullscreenMenuCampaignMapSelect select_campaignmap(true);
2459- select_campaignmap.set_campaign(0);
2460+ FullscreenMenuScenarioSelect select_campaignmap;
2461 if (select_campaignmap.run<FullscreenMenuBase::MenuTarget>() ==
2462 FullscreenMenuBase::MenuTarget::kOk) {
2463 filename = select_campaignmap.get_map();
2464@@ -1369,20 +1370,22 @@
2465 Widelands::Game game;
2466 std::string filename;
2467 for (;;) { // Campaign UI - Loop
2468- int32_t campaign;
2469+ std::unique_ptr<Campaigns> campaign_visibility(new Campaigns());
2470+
2471+ size_t campaign_index;
2472 { // First start UI for selecting the campaign.
2473- FullscreenMenuCampaignSelect select_campaign;
2474+ FullscreenMenuCampaignSelect select_campaign(campaign_visibility.get());
2475 if (select_campaign.run<FullscreenMenuBase::MenuTarget>() ==
2476- FullscreenMenuBase::MenuTarget::kOk)
2477- campaign = select_campaign.get_campaign();
2478- else { // back was pressed
2479+ FullscreenMenuBase::MenuTarget::kOk) {
2480+ campaign_index = select_campaign.get_campaign_index();
2481+ } else { // back was pressed
2482 filename = "";
2483 break;
2484 }
2485 }
2486 // Then start UI for the selected campaign.
2487- FullscreenMenuCampaignMapSelect select_campaignmap;
2488- select_campaignmap.set_campaign(campaign);
2489+ CampaignData* campaign_data = campaign_visibility->get_campaign(campaign_index);
2490+ FullscreenMenuScenarioSelect select_campaignmap(campaign_data);
2491 if (select_campaignmap.run<FullscreenMenuBase::MenuTarget>() ==
2492 FullscreenMenuBase::MenuTarget::kOk) {
2493 filename = select_campaignmap.get_map();
2494
2495=== modified file 'src/wui/CMakeLists.txt'
2496--- src/wui/CMakeLists.txt 2018-11-13 12:18:10 +0000
2497+++ src/wui/CMakeLists.txt 2019-04-23 16:17:37 +0000
2498@@ -87,6 +87,7 @@
2499
2500 wl_library(wui_common_mapdetails
2501 SRCS
2502+ mapauthordata.h
2503 mapdetails.cc
2504 mapdetails.h
2505 mapdata.cc
2506@@ -102,9 +103,9 @@
2507 graphic
2508 graphic_fonthandler
2509 graphic_text_constants
2510+ graphic_text_layout
2511 io_filesystem
2512 logic
2513- logic_constants
2514 logic_game_controller
2515 logic_game_settings
2516 map_io_map_loader
2517
2518=== modified file 'src/wui/load_or_save_game.cc'
2519--- src/wui/load_or_save_game.cc 2019-03-30 19:46:16 +0000
2520+++ src/wui/load_or_save_game.cc 2019-04-23 16:17:37 +0000
2521@@ -366,22 +366,20 @@
2522
2523 if (filetype_ == FileType::kReplay) {
2524 gamefiles = filter(g_fs->list_directory(kReplayDir), [](const std::string& fn) {
2525- return boost::ends_with(fn, kReplayExtension);
2526+ return boost::algorithm::ends_with(fn, kReplayExtension);
2527 });
2528 // Update description column title for replays
2529 table_.set_column_tooltip(2, show_filenames_ ? _("Filename: Map name (start of replay)") :
2530 _("Map name (start of replay)"));
2531 } else {
2532- gamefiles = g_fs->list_directory(kSaveDir);
2533+ gamefiles = filter(g_fs->list_directory(kSaveDir), [](const std::string& fn) {
2534+ return boost::algorithm::ends_with(fn, kSavegameExtension);
2535+ });
2536 }
2537
2538 Widelands::GamePreloadPacket gpdp;
2539
2540 for (const std::string& gamefilename : gamefiles) {
2541- if (gamefilename == kCampVisFile || gamefilename == g_fs->fix_cross_file(kCampVisFile)) {
2542- continue;
2543- }
2544-
2545 SavegameData gamedata;
2546
2547 std::string savename = gamefilename;
2548
2549=== added file 'src/wui/mapauthordata.h'
2550--- src/wui/mapauthordata.h 1970-01-01 00:00:00 +0000
2551+++ src/wui/mapauthordata.h 2019-04-23 16:17:37 +0000
2552@@ -0,0 +1,72 @@
2553+/*
2554+ * Copyright (C) 2002-2017 by the Widelands Development Team
2555+ *
2556+ * This program is free software; you can redistribute it and/or
2557+ * modify it under the terms of the GNU General Public License
2558+ * as published by the Free Software Foundation; either version 2
2559+ * of the License, or (at your option) any later version.
2560+ *
2561+ * This program is distributed in the hope that it will be useful,
2562+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2563+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2564+ * GNU General Public License for more details.
2565+ *
2566+ * You should have received a copy of the GNU General Public License
2567+ * along with this program; if not, write to the Free Software
2568+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2569+ *
2570+ */
2571+
2572+#ifndef WL_WUI_MAPAUTHORDATA_H
2573+#define WL_WUI_MAPAUTHORDATA_H
2574+
2575+#include <set>
2576+#include <string>
2577+#include <vector>
2578+
2579+#include <boost/algorithm/string.hpp>
2580+#include <boost/format.hpp>
2581+
2582+#include "base/i18n.h"
2583+#include "io/filesystem/filesystem.h"
2584+#include "logic/map.h"
2585+
2586+/**
2587+ * Author data for a map or scenario.
2588+ */
2589+struct MapAuthorData {
2590+ const std::string& get_names() const {
2591+ return names_;
2592+ }
2593+ size_t get_number() const {
2594+ return number_;
2595+ }
2596+
2597+ void set_authors(const std::string& author_list) {
2598+ std::vector<std::string> authors;
2599+ {
2600+ i18n::Textdomain td("maps");
2601+ const std::string loc_author_list = _(author_list);
2602+ boost::split(authors, loc_author_list, boost::is_any_of(","));
2603+ }
2604+ names_ = i18n::localize_list(authors, i18n::ConcatenateWith::AMPERSAND);
2605+ number_ = authors.size();
2606+ }
2607+
2608+ // We allow empty authors, because those will often be loaded
2609+ // later from the maps
2610+ MapAuthorData() = default;
2611+
2612+ // Parses author list string into localized contatenated list
2613+ // string. Use , as list separator and no whitespaces between
2614+ // author names.
2615+ explicit MapAuthorData(const std::string& author_list) {
2616+ set_authors(author_list);
2617+ }
2618+
2619+private:
2620+ std::string names_;
2621+ size_t number_;
2622+};
2623+
2624+#endif // end of include guard: WL_WUI_MAPAUTHORDATA_H
2625
2626=== modified file 'src/wui/mapdata.cc'
2627--- src/wui/mapdata.cc 2019-02-23 11:00:49 +0000
2628+++ src/wui/mapdata.cc 2019-04-23 16:17:37 +0000
2629@@ -41,16 +41,16 @@
2630 const std::string& init_filename,
2631 const MapData::MapType& init_maptype,
2632 const MapData::DisplayType& init_displaytype)
2633- : MapData(init_filename, _("No Name"), _("No Author"), init_maptype, init_displaytype) {
2634+ : MapData(init_filename,
2635+ _("No Name"),
2636+ map.get_author().empty() ? _("No Author") : map.get_author(),
2637+ init_maptype,
2638+ init_displaytype) {
2639
2640 i18n::Textdomain td("maps");
2641 if (!map.get_name().empty()) {
2642 name = map.get_name();
2643- }
2644- localized_name = _(name);
2645- // Localizing this, because some author fields now have "edited by" text.
2646- if (!map.get_author().empty()) {
2647- authors = map.get_author();
2648+ localized_name = _(name);
2649 }
2650 description = map.get_description().empty() ? "" : _(map.get_description());
2651 hint = map.get_hint().empty() ? "" : _(map.get_hint());
2652@@ -68,7 +68,7 @@
2653 MapData::MapData(const std::string& init_filename, const std::string& init_localized_name)
2654 : MapData(init_filename,
2655 init_localized_name,
2656- "",
2657+ _("No Author"),
2658 MapData::MapType::kDirectory,
2659 MapData::DisplayType::kMapnamesLocalized) {
2660 }
2661
2662=== modified file 'src/wui/mapdata.h'
2663--- src/wui/mapdata.h 2019-02-23 11:00:49 +0000
2664+++ src/wui/mapdata.h 2019-04-23 16:17:37 +0000
2665@@ -30,33 +30,7 @@
2666 #include "base/i18n.h"
2667 #include "io/filesystem/filesystem.h"
2668 #include "logic/map.h"
2669-#include "logic/widelands.h"
2670-
2671-/**
2672- * Author data for a map or scenario.
2673- */
2674-struct MapAuthorData {
2675- const std::string& get_names() const {
2676- return names_;
2677- }
2678- size_t get_number() const {
2679- return number_;
2680- }
2681-
2682- // Parses author list string into localized contatenated list
2683- // string. Use , as list separator and no whitespaces between
2684- // author names.
2685- MapAuthorData(const std::string& author_list) {
2686- std::vector<std::string> authors;
2687- boost::split(authors, author_list, boost::is_any_of(","));
2688- names_ = i18n::localize_list(authors, i18n::ConcatenateWith::AMPERSAND);
2689- number_ = authors.size();
2690- }
2691-
2692-private:
2693- std::string names_;
2694- size_t number_;
2695-};
2696+#include "wui/mapauthordata.h"
2697
2698 /**
2699 * Data about a map that we're interested in.
2700
2701=== modified file 'src/wui/mapdetails.h'
2702--- src/wui/mapdetails.h 2019-02-23 11:00:49 +0000
2703+++ src/wui/mapdetails.h 2019-04-23 16:17:37 +0000
2704@@ -20,6 +20,7 @@
2705 #ifndef WL_WUI_MAPDETAILS_H
2706 #define WL_WUI_MAPDETAILS_H
2707
2708+#include "graphic/text_layout.h"
2709 #include "ui_basic/box.h"
2710 #include "ui_basic/multilinetextarea.h"
2711 #include "ui_basic/panel.h"

Subscribers

People subscribed via source and target branches

to status/vote changes: