Add basic achievements functionality. (#7237)
* Add basic achievements functionality. This reads the mainline achievements.cfg and then all the achievements of each installed add-on. This is intentionally handled separately from other WML loading so that: a) All achievements and their status are able to be displayed on the main menu right after Wesnoth starts and regardless of which add-ons are active. b) Add-ons can add additional achievements to other content, whether UMC or mainline. For example, a modification that adds more achievements for mainline campaigns. Marking something as achieved is handled by the new [set_achieved] tag and whether an achievement has been completed can be checked via [has_achievement]. There is no attempt to prevent people from manually editing which achievements they've accomplished. NOTE: These are *not* in any way related to Steam achievements!
This commit is contained in:
parent
8887d9eac2
commit
d1465a9eb9
54 changed files with 1057 additions and 7 deletions
|
@ -1,5 +1,6 @@
|
|||
test_fire_event
|
||||
test_gui2_iterator
|
||||
test_gui2/modal_dialog_test_achievements_dialog
|
||||
test_gui2/modal_dialog_test_addon_auth
|
||||
test_gui2/modal_dialog_test_addon_connect
|
||||
test_gui2/modal_dialog_test_addon_license_prompt
|
||||
|
|
97
data/achievements.cfg
Normal file
97
data/achievements.cfg
Normal file
|
@ -0,0 +1,97 @@
|
|||
#textdomain wesnoth
|
||||
|
||||
# NOTE: due to achievements being loaded separately from the rest of the WML, this macro is not available for general use.
|
||||
#define ACHIEVEMENT ID NAME DESC
|
||||
#arg HIDDEN
|
||||
no#endarg
|
||||
|
||||
#arg HIDDEN_NAME
|
||||
_"???"#endarg
|
||||
|
||||
#arg HIDDEN_HINT
|
||||
#endarg
|
||||
|
||||
#arg NAME_COMPLETE
|
||||
#endarg
|
||||
|
||||
#arg DESC_COMPLETE
|
||||
#endarg
|
||||
|
||||
#arg ICON
|
||||
data/core/images/icons/potion_red_small.png#endarg
|
||||
|
||||
#arg ICON_COMPLETE
|
||||
data/core/images/icons/potion_green_small.png#endarg
|
||||
|
||||
[achievement]
|
||||
id={ID}
|
||||
name={NAME}
|
||||
name_completed={NAME_COMPLETE}
|
||||
description={DESC}
|
||||
description_completed={DESC_COMPLETE}
|
||||
icon={ICON}
|
||||
icon_completed={ICON_COMPLETE}
|
||||
hidden={HIDDEN}
|
||||
hidden_name={HIDDEN_NAME}
|
||||
hidden_hint={HIDDEN_HINT}
|
||||
[/achievement]
|
||||
#enddef
|
||||
|
||||
# some distros (ie: Debian) put campaigns in separate independent packages, so they aren't all guaranteed to exist even though they're part of mainline
|
||||
#ifhave campaigns/tutorial
|
||||
{campaigns/tutorial/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Dead_Water
|
||||
{campaigns/Dead_Water/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Delfadors_Memoirs
|
||||
{campaigns/Delfadors_Memoirs/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Descent_Into_Darkness
|
||||
{campaigns/Descent_Into_Darkness/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Eastern_Invasion
|
||||
{campaigns/Eastern_Invasion/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Heir_To_The_Throne
|
||||
{campaigns/Heir_To_The_Throne/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Legend_of_Wesmere
|
||||
{campaigns/Legend_of_Wesmere/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Liberty
|
||||
{campaigns/Liberty/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Northern_Rebirth
|
||||
{campaigns/Northern_Rebirth/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Sceptre_of_Fire
|
||||
{campaigns/Sceptre_of_Fire/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Secrets_of_the_Ancients
|
||||
{campaigns/Secrets_of_the_Ancients/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Son_Of_The_Black_Eye
|
||||
{campaigns/Son_Of_The_Black_Eye/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/The_Hammer_of_Thursagan
|
||||
{campaigns/The_Hammer_of_Thursagan/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/The_Rise_Of_Wesnoth
|
||||
{campaigns/The_Rise_Of_Wesnoth/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/The_South_Guard
|
||||
{campaigns/The_South_Guard/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Two_Brothers
|
||||
{campaigns/Two_Brothers/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Under_the_Burning_Suns
|
||||
{campaigns/Under_the_Burning_Suns/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/Winds_of_Fate
|
||||
{campaigns/Winds_of_Fate/achievements.cfg}
|
||||
#endif
|
||||
#ifhave campaigns/World_Conquest
|
||||
{campaigns/World_Conquest/achievements.cfg}
|
||||
#endif
|
0
data/campaigns/Dead_Water/achievements.cfg
Normal file
0
data/campaigns/Dead_Water/achievements.cfg
Normal file
0
data/campaigns/Delfadors_Memoirs/achievements.cfg
Normal file
0
data/campaigns/Delfadors_Memoirs/achievements.cfg
Normal file
5
data/campaigns/Descent_Into_Darkness/achievements.cfg
Normal file
5
data/campaigns/Descent_Into_Darkness/achievements.cfg
Normal file
|
@ -0,0 +1,5 @@
|
|||
[achievement_group]
|
||||
display_name=_"Descent into Darkness"
|
||||
content_for=descent_into_darkness
|
||||
{ACHIEVEMENT "did_rat_eater" _"Rat Eater" _"Have a Ghoul eat way too many rats during Descent into Darkness's A Haunting in Winter." HIDDEN=yes HIDDEN_HINT=_"Eat some rats!"}
|
||||
[/achievement_group]
|
|
@ -1206,6 +1206,11 @@
|
|||
speaker=Malin Keshar
|
||||
message= _ "I must be absolutely mad to be spending hours here just feeding rats to this rotten pile of flesh..."
|
||||
[/message]
|
||||
|
||||
[set_achieved]
|
||||
content_for="Descent into Darkness"
|
||||
id="did_rat_eater"
|
||||
[/set_achieved]
|
||||
[/event]
|
||||
[event]
|
||||
name=rat eating2
|
||||
|
|
0
data/campaigns/Eastern_Invasion/achievements.cfg
Normal file
0
data/campaigns/Eastern_Invasion/achievements.cfg
Normal file
0
data/campaigns/Heir_To_The_Throne/achievements.cfg
Normal file
0
data/campaigns/Heir_To_The_Throne/achievements.cfg
Normal file
0
data/campaigns/Legend_of_Wesmere/achievements.cfg
Normal file
0
data/campaigns/Legend_of_Wesmere/achievements.cfg
Normal file
0
data/campaigns/Liberty/achievements.cfg
Normal file
0
data/campaigns/Liberty/achievements.cfg
Normal file
0
data/campaigns/Northern_Rebirth/achievements.cfg
Normal file
0
data/campaigns/Northern_Rebirth/achievements.cfg
Normal file
0
data/campaigns/Sceptre_of_Fire/achievements.cfg
Normal file
0
data/campaigns/Sceptre_of_Fire/achievements.cfg
Normal file
0
data/campaigns/Secrets_of_the_Ancients/achievements.cfg
Normal file
0
data/campaigns/Secrets_of_the_Ancients/achievements.cfg
Normal file
0
data/campaigns/Son_Of_The_Black_Eye/achievements.cfg
Normal file
0
data/campaigns/Son_Of_The_Black_Eye/achievements.cfg
Normal file
0
data/campaigns/The_Hammer_of_Thursagan/achievements.cfg
Normal file
0
data/campaigns/The_Hammer_of_Thursagan/achievements.cfg
Normal file
0
data/campaigns/The_Rise_Of_Wesnoth/achievements.cfg
Normal file
0
data/campaigns/The_Rise_Of_Wesnoth/achievements.cfg
Normal file
0
data/campaigns/The_South_Guard/achievements.cfg
Normal file
0
data/campaigns/The_South_Guard/achievements.cfg
Normal file
0
data/campaigns/Two_Brothers/achievements.cfg
Normal file
0
data/campaigns/Two_Brothers/achievements.cfg
Normal file
0
data/campaigns/Under_the_Burning_Suns/achievements.cfg
Normal file
0
data/campaigns/Under_the_Burning_Suns/achievements.cfg
Normal file
0
data/campaigns/Winds_of_Fate/achievements.cfg
Normal file
0
data/campaigns/Winds_of_Fate/achievements.cfg
Normal file
0
data/campaigns/World_Conquest/achievements.cfg
Normal file
0
data/campaigns/World_Conquest/achievements.cfg
Normal file
5
data/campaigns/tutorial/achievements.cfg
Normal file
5
data/campaigns/tutorial/achievements.cfg
Normal file
|
@ -0,0 +1,5 @@
|
|||
[achievement_group]
|
||||
display_name=_"Tutorial"
|
||||
content_for=tutorial
|
||||
{ACHIEVEMENT "completed" _"Complete the Tutorial" _"Complete all scenarios of the Tutorial campaign."}
|
||||
[/achievement_group]
|
|
@ -1498,6 +1498,11 @@ Rest-healing is an exception to the rule — if a unit doesn’t do anything for
|
|||
message= _ "You can also refer to the in-game help browser if you ever need to refresh your memory on gameplay mechanics."
|
||||
[/message]
|
||||
|
||||
[set_achieved]
|
||||
content_for="Tutorial"
|
||||
id="tutorial_finished"
|
||||
[/set_achieved]
|
||||
|
||||
{CLEAR_VARIABLE low_hp_unit_message,lhpu_msg_i}
|
||||
{CLEAR_VARIABLE spoke_about_income,spoke_about_orcs_crossing_river}
|
||||
{CLEAR_VARIABLE undo_option}
|
||||
|
|
|
@ -57,6 +57,12 @@
|
|||
key=a
|
||||
{IF_APPLE_CMD_ELSE_CTRL}
|
||||
[/hotkey]
|
||||
[hotkey]
|
||||
command=achievements
|
||||
key=a
|
||||
shift=yes
|
||||
{IF_APPLE_CMD_ELSE_CTRL}
|
||||
[/hotkey]
|
||||
[hotkey]
|
||||
command=bestenemymoves
|
||||
key=b
|
||||
|
|
220
data/gui/window/achievements_dialog.cfg
Normal file
220
data/gui/window/achievements_dialog.cfg
Normal file
|
@ -0,0 +1,220 @@
|
|||
#textdomain wesnoth-lib
|
||||
###
|
||||
### Definition of the window for showing achievements and their statuses
|
||||
###
|
||||
|
||||
[window]
|
||||
id = "achievements_dialog"
|
||||
description = "Dialog for displaying achievements and their statuses"
|
||||
|
||||
[resolution]
|
||||
definition = "default"
|
||||
|
||||
automatic_placement = true
|
||||
vertical_placement = "center"
|
||||
horizontal_placement = "center"
|
||||
|
||||
maximum_width = 800
|
||||
|
||||
[tooltip]
|
||||
id = "tooltip"
|
||||
[/tooltip]
|
||||
|
||||
[helptip]
|
||||
id = "tooltip"
|
||||
[/helptip]
|
||||
|
||||
[linked_group]
|
||||
id = "achievements"
|
||||
fixed_width = true
|
||||
fixed_height = true
|
||||
[/linked_group]
|
||||
|
||||
[grid]
|
||||
|
||||
[row]
|
||||
[column]
|
||||
horizontal_grow = true
|
||||
[grid]
|
||||
[row]
|
||||
[column]
|
||||
grow_factor = 1
|
||||
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
[label]
|
||||
definition = "title"
|
||||
|
||||
label = _ "Achievements"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
horizontal_alignment = "right"
|
||||
grow_factor = 0
|
||||
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[label]
|
||||
definition = "default"
|
||||
label = _ "Content:"
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
horizontal_alignment = "right"
|
||||
grow_factor = 0
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[menu_button]
|
||||
id = "selected_achievements_list"
|
||||
definition = "default"
|
||||
[/menu_button]
|
||||
|
||||
[/column]
|
||||
[/row]
|
||||
[/grid]
|
||||
[/column]
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 1
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
{GUI_FORCE_WIDGET_MINIMUM_SIZE 600 480 (
|
||||
[listbox]
|
||||
id = "achievements_list"
|
||||
|
||||
vertical_scrollbar_mode = "always"
|
||||
horizontal_scrollbar_mode = "never"
|
||||
|
||||
[list_definition]
|
||||
|
||||
[row]
|
||||
|
||||
[column]
|
||||
horizontal_grow = true
|
||||
|
||||
[toggle_panel]
|
||||
id = "panel"
|
||||
definition = "default"
|
||||
linked_group = "achievements"
|
||||
|
||||
[grid]
|
||||
[row]
|
||||
[column]
|
||||
grow_factor = 0
|
||||
horizontal_grow = false
|
||||
vertical_alignment = "center"
|
||||
horizontal_alignment = "left"
|
||||
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[drawing]
|
||||
id = "icon"
|
||||
definition = "default"
|
||||
|
||||
width = 72
|
||||
height = 72
|
||||
|
||||
[draw]
|
||||
|
||||
[image]
|
||||
name = "(text)"
|
||||
w = "(min(image_original_width, 60))"
|
||||
h = "(min(image_original_height, 60))"
|
||||
|
||||
{GUI_CENTERED_IMAGE}
|
||||
[/image]
|
||||
|
||||
[/draw]
|
||||
|
||||
[/drawing]
|
||||
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_alignment = "left"
|
||||
[grid]
|
||||
[row]
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
id = "name"
|
||||
definition = "default_large"
|
||||
use_markup = true
|
||||
[/label]
|
||||
[/column]
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
[column]
|
||||
grow_factor = 1
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "left"
|
||||
|
||||
[label]
|
||||
id = "description"
|
||||
definition = "default_small"
|
||||
characters_per_line = 70
|
||||
use_markup = true
|
||||
[/label]
|
||||
[/column]
|
||||
[/row]
|
||||
[/grid]
|
||||
[/column]
|
||||
[/row]
|
||||
[/grid]
|
||||
[/toggle_panel]
|
||||
|
||||
[/column]
|
||||
[/row]
|
||||
|
||||
[/list_definition]
|
||||
[/listbox]
|
||||
)}
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
border = "all"
|
||||
border_size = 5
|
||||
horizontal_alignment = "right"
|
||||
|
||||
[button]
|
||||
id = "ok"
|
||||
definition = "default"
|
||||
|
||||
label = _ "OK"
|
||||
[/button]
|
||||
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[/grid]
|
||||
|
||||
[/resolution]
|
||||
|
||||
[/window]
|
|
@ -38,3 +38,7 @@ function wesnoth.wml_conditionals.variable(cfg)
|
|||
return old_variable(cfg)
|
||||
end
|
||||
end
|
||||
|
||||
function wesnoth.wml_conditionals.has_achievement(cfg)
|
||||
return wesnoth.achievements.has(cfg.content_for, cfg.id);
|
||||
end
|
||||
|
|
|
@ -1011,3 +1011,12 @@ function wml_actions.remove_trait(cfg)
|
|||
unit:remove_modifications({id = obj_id}, "trait")
|
||||
end
|
||||
end
|
||||
|
||||
function wml_actions.set_achievement(cfg)
|
||||
local achievement = wesnoth.achievements.get(cfg.content_for, cfg.id)
|
||||
-- don't show the achievement popup for an achievement they already have
|
||||
if not achievement.achieved then
|
||||
wesnoth.achievements.set(cfg.content_for, cfg.id)
|
||||
gui.show_popup(achievement.name_completed, achievement.description_completed, achievement.icon_completed)
|
||||
end
|
||||
end
|
||||
|
|
27
data/schema/achievements.cfg
Normal file
27
data/schema/achievements.cfg
Normal file
|
@ -0,0 +1,27 @@
|
|||
[wml_schema]
|
||||
{./macros.cfg}
|
||||
{./types/basic.cfg}
|
||||
[tag]
|
||||
name="root"
|
||||
[tag]
|
||||
name="achievement_group"
|
||||
max="infinite"
|
||||
{REQUIRED_KEY content_for string}
|
||||
{REQUIRED_KEY display_name t_string}
|
||||
[tag]
|
||||
name="achievement"
|
||||
max="infinite"
|
||||
{REQUIRED_KEY id string}
|
||||
{REQUIRED_KEY name t_string}
|
||||
{SIMPLE_KEY name_completed t_string}
|
||||
{REQUIRED_KEY description t_string}
|
||||
{SIMPLE_KEY description_completed t_string}
|
||||
{REQUIRED_KEY icon string}
|
||||
{SIMPLE_KEY icon_completed string}
|
||||
{SIMPLE_KEY hidden bool}
|
||||
{SIMPLE_KEY hidden_name t_string}
|
||||
{SIMPLE_KEY hidden_hint t_string}
|
||||
[/tag]
|
||||
[/tag]
|
||||
[/tag]
|
||||
[/wml_schema]
|
|
@ -0,0 +1,36 @@
|
|||
#####
|
||||
# API(s) being tested: [has_achievement]
|
||||
#####
|
||||
{GENERIC_UNIT_TEST "has_achievement" (
|
||||
[event]
|
||||
name = start
|
||||
|
||||
[if]
|
||||
[has_achievement]
|
||||
content_for=tutorial
|
||||
id=completed
|
||||
[/has_achievement]
|
||||
[then]
|
||||
{FAIL}
|
||||
[/then]
|
||||
[/if]
|
||||
|
||||
[set_achievement]
|
||||
content_for=tutorial
|
||||
id=completed
|
||||
[/set_achievement]
|
||||
|
||||
[if]
|
||||
[has_achievement]
|
||||
content_for=tutorial
|
||||
id=completed
|
||||
[/has_achievement]
|
||||
[then]
|
||||
{SUCCEED}
|
||||
[/then]
|
||||
[else]
|
||||
{FAIL}
|
||||
[/else]
|
||||
[/if]
|
||||
[/event]
|
||||
)}
|
|
@ -100,6 +100,8 @@
|
|||
</Unit>
|
||||
<Unit filename="../../src/about.cpp" />
|
||||
<Unit filename="../../src/about.hpp" />
|
||||
<Unit filename="../../src/achievements.cpp" />
|
||||
<Unit filename="../../src/achievements.hpp" />
|
||||
<Unit filename="../../src/actions/advancement.cpp" />
|
||||
<Unit filename="../../src/actions/advancement.hpp" />
|
||||
<Unit filename="../../src/actions/attack.cpp" />
|
||||
|
@ -565,6 +567,8 @@
|
|||
<Unit filename="../../src/gui/core/window_builder/helper.hpp" />
|
||||
<Unit filename="../../src/gui/core/window_builder/instance.cpp" />
|
||||
<Unit filename="../../src/gui/core/window_builder/instance.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/achievements_dialog.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/achievements_dialog.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/addon/addon_auth.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/addon/addon_auth.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/addon/connect.cpp" />
|
||||
|
|
|
@ -98,6 +98,8 @@
|
|||
</Unit>
|
||||
<Unit filename="../../src/about.cpp" />
|
||||
<Unit filename="../../src/about.hpp" />
|
||||
<Unit filename="../../src/achievements.cpp" />
|
||||
<Unit filename="../../src/achievements.hpp" />
|
||||
<Unit filename="../../src/actions/advancement.cpp" />
|
||||
<Unit filename="../../src/actions/advancement.hpp" />
|
||||
<Unit filename="../../src/actions/attack.cpp" />
|
||||
|
@ -560,6 +562,8 @@
|
|||
<Unit filename="../../src/gui/core/window_builder/helper.hpp" />
|
||||
<Unit filename="../../src/gui/core/window_builder/instance.cpp" />
|
||||
<Unit filename="../../src/gui/core/window_builder/instance.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/achievements_dialog.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/achievements_dialog.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/addon/addon_auth.cpp" />
|
||||
<Unit filename="../../src/gui/dialogs/addon/addon_auth.hpp" />
|
||||
<Unit filename="../../src/gui/dialogs/addon/connect.cpp" />
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
1234567890ABCDEF12345678 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
|
||||
000000000000000000000005 /* achievements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000001 /* achievements.cpp */; };
|
||||
000000000000000000000006 /* achievements_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000003 /* achievements_dialog.cpp */; };
|
||||
1234567890ABCDEF12345679 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
|
||||
000000000000000000000007 /* achievements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000001 /* achievements.cpp */; };
|
||||
000000000000000000000008 /* achievements_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000003 /* achievements_dialog.cpp */; };
|
||||
36B146FAA79A55E9F43723B1 /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
|
||||
4291489DA38012477DA3BA7C /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
|
||||
3C254DF5B7DF196F2041955F /* mp_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58C649488B3014E6F7254B62 /* mp_report.cpp */; };
|
||||
|
@ -1498,6 +1502,10 @@
|
|||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
1234567890ABCDEF12345680 /* file_progress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_progress.cpp; sourceTree = "<group>"; };
|
||||
1234567890ABCDEF12345681 /* file_progress.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = file_progress.hpp; sourceTree = "<group>"; };
|
||||
000000000000000000000001 /* achievements.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = achievements.cpp; sourceTree = "<group>"; };
|
||||
000000000000000000000002 /* achievements.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = achievements.hpp; sourceTree = "<group>"; };
|
||||
000000000000000000000003 /* achievements_dialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = achievements_dialog.cpp; sourceTree = "<group>"; };
|
||||
000000000000000000000004 /* achievements_dialog.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = achievements_dialog.hpp; sourceTree = "<group>"; };
|
||||
1C58BBDF21822A930078D25A /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
460D897824DC7830000B1ABC /* game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = game.cpp; path = ../../src/server/wesnothd/game.cpp; sourceTree = SOURCE_ROOT; };
|
||||
460D897924DC7830000B1ABC /* ban.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ban.hpp; path = ../../src/server/wesnothd/ban.hpp; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -3095,6 +3103,8 @@
|
|||
children = (
|
||||
B5599AC60EC62181008DD061 /* about.cpp */,
|
||||
B5599ABC0EC62181008DD061 /* about.hpp */,
|
||||
000000000000000000000001 /* achievements.cpp */,
|
||||
000000000000000000000002 /* achievements.hpp */,
|
||||
620A386115E9364E00A4F513 /* actions */,
|
||||
B52EE9A81213640400CFBDAB /* addon */,
|
||||
B597EBCA0FC082AB00CE81F5 /* ai */,
|
||||
|
@ -3637,6 +3647,8 @@
|
|||
46F92C842174F6A300602C1C /* file_dialog.hpp */,
|
||||
1234567890ABCDEF12345680 /* file_progress.cpp */,
|
||||
1234567890ABCDEF12345681 /* file_progress.hpp */,
|
||||
000000000000000000000003 /* achievements_dialog.cpp */,
|
||||
000000000000000000000004 /* achievements_dialog.hpp */,
|
||||
46F92C6E2174F6A300602C1C /* folder_create.cpp */,
|
||||
46F92C8A2174F6A300602C1C /* folder_create.hpp */,
|
||||
46F92CBB2174F6A300602C1C /* formula_debugger.cpp */,
|
||||
|
@ -5514,6 +5526,8 @@
|
|||
B52EE8D7121359A600CFBDAB /* persist_manager.cpp in Sources */,
|
||||
46F92DBB2174F6A300602C1C /* file_dialog.cpp in Sources */,
|
||||
1234567890ABCDEF12345678 /* file_progress.cpp in Sources */,
|
||||
000000000000000000000005 /* achievements.cpp in Sources */,
|
||||
000000000000000000000006 /* achievements_dialog.cpp in Sources */,
|
||||
46F92F0F2174FEC000602C1C /* standard_colors.cpp in Sources */,
|
||||
46F92E852174F6A400602C1C /* debug.cpp in Sources */,
|
||||
B52EE8D8121359A600CFBDAB /* persist_var.cpp in Sources */,
|
||||
|
@ -6137,6 +6151,8 @@
|
|||
91E3570A1CACC9B200774252 /* playcampaign.cpp in Sources */,
|
||||
46F92DBC2174F6A300602C1C /* file_dialog.cpp in Sources */,
|
||||
1234567890ABCDEF12345679 /* file_progress.cpp in Sources */,
|
||||
000000000000000000000007 /* achievements.cpp in Sources */,
|
||||
000000000000000000000008 /* achievements_dialog.cpp in Sources */,
|
||||
91E3570B1CACC9B200774252 /* singleplayer.cpp in Sources */,
|
||||
4649B88B20288EEF00827CFB /* surface.cpp in Sources */,
|
||||
46F92DB02174F6A300602C1C /* campaign_selection.cpp in Sources */,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
about.cpp
|
||||
achievements.cpp
|
||||
actions/advancement.cpp
|
||||
actions/attack.cpp
|
||||
actions/create.cpp
|
||||
|
@ -172,6 +173,7 @@ gui/dialogs/addon/install_dependencies.cpp
|
|||
gui/dialogs/addon/license_prompt.cpp
|
||||
gui/dialogs/addon/manager.cpp
|
||||
gui/dialogs/addon/uninstall_list.cpp
|
||||
gui/dialogs/achievements_dialog.cpp
|
||||
gui/dialogs/attack_predictions.cpp
|
||||
gui/dialogs/campaign_difficulty.cpp
|
||||
gui/dialogs/campaign_selection.cpp
|
||||
|
|
108
src/achievements.cpp
Normal file
108
src/achievements.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2022
|
||||
by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "achievements.hpp"
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "preferences/general.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
#include "serialization/preprocessor.hpp"
|
||||
|
||||
static lg::log_domain log_config("config");
|
||||
#define ERR_CONFIG LOG_STREAM(err, log_config)
|
||||
|
||||
/**
|
||||
* Reads the mainline achievements.cfg and then all the achievements of each installed add-on.
|
||||
*
|
||||
* This is intentionally handled separately from other WML loading so that:
|
||||
* a) All achievements and their status are able to be displayed on the main menu right after Wesnoth starts and regardless of which add-ons are active.
|
||||
* b) Add-ons can add additional achievements to other content, whether UMC or mainline. For example, a modification that adds more achievements for mainline campaigns.
|
||||
*
|
||||
* NOTE: These are *not* in any way related to Steam achievements!
|
||||
*/
|
||||
achievements::achievements()
|
||||
: achievement_list_()
|
||||
{
|
||||
// mainline
|
||||
try {
|
||||
config cfg = read_achievements_file(game_config::path + "/data/achievements.cfg");
|
||||
process_achievements_file(cfg, "Mainline");
|
||||
} catch(const game::error& e) {
|
||||
ERR_CONFIG << "Error processing mainline achievements, ignoring: " << e.what();
|
||||
}
|
||||
|
||||
// add-ons
|
||||
std::vector<std::string> dirs;
|
||||
filesystem::get_files_in_dir(filesystem::get_addons_dir(), nullptr, &dirs);
|
||||
for(const std::string& dir : dirs) {
|
||||
try {
|
||||
config cfg = read_achievements_file(filesystem::get_addons_dir() + "/" + dir + "/achievements.cfg");
|
||||
process_achievements_file(cfg, dir);
|
||||
} catch(const game::error& e) {
|
||||
ERR_CONFIG << "Error processing add-on " << dir << " achievements, ignoring: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an achievements.cfg file into a config.
|
||||
*
|
||||
* @param path The path to the achievements.cfg file.
|
||||
* @return The config containing all the achievements.
|
||||
*/
|
||||
config achievements::read_achievements_file(const std::string& path)
|
||||
{
|
||||
config cfg;
|
||||
if(filesystem::file_exists(path)) {
|
||||
filesystem::scoped_istream stream = preprocess_file(path);
|
||||
read(cfg, *stream);
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a config object to add new achievements to @a achievement_list_.
|
||||
*
|
||||
* @param cfg The config containing additional achievements.
|
||||
* @param content_source The source of the additional achievements - either mainline or an add-on.
|
||||
*/
|
||||
void achievements::process_achievements_file(const config& cfg, const std::string& content_source)
|
||||
{
|
||||
for(const config& achgrp : cfg.child_range("achievement_group")) {
|
||||
if(achgrp["content_for"].str().empty()) {
|
||||
ERR_CONFIG << content_source + " achievement_group missing content_for attribute:\n" << achgrp.debug();
|
||||
continue;
|
||||
}
|
||||
achievement_list_.emplace_back(achgrp);
|
||||
}
|
||||
}
|
||||
|
||||
achievement_group::achievement_group(const config& cfg)
|
||||
: display_name_(cfg["display_name"].t_str())
|
||||
, content_for_(cfg["content_for"].str())
|
||||
, achievements_()
|
||||
{
|
||||
for(const config& ach : cfg.child_range("achievement")) {
|
||||
std::string id = ach["id"].str();
|
||||
|
||||
if(id.empty()) {
|
||||
ERR_CONFIG << content_for_ + " achievement missing id attribute:\n" << ach.debug();
|
||||
} else {
|
||||
achievements_.emplace_back(ach, preferences::achievement(content_for_, id));
|
||||
}
|
||||
}
|
||||
}
|
110
src/achievements.hpp
Normal file
110
src/achievements.hpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2022
|
||||
by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "tstring.hpp"
|
||||
|
||||
/**
|
||||
* Represents a single achievement and its data.
|
||||
*/
|
||||
struct achievement
|
||||
{
|
||||
/** The ID of the achievement. Must be unique per achievement_group */
|
||||
std::string id_;
|
||||
/** The name of the achievement to show on the UI. */
|
||||
t_string name_;
|
||||
/** The name of the achievement to show on the UI if the achievement is completed. */
|
||||
t_string name_completed_;
|
||||
/** The description of the achievement to show on the UI. */
|
||||
t_string description_;
|
||||
/** The name of the achievement to show on the UI if the achievement is completed. */
|
||||
t_string description_completed_;
|
||||
/** The icon of the achievement to show on the UI. */
|
||||
std::string icon_;
|
||||
/** The icon of the achievement to show on the UI if the achievement is completed. */
|
||||
std::string icon_completed_;
|
||||
/** Whether to show the achievement's actual name and description on the UI before it's been completed. */
|
||||
bool hidden_;
|
||||
/** The hint to display in place of the description if the achievement is hidden and uncompleted */
|
||||
t_string hidden_name_;
|
||||
/** The hint to display in place of the description if the achievement is hidden and uncompleted */
|
||||
t_string hidden_hint_;
|
||||
/** Whether the achievement has been completed. */
|
||||
bool achieved_;
|
||||
|
||||
achievement(const config& cfg, bool achieved)
|
||||
: id_(cfg["id"].str())
|
||||
, name_(cfg["name"].t_str())
|
||||
, name_completed_(cfg["name_completed"].t_str())
|
||||
, description_(cfg["description"].t_str())
|
||||
, description_completed_(cfg["description_completed"].t_str())
|
||||
, icon_(cfg["icon"].str()+"~GS()")
|
||||
, icon_completed_(cfg["icon_completed"].str())
|
||||
, hidden_(cfg["hidden"].to_bool())
|
||||
, hidden_name_(cfg["hidden_name"].t_str())
|
||||
, hidden_hint_(cfg["hidden_hint"].t_str())
|
||||
, achieved_(achieved)
|
||||
{
|
||||
if(name_completed_.empty()) {
|
||||
name_completed_ = name_;
|
||||
}
|
||||
if(description_completed_.empty()) {
|
||||
description_completed_ = description_;
|
||||
}
|
||||
if(icon_completed_.empty()) {
|
||||
icon_completed_ = icon_;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of achievements tied to a particular content. Achievements can be added to any content from any add-on, even if it's entirely unrelated.
|
||||
*/
|
||||
struct achievement_group
|
||||
{
|
||||
/** The name of the content to display on the UI. */
|
||||
t_string display_name_;
|
||||
/** The internal ID used for this content. */
|
||||
std::string content_for_;
|
||||
/** The achievements associated to this content. */
|
||||
std::vector<achievement> achievements_;
|
||||
|
||||
achievement_group(const config& cfg);
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is responsible for reading all available achievements from mainline's and any add-ons' achievements.cfg files for use in achievements_dialog.
|
||||
*/
|
||||
class achievements
|
||||
{
|
||||
public:
|
||||
achievements();
|
||||
std::vector<achievement_group>& get_list()
|
||||
{
|
||||
return achievement_list_;
|
||||
}
|
||||
|
||||
private:
|
||||
config read_achievements_file(const std::string& path);
|
||||
void process_achievements_file(const config& cfg, const std::string& content_source);
|
||||
|
||||
std::vector<achievement_group> achievement_list_;
|
||||
};
|
|
@ -61,6 +61,7 @@ game_config_manager::game_config_manager(const commandline_options& cmdline_opts
|
|||
, old_defines_map_()
|
||||
, paths_manager_()
|
||||
, cache_(game_config::config_cache::instance())
|
||||
, achievements_()
|
||||
{
|
||||
assert(!singleton);
|
||||
singleton = this;
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "commandline_options.hpp"
|
||||
#include "config.hpp"
|
||||
#include "config_cache.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "game_config_view.hpp"
|
||||
#include "terrain/type_data.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
class game_classification;
|
||||
|
@ -42,7 +44,8 @@ public:
|
|||
|
||||
const game_config_view& game_config() const { return game_config_view_; }
|
||||
const preproc_map& old_defines_map() const { return old_defines_map_; }
|
||||
const std::shared_ptr<terrain_type_data> & terrain_types() const { return tdata_; }
|
||||
const std::shared_ptr<terrain_type_data>& terrain_types() const { return tdata_; }
|
||||
std::vector<achievement_group>& get_achievements() { return achievements_.get_list(); }
|
||||
|
||||
bool init_game_config(FORCE_RELOAD_CONFIG force_reload);
|
||||
void reload_changed_game_config();
|
||||
|
@ -84,4 +87,6 @@ private:
|
|||
game_config::config_cache& cache_;
|
||||
|
||||
std::shared_ptr<terrain_type_data> tdata_;
|
||||
|
||||
achievements achievements_;
|
||||
};
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "pathfind/pathfind.hpp"
|
||||
#include "persist_var.hpp"
|
||||
#include "play_controller.hpp"
|
||||
#include "preferences/general.hpp"
|
||||
#include "recall_list_manager.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "random.hpp"
|
||||
|
@ -239,16 +240,16 @@ wml_action::wml_action(const std::string & tag, handler function)
|
|||
*
|
||||
* Generated code looks like this:
|
||||
* \code
|
||||
* void wml_func_foo(...);
|
||||
* static void wml_func_foo(...);
|
||||
* static wml_action wml_action_foo("foo", &wml_func_foo);
|
||||
* void wml_func_foo(...)
|
||||
* static void wml_func_foo(...)
|
||||
* {
|
||||
* // code for foo
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
#define WML_HANDLER_FUNCTION(pname, pei, pcfg) \
|
||||
static void wml_func_##pname(const queued_event &pei, const vconfig &pcfg); \
|
||||
static void wml_func_##pname(const queued_event& pei, const vconfig& pcfg); \
|
||||
static wml_action wml_action_##pname(#pname, &wml_func_##pname); \
|
||||
static void wml_func_##pname(const queued_event& pei, const vconfig& pcfg)
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "game_board.hpp"
|
||||
#include "game_data.hpp"
|
||||
#include "log.hpp"
|
||||
#include "preferences/general.hpp"
|
||||
#include "recall_list_manager.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "scripting/game_lua_kernel.hpp"
|
||||
|
|
125
src/gui/dialogs/achievements_dialog.cpp
Normal file
125
src/gui/dialogs/achievements_dialog.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2022
|
||||
by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#define GETTEXT_DOMAIN "wesnoth-lib"
|
||||
|
||||
#include "gui/dialogs/achievements_dialog.hpp"
|
||||
|
||||
#include "game_config_manager.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "gui/auxiliary/find_widget.hpp"
|
||||
#include "gui/widgets/window.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace gui2::dialogs
|
||||
{
|
||||
|
||||
REGISTER_DIALOG(achievements_dialog)
|
||||
|
||||
achievements_dialog::achievements_dialog()
|
||||
: modal_dialog(window_id())
|
||||
, achieve_()
|
||||
, achievements_box_(nullptr)
|
||||
, content_names_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void achievements_dialog::pre_show(window& win)
|
||||
{
|
||||
std::vector<config> content_list;
|
||||
content_names_ = &find_widget<menu_button>(&win, "selected_achievements_list", false);
|
||||
connect_signal_notify_modified(*content_names_, std::bind(&achievements_dialog::set_achievements_content, this));
|
||||
|
||||
achievements_box_ = find_widget<listbox>(&win, "achievements_list", false, true);
|
||||
|
||||
auto* a = game_config_manager::get();
|
||||
auto b = a->get_achievements();
|
||||
for(const auto& list : b) {
|
||||
// populate all possibilities into the dropdown
|
||||
content_list.emplace_back("label", list.display_name_);
|
||||
|
||||
// only display the achievements for the first dropdown option on first showing the dialog
|
||||
if(content_list.size() == 1) {
|
||||
for(const auto& ach : list.achievements_) {
|
||||
widget_data row;
|
||||
widget_item item;
|
||||
|
||||
item["label"] = !ach.achieved_ ? ach.icon_ : ach.icon_completed_;
|
||||
row.emplace("icon", item);
|
||||
|
||||
if(ach.hidden_ && !ach.achieved_) {
|
||||
item["label"] = ach.hidden_name_;
|
||||
} else if(!ach.achieved_) {
|
||||
item["label"] = ach.name_;
|
||||
} else {
|
||||
item["label"] = "<span color='green'>"+ach.name_completed_+"</span>";
|
||||
}
|
||||
row.emplace("name", item);
|
||||
|
||||
if(ach.hidden_ && !ach.achieved_) {
|
||||
item["label"] = ach.hidden_hint_;
|
||||
} else if(!ach.achieved_) {
|
||||
item["label"] = ach.description_;
|
||||
} else {
|
||||
item["label"] = "<span color='green'>"+ach.description_completed_+"</span>";
|
||||
}
|
||||
row.emplace("description", item);
|
||||
|
||||
achievements_box_->add_row(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(content_list.size() > 0) {
|
||||
content_names_->set_values(content_list);
|
||||
}
|
||||
}
|
||||
|
||||
void achievements_dialog::post_show(window&)
|
||||
{
|
||||
}
|
||||
|
||||
void achievements_dialog::set_achievements_content()
|
||||
{
|
||||
achievements_box_->clear();
|
||||
for(const auto& ach : game_config_manager::get()->get_achievements().at(content_names_->get_value()).achievements_) {
|
||||
widget_data row;
|
||||
widget_item item;
|
||||
|
||||
item["label"] = !ach.achieved_ ? ach.icon_ : ach.icon_completed_;
|
||||
row.emplace("icon", item);
|
||||
|
||||
if(ach.hidden_ && !ach.achieved_) {
|
||||
item["label"] = ach.hidden_name_;
|
||||
} else if(!ach.achieved_) {
|
||||
item["label"] = ach.name_;
|
||||
} else {
|
||||
item["label"] = "<span color='green'>"+ach.name_completed_+"</span>";
|
||||
}
|
||||
row.emplace("name", item);
|
||||
|
||||
if(ach.hidden_ && !ach.achieved_) {
|
||||
item["label"] = ach.hidden_hint_;
|
||||
} else if(!ach.achieved_) {
|
||||
item["label"] = ach.description_;
|
||||
} else {
|
||||
item["label"] = "<span color='green'>"+ach.description_completed_+"</span>";
|
||||
}
|
||||
row.emplace("description", item);
|
||||
|
||||
achievements_box_->add_row(row);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gui2::dialogs
|
59
src/gui/dialogs/achievements_dialog.hpp
Normal file
59
src/gui/dialogs/achievements_dialog.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2022
|
||||
by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "gui/dialogs/modal_dialog.hpp"
|
||||
#include "gui/widgets/listbox.hpp"
|
||||
#include "gui/widgets/menu_button.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace gui2::dialogs
|
||||
{
|
||||
|
||||
/**
|
||||
* @ingroup GUIWindowDefinitionWML
|
||||
*
|
||||
* This shows a dialog displaying achievements.
|
||||
*
|
||||
* Key |Type |Mandatory|Description
|
||||
* --------------------------|-------------|---------|-----------
|
||||
* selected_achievements_list|menu_button |yes |Allows selecting achievements by what content they're for.
|
||||
* name |label |yes |The user displayed name of the achievement.
|
||||
* description |label |yes |The achievement's longer description.
|
||||
* icon |image |yes |An icon to display to the left of the achievement.
|
||||
*/
|
||||
class achievements_dialog : public modal_dialog
|
||||
{
|
||||
public:
|
||||
DEFINE_SIMPLE_EXECUTE_WRAPPER(achievements_dialog)
|
||||
|
||||
achievements_dialog();
|
||||
|
||||
private:
|
||||
achievements achieve_;
|
||||
listbox* achievements_box_;
|
||||
menu_button* content_names_;
|
||||
|
||||
void set_achievements_content();
|
||||
|
||||
virtual const std::string& window_id() const override;
|
||||
|
||||
virtual void pre_show(window& window) override;
|
||||
|
||||
virtual void post_show(window& window) override;
|
||||
};
|
||||
|
||||
} // namespace gui2::dialogs
|
|
@ -26,6 +26,7 @@
|
|||
#include "gui/auxiliary/find_widget.hpp"
|
||||
#include "gui/auxiliary/tips.hpp"
|
||||
#include "gui/core/timer.hpp"
|
||||
#include "gui/dialogs/achievements_dialog.hpp"
|
||||
#include "gui/dialogs/core_selection.hpp"
|
||||
#include "gui/dialogs/debug_clock.hpp"
|
||||
#include "gui/dialogs/game_version_dialog.hpp"
|
||||
|
@ -163,6 +164,9 @@ void title_screen::init_callbacks()
|
|||
register_hotkey(hotkey::TITLE_SCREEN__RELOAD_WML,
|
||||
std::bind(&gui2::window::set_retval, std::ref(*this), RELOAD_GAME_DATA, true));
|
||||
|
||||
register_hotkey(hotkey::HOTKEY_ACHIEVEMENTS,
|
||||
std::bind(&title_screen::show_achievements, this));
|
||||
|
||||
register_hotkey(hotkey::TITLE_SCREEN__TEST,
|
||||
std::bind(&title_screen::hotkey_callback_select_tests, this));
|
||||
|
||||
|
@ -440,6 +444,12 @@ void title_screen::hotkey_callback_select_tests()
|
|||
}
|
||||
}
|
||||
|
||||
void title_screen::show_achievements()
|
||||
{
|
||||
achievements_dialog ach;
|
||||
ach.show();
|
||||
}
|
||||
|
||||
void title_screen::button_callback_multiplayer()
|
||||
{
|
||||
while(true) {
|
||||
|
|
|
@ -108,6 +108,8 @@ private:
|
|||
|
||||
void hotkey_callback_select_tests();
|
||||
|
||||
void show_achievements();
|
||||
|
||||
void button_callback_multiplayer();
|
||||
|
||||
void button_callback_cores();
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "hotkey/command_executor.hpp"
|
||||
#include "hotkey/hotkey_item.hpp"
|
||||
|
||||
#include "gui/dialogs/achievements_dialog.hpp"
|
||||
#include "gui/dialogs/lua_interpreter.hpp"
|
||||
#include "gui/dialogs/message.hpp"
|
||||
#include "gui/dialogs/screenshot_notification.hpp"
|
||||
|
@ -388,6 +389,12 @@ bool command_executor::do_execute_command(const hotkey_command& cmd, int /*inde
|
|||
preferences::toggle_minimap_draw_villages();
|
||||
recalculate_minimap();
|
||||
break;
|
||||
case HOTKEY_ACHIEVEMENTS:
|
||||
{
|
||||
gui2::dialogs::achievements_dialog ach;
|
||||
ach.show();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ constexpr std::array<hotkey_command_temp, HOTKEY_NULL - 1> master_hotkey_list {{
|
|||
{ HOTKEY_ZOOM_OUT, "zoomout", N_("Zoom Out"), false, scope_game | scope_editor, HKCAT_GENERAL, "" },
|
||||
{ HOTKEY_ZOOM_DEFAULT, "zoomdefault", N_("Default Zoom"), false, scope_game | scope_editor, HKCAT_GENERAL, "" },
|
||||
{ HOTKEY_FULLSCREEN, "fullscreen", N_("Toggle Full Screen"), false, scope_game | scope_editor | scope_main, HKCAT_GENERAL, "" },
|
||||
{ HOTKEY_ACHIEVEMENTS, "achievements", N_("Achievements"), false, scope_game | scope_main, HKCAT_GENERAL, "" },
|
||||
{ HOTKEY_SCREENSHOT, "screenshot", N_("Screenshot"), false, scope_game | scope_editor | scope_main, HKCAT_GENERAL, "" },
|
||||
{ HOTKEY_MAP_SCREENSHOT, "mapscreenshot", N_("Map Screenshot"), false, scope_game | scope_editor, HKCAT_GENERAL, "" },
|
||||
{ HOTKEY_ACCELERATED, "accelerated", N_("Toggle Accelerated Speed"), false, scope_game, HKCAT_GENERAL, "" },
|
||||
|
|
|
@ -96,6 +96,7 @@ enum HOTKEY_COMMAND {
|
|||
HOTKEY_AI_FORMULA,
|
||||
HOTKEY_CLEAR_MSG,
|
||||
HOTKEY_LABEL_SETTINGS,
|
||||
HOTKEY_ACHIEVEMENTS,
|
||||
|
||||
// Minimap
|
||||
HOTKEY_MINIMAP_CODING_TERRAIN, HOTKEY_MINIMAP_CODING_UNIT,
|
||||
|
|
|
@ -336,6 +336,7 @@ bool play_controller::hotkey_handler::can_execute_command(const hotkey::hotkey_c
|
|||
case hotkey::HOTKEY_SCROLL_DOWN:
|
||||
case hotkey::HOTKEY_SCROLL_LEFT:
|
||||
case hotkey::HOTKEY_SCROLL_RIGHT:
|
||||
case hotkey::HOTKEY_ACHIEVEMENTS:
|
||||
return true;
|
||||
|
||||
case hotkey::HOTKEY_SURRENDER: {
|
||||
|
|
|
@ -1026,5 +1026,41 @@ void set_addon_manager_saved_order_direction(sort_order::type value)
|
|||
set("addon_manager_saved_order_direction", sort_order::get_string(value));
|
||||
}
|
||||
|
||||
bool achievement(const std::string& content_for, const std::string& id)
|
||||
{
|
||||
for(config& ach : prefs.child_range("achievements"))
|
||||
{
|
||||
if(ach["content_for"].str() == content_for)
|
||||
{
|
||||
std::vector<std::string> ids = utils::split(ach["ids"]);
|
||||
return std::find(ids.begin(), ids.end(), id) != ids.end();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_achievement(const std::string& content_for, const std::string& id)
|
||||
{
|
||||
for(config& ach : prefs.child_range("achievements"))
|
||||
{
|
||||
// if achievements already exist for this content and the achievement has not already been set, add it
|
||||
if(ach["content_for"].str() == content_for)
|
||||
{
|
||||
std::vector<std::string> ids = utils::split(ach["ids"]);
|
||||
if(std::find(ids.begin(), ids.end(), id) == ids.end())
|
||||
{
|
||||
ach["ids"] = ach["ids"].str() + "," + id;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else no achievements have been set for this content yet
|
||||
config ach;
|
||||
ach["content_for"] = content_for;
|
||||
ach["ids"] = id;
|
||||
prefs.add_child("achievements", ach);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace preferences
|
||||
|
|
|
@ -277,4 +277,7 @@ namespace preferences {
|
|||
sort_order::type addon_manager_saved_order_direction();
|
||||
void set_addon_manager_saved_order_direction(sort_order::type value);
|
||||
|
||||
bool achievement(const std::string& content_for, const std::string& id);
|
||||
void set_achievement(const std::string& content_for, const std::string& id);
|
||||
|
||||
} // end namespace preferences
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
#include "pathfind/pathfind.hpp" // for full_cost_map, plain_route, etc
|
||||
#include "pathfind/teleport.hpp" // for get_teleport_locations, etc
|
||||
#include "play_controller.hpp" // for play_controller
|
||||
#include "preferences/general.hpp"
|
||||
#include "recall_list_manager.hpp" // for recall_list_manager
|
||||
#include "replay.hpp" // for get_user_choice, etc
|
||||
#include "reports.hpp" // for register_generator, etc
|
||||
|
@ -3064,6 +3065,102 @@ int game_lua_kernel::intf_play_sound(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an achievement as being completed.
|
||||
* - Arg 1: string - content_for.
|
||||
* - Arg 2: string - id.
|
||||
*/
|
||||
int game_lua_kernel::intf_set_achievement(lua_State *L)
|
||||
{
|
||||
const char *content_for = luaL_checkstring(L, 1);
|
||||
const char *id = luaL_checkstring(L, 2);
|
||||
|
||||
for(achievement_group& group : game_config_manager::get()->get_achievements()) {
|
||||
if(group.content_for_ == content_for) {
|
||||
for(achievement& achieve : group.achievements_) {
|
||||
if(achieve.id_ == id) {
|
||||
// found the achievement - mark it as completed
|
||||
preferences::set_achievement(content_for, id);
|
||||
achieve.achieved_ = true;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// achievement not found - existing achievement group but non-existing achievement id
|
||||
ERR_LUA << "Achievement " << id << " not found for achievement group " << content_for;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// achievement group not found
|
||||
ERR_LUA << "Achievement group " << content_for << " not found";
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an achievement has been completed.
|
||||
* - Arg 1: string - content_for.
|
||||
* - Arg 2: string - id.
|
||||
* - Ret 1: boolean.
|
||||
*/
|
||||
int game_lua_kernel::intf_has_achievement(lua_State *L)
|
||||
{
|
||||
const char *content_for = luaL_checkstring(L, 1);
|
||||
const char *id = luaL_checkstring(L, 2);
|
||||
|
||||
if(resources::controller->is_networked_mp() && synced_context::is_synced()) {
|
||||
ERR_LUA << "Returning false for whether a player has completed an achievement due to being networked multiplayer.";
|
||||
lua_pushboolean(L, false);
|
||||
} else {
|
||||
lua_pushboolean(L, preferences::achievement(content_for, id));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information on a single achievement, or no data if the achievement is not found.
|
||||
* - Arg 1: string - content_for.
|
||||
* - Arg 2: string - id.
|
||||
* - Ret 1: WML table returned by the function.
|
||||
*/
|
||||
int game_lua_kernel::intf_get_achievement(lua_State *L)
|
||||
{
|
||||
const char *content_for = luaL_checkstring(L, 1);
|
||||
const char *id = luaL_checkstring(L, 2);
|
||||
|
||||
config cfg;
|
||||
for(const auto& group : game_config_manager::get()->get_achievements()) {
|
||||
if(group.content_for_ == content_for) {
|
||||
for(const auto& achieve : group.achievements_) {
|
||||
if(achieve.id_ == id) {
|
||||
// found the achievement - return it as a config
|
||||
cfg["id"] = achieve.id_;
|
||||
cfg["name"] = achieve.name_;
|
||||
cfg["name_completed"] = achieve.name_completed_;
|
||||
cfg["description"] = achieve.description_;
|
||||
cfg["description_completed"] = achieve.description_completed_;
|
||||
cfg["icon"] = achieve.icon_;
|
||||
cfg["icon_completed"] = achieve.icon_completed_;
|
||||
cfg["hidden"] = achieve.hidden_;
|
||||
cfg["hidden_name"] = achieve.hidden_name_;
|
||||
cfg["hidden_hint"] = achieve.hidden_hint_;
|
||||
cfg["achieved"] = achieve.achieved_;
|
||||
luaW_pushconfig(L, cfg);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// return empty config - existing achievement group but non-existing achievement id
|
||||
ERR_LUA << "Achievement " << id << " not found for achievement group " << content_for;
|
||||
luaW_pushconfig(L, cfg);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// return empty config - non-existing achievement group
|
||||
ERR_LUA << "Achievement group " << content_for << " not found";
|
||||
luaW_pushconfig(L, cfg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to given tile.
|
||||
* - Arg 1: location.
|
||||
|
@ -4885,6 +4982,20 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
|
|||
lua_setfield(L, -2, "interface");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Create the achievements module
|
||||
cmd_log_ << "Adding achievements module...\n";
|
||||
static luaL_Reg const achievement_callbacks[] {
|
||||
{ "set", &dispatch<&game_lua_kernel::intf_set_achievement> },
|
||||
{ "has", &dispatch<&game_lua_kernel::intf_has_achievement> },
|
||||
{ "get", &dispatch<&game_lua_kernel::intf_get_achievement> },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
lua_getglobal(L, "wesnoth");
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, achievement_callbacks, 0);
|
||||
lua_setfield(L, -2, "achievements");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Create the audio module
|
||||
cmd_log_ << "Adding audio module...\n";
|
||||
static luaL_Reg const audio_callbacks[] {
|
||||
|
|
|
@ -119,6 +119,9 @@ class game_lua_kernel : public lua_kernel_base
|
|||
int intf_heal_unit(lua_State *L);
|
||||
int intf_message(lua_State *L);
|
||||
int intf_play_sound(lua_State *L);
|
||||
int intf_set_achievement(lua_State *L);
|
||||
int intf_has_achievement(lua_State *L);
|
||||
int intf_get_achievement(lua_State *L);
|
||||
int intf_set_floating_label(lua_State* L, bool spawn);
|
||||
int intf_remove_floating_label(lua_State* L);
|
||||
int intf_move_floating_label(lua_State* L);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "filesystem.hpp"
|
||||
#include "formula/debugger.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_config_manager.hpp"
|
||||
#include "game_config_view.hpp"
|
||||
#include "game_display.hpp"
|
||||
#include "game_events/manager.hpp"
|
||||
|
@ -37,6 +38,7 @@
|
|||
#include "gui/dialogs/addon/install_dependencies.hpp"
|
||||
#include "gui/dialogs/addon/license_prompt.hpp"
|
||||
#include "gui/dialogs/addon/manager.hpp"
|
||||
#include "gui/dialogs/achievements_dialog.hpp"
|
||||
#include "gui/dialogs/attack_predictions.hpp"
|
||||
#include "gui/dialogs/campaign_difficulty.hpp"
|
||||
#include "gui/dialogs/campaign_selection.hpp"
|
||||
|
@ -131,9 +133,12 @@ using namespace gui2::dialogs;
|
|||
|
||||
struct test_gui2_fixture {
|
||||
test_gui2_fixture()
|
||||
: config_manager()
|
||||
, dummy_args({"wesnoth", "--noaddons"})
|
||||
{
|
||||
/** The main config, which contains the entire WML tree. */
|
||||
game_config_view game_config_view_ = game_config_view::wrap(main_config);
|
||||
config_manager.reset(new game_config_manager(dummy_args));
|
||||
|
||||
game_config::config_cache& cache = game_config::config_cache::instance();
|
||||
|
||||
|
@ -152,6 +157,8 @@ struct test_gui2_fixture {
|
|||
}
|
||||
static config main_config;
|
||||
static const std::string widgets_file;
|
||||
std::unique_ptr<game_config_manager> config_manager;
|
||||
std::vector<std::string> dummy_args;
|
||||
};
|
||||
config test_gui2_fixture::main_config;
|
||||
const std::string test_gui2_fixture::widgets_file = "widgets_tested.log";
|
||||
|
@ -589,6 +596,10 @@ BOOST_AUTO_TEST_CASE(modal_dialog_test_wml_message_double)
|
|||
{
|
||||
test<wml_message_double>();
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(modal_dialog_test_achievements_dialog)
|
||||
{
|
||||
test<achievements_dialog>();
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(modeless_dialog_test_debug_clock)
|
||||
{
|
||||
test_popup<debug_clock>();
|
||||
|
|
|
@ -33,6 +33,7 @@ validate() {
|
|||
|
||||
validate_core() { validate "$1" ./wesnoth --validate data/_main.cfg; }
|
||||
validate_misc() { validate "$1" ./wesnoth --data-dir=. --validate=data/_main.cfg --preprocess-defines="$2"; }
|
||||
validate_achievements() { validate "Achievements" ./wesnoth --data-dir=. --validate=data/achievements.cfg --use-schema=data/schema/achievements.cfg; }
|
||||
validate_schema() { validate "$1" ./wesnoth --data-dir=. --validate-schema=data/schema/"$2".cfg; }
|
||||
|
||||
validate_campaign() {
|
||||
|
@ -75,11 +76,17 @@ validate_schema "Game Config" "game_config" || RET=1
|
|||
validate_schema "GUI2" "gui" || RET=1
|
||||
validate_schema "Server Pbl" "pbl" || RET=1
|
||||
validate_schema "WML Diff" "diff" || RET=1
|
||||
validate_schema "Achievements schema" "achievements" || RET=1
|
||||
|
||||
validate_core "Core" || RET=1
|
||||
validate_misc "Editor" "EDITOR" || RET=1
|
||||
validate_misc "Multiplayer" "MULTIPLAYER,MULTIPLAYER_A_NEW_LAND_LOAD" || RET=1
|
||||
validate_misc "Test" "TEST" || RET=1
|
||||
|
||||
validate_achievements || RET=1
|
||||
|
||||
validate_misc "Editor" "EDITOR" || RET=1
|
||||
validate_misc "Multiplayer" "MULTIPLAYER,MULTIPLAYER_A_NEW_LAND_LOAD" || RET=1
|
||||
validate_misc "Test" "TEST" || RET=1
|
||||
validate_misc "World_Conquest" "MULTIPLAYER,LOAD_WC2,LOAD_WC2_EVEN_THOUGH_IT_NEEDS_A_NEW_MAINTAINER" || RET=1
|
||||
|
||||
validate_campaign "Dead_Water" "CAMPAIGN_DEAD_WATER" "EASY" "NORMAL" "HARD" "NIGHTMARE" || RET=1
|
||||
validate_campaign "Delfadors_Memoirs" "CAMPAIGN_DELFADORS_MEMOIRS" "EASY" "NORMAL" "HARD" || RET=1
|
||||
validate_campaign "Descent_Into_Darkness" "CAMPAIGN_DESCENT" "EASY" "NORMAL" "HARD" || RET=1
|
||||
|
|
|
@ -202,6 +202,7 @@
|
|||
9 test_store_unit_defense_deprecated
|
||||
0 special_note_from_movetype
|
||||
0 special_note_individual_unit
|
||||
0 has_achievement
|
||||
# Terrain mask tests
|
||||
0 test_terrain_mask_simple_nop
|
||||
0 test_terrain_mask_simple_set
|
||||
|
|
Loading…
Add table
Reference in a new issue