diff --git a/CMakeLists.txt b/CMakeLists.txt index 524f575bb..b6f750b99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -457,6 +457,8 @@ set(OC_CLONK_SOURCES src/player/C4PlayerList.h src/player/C4RankSystem.cpp src/player/C4RankSystem.h + src/player/C4ScenarioParameters.cpp + src/player/C4ScenarioParameters.h src/script/C4AulDebug.cpp src/script/C4AulDebug.h thirdparty/timsort/sort.h diff --git a/docs/sdk/scenario/ParameterDefs.xml b/docs/sdk/scenario/ParameterDefs.xml index 14337622e..dcd4a6dd3 100644 --- a/docs/sdk/scenario/ParameterDefs.xml +++ b/docs/sdk/scenario/ParameterDefs.xml @@ -47,6 +47,12 @@ Value of default option that is chosen if the user does not select anything. 0 + + Achievement + String + If set to an achievement ID, this parameter is hidden in the selection dialogue and only used to track player achievements for this scenario. See section below. + + Each parameter of type Enumeration should be followed by subordinate section [Options], which contains several subordinate sections [Option]. Subordinate sections need to be indented further than their parent section. @@ -79,6 +85,11 @@ + Achievements + Scenario achievements are special parameter definitions for which the values are stored per player and per scenario. These parameters cannot be set maually, but can only be gained in game using the GainScenarioAchievement function. Each achievement that has been gained is represented by a small image beside the scenario title in the scenario selection dialogue. + Achievements need to be defined as parameter definition with the Achievement value set in the [ParameterDef] section. The value corresponds to the icon file name formatted as Achv*.png, where * is replaced by the achievement name. The icon may either be placed in the scenario or - if multiple scenarios share the same icon - in the global Graphics.ocg file. Icons are square with the height determining the dimensions. + By using achievement values higher than one, multiple stages of an achievement may be defined e.g. for finishing a scenario on different difficulties. The achievement value denotes the 1-based index in the icon file with multiple icons arranged from left to right. + If several players are activated while the scenario selection dialogue is opened, the player with the highest achievement value is considered for each achievement. Example Example ParameterDef.txt file for a difficulty setting in a scenario that controls the amount of meteorites: [ParameterDef] @@ -95,19 +106,40 @@ Default=15 [Option] Name=$Hard$ Description=$DescHard$ - Value=100 + Value=100 + +[ParameterDef] +ID=Done +Achievement=Done + [Options] + + [Option] + Description=$AchvEasy$ + Value=1 + + [Option] + Description=$AchvHard$ + Value=3 The names and descriptions are localized strings which can be set for English in a StringTblUS.txt: Difficulty=Difficulty Easy=Easy Hard=Hard DescDifficulty=Conrols the amount of meteorites DescEasy=Very few meteorites -DescHard=Lots of meteorites - Finally, the Script.c file of the scenario should contain code to evaluate the setting: +DescHard=Lots of meteorites +AchvEasy=Finished on easy. +AchvHard=Finished on hard. + Finally, the Script.c file of the scenario should contain code to evaluate the setting and give an achievement accordingly: func Initialize() { // Meteorite amount depending on difficulty setting Meteor->SetChance(SCENPAR_Difficulty); +} + +func OnGoalsFulfilled() +{ + GainScenarioAchievement("Done", BoundBy(SCENPAR_Difficulty, 1, 3)); // 1 for easy, 3 for hard + return false; } Sven22014-09 diff --git a/docs/sdk/script/fn/GainScenarioAchievement.xml b/docs/sdk/script/fn/GainScenarioAchievement.xml new file mode 100644 index 000000000..72c076940 --- /dev/null +++ b/docs/sdk/script/fn/GainScenarioAchievement.xml @@ -0,0 +1,44 @@ + + + + + + GainScenarioAchievement + Player + 5.4 OC + + bool + + + string + id + ID of achievement to gain. Must be defined as a custom scenario parameter for the scenario. + + + value + int + Value to set for this achievement. Defaults to 1. + + + + player + int + Value to set for this achievement. If nil or NO_OWNER, the achievement is gained for all player currently in the game. + + + + string + scenario_name + Can be set to the filename of another scenario to gain an achievement for that scenario. Scenario paths contain all .oc* paths, e.g. "Missions.ocf\Raid.ocs". Defaults to the filename of the current scenario or its origin. + + + + + Gain a scenario achievement. Gained achievements are represented as small symbols beside the scenario name in the selection screen. + Make sure the gain the achievement before players are saved, i.e. before the game is evaluated. Otherwise, gained achievements are not stored. + GainMissionAccess + Scenario parameter definitions + + Sven22014-09 + diff --git a/planet/Graphics.ocg/AchvDone.png b/planet/Graphics.ocg/AchvDone.png new file mode 100644 index 000000000..d5640ee81 Binary files /dev/null and b/planet/Graphics.ocg/AchvDone.png differ diff --git a/planet/Tests.ocf/Parameters.ocs/ParameterDefs.txt b/planet/Tests.ocf/Parameters.ocs/ParameterDefs.txt index 1deecc7b4..6b2ddb189 100644 --- a/planet/Tests.ocf/Parameters.ocs/ParameterDefs.txt +++ b/planet/Tests.ocf/Parameters.ocs/ParameterDefs.txt @@ -14,3 +14,16 @@ Default=1 Name=$Hard$ Description=$DescHard$ Value=99 + +[ParameterDef] +ID=Done +Achievement=Done + [Options] + + [Option] + Description=$AchvEasy$ + Value=1 + + [Option] + Description=$AchvHard$ + Value=3 diff --git a/planet/Tests.ocf/Parameters.ocs/Script.c b/planet/Tests.ocf/Parameters.ocs/Script.c index 8447f0e6e..5470dc75a 100644 --- a/planet/Tests.ocf/Parameters.ocs/Script.c +++ b/planet/Tests.ocf/Parameters.ocs/Script.c @@ -1,4 +1,26 @@ +static num_to_collect; + func Initialize() { Log("Difficulty = %d", SCENPAR_Difficulty); + num_to_collect = SCENPAR_Difficulty; + for (var i=0; iName("Option")) { pComp->NameEnd(); pComp->excNotFound("Option"); } - pComp->Value(mkNamingAdapt(mkParAdapt(Name, StdCompiler::RCT_All), "Name", StdCopyStrBuf())); - pComp->Value(mkNamingAdapt(mkParAdapt(Description, StdCompiler::RCT_All), "Description", StdCopyStrBuf())); - pComp->Value(mkNamingAdapt( Value, "Value", 0)); - pComp->NameEnd(); -} - -const C4ScenarioParameterDef::Option *C4ScenarioParameterDef::GetOptionByValue(int32_t val) const -{ - // search option by value - for (auto i = Options.cbegin(); i != Options.cend(); ++i) - if (i->Value == val) - return &*i; - return NULL; -} - -const C4ScenarioParameterDef::Option *C4ScenarioParameterDef::GetOptionByIndex(size_t idx) const -{ - if (idx >= Options.size()) return NULL; - return &Options[idx]; -} - -void C4ScenarioParameterDef::CompileFunc(StdCompiler *pComp) -{ - if (!pComp->Name("ParameterDef")) { pComp->NameEnd(); pComp->excNotFound("ParameterDef"); } - pComp->Value(mkNamingAdapt(mkParAdapt(Name, StdCompiler::RCT_All), "Name", StdCopyStrBuf())); - pComp->Value(mkNamingAdapt(mkParAdapt(Description, StdCompiler::RCT_All), "Description", StdCopyStrBuf())); - pComp->Value(mkNamingAdapt(mkParAdapt(ID, StdCompiler::RCT_Idtf), "ID", StdCopyStrBuf())); - StdEnumEntry ParTypeEntries[] = - { - { "Enumeration", SPDT_Enum }, - { NULL, SPDT_Enum } - }; - pComp->Value(mkNamingAdapt(mkEnumAdaptT(Type, ParTypeEntries), "Type", SPDT_Enum)); - pComp->Value(mkNamingAdapt(Default, "Default", 0)); - pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(Options, StdCompiler::SEP_NONE), "Options")); - pComp->NameEnd(); -} - -void C4ScenarioParameterDefs::CompileFunc(StdCompiler *pComp) -{ - pComp->Value(mkSTLContainerAdapt(Parameters, StdCompiler::SEP_NONE)); -} - -const C4ScenarioParameterDef *C4ScenarioParameterDefs::GetParameterDefByIndex(size_t idx) const -{ - if (idx >= Parameters.size()) return NULL; - return &Parameters[idx]; -} - -bool C4ScenarioParameterDefs::Load(C4Group &hGroup, C4LangStringTable *pLang) -{ - // Load buffer, localize and parse - StdStrBuf Buf; - if (!hGroup.LoadEntryString(C4CFN_ScenarioParameterDefs,&Buf)) return false; - if (pLang) pLang->ReplaceStrings(Buf); - if (!CompileFromBuf_LogWarn(*this, Buf, C4CFN_ScenarioCore)) - { return false; } - return true; -} - -void C4ScenarioParameterDefs::RegisterScriptConstants(const C4ScenarioParameters &values) -{ - // register constants for all parameters in script engine - for (auto i = Parameters.cbegin(); i != Parameters.cend(); ++i) - { - StdStrBuf constant_name; - constant_name.Format("SCENPAR_%s", i->GetID()); - int32_t constant_value = values.GetValueByID(i->GetID(), i->GetDefault()); - ::ScriptEngine.RegisterGlobalConstant(constant_name.getData(), C4VInt(constant_value)); - } -} - -int32_t C4ScenarioParameters::GetValueByID(const char *id, int32_t default_value) const -{ - // return map value if name is in map. Otherwise, return default value. - auto i = Parameters.find(StdStrBuf(id)); - if (i != Parameters.end()) return i->second; else return default_value; -} - -void C4ScenarioParameters::SetValue(const char *id, int32_t value) -{ - // just update map - Parameters[StdStrBuf(id)] = value; -} - -void C4ScenarioParameters::CompileFunc(StdCompiler *pComp) -{ - // Unfortunately, StdCompiler cannot save std::map yet - if (pComp->isCompiler()) - { - Parameters.clear(); - if (pComp->hasNaming()) - { - // load from INI - size_t name_count = pComp->NameCount(); - for (size_t i=0; iGetNameByIndex(0); // always get name index 0, because names are removed after values have been extracted for them - StdCopyStrBuf sName(name); - if (!name) continue; - pComp->Value(mkNamingAdapt(v, sName.getData(), 0)); - Parameters[sName] = v; - } - } - else - { - // load from binary - int32_t name_count=0; - pComp->Value(name_count); - for (int32_t i=0; iValue(name); - pComp->Value(v); - Parameters[name] = v; - } - } - } - else - { - if (pComp->hasNaming()) - { - // save to INI - for (auto i = Parameters.begin(); i != Parameters.end(); ++i) - pComp->Value(mkNamingAdapt(i->second, i->first.getData())); - } - else - { - // save to binary - int32_t name_count=Parameters.size(); - pComp->Value(name_count); - for (auto i = Parameters.begin(); i != Parameters.end(); ++i) - { - pComp->Value(const_cast(i->first)); - pComp->Value(i->second); - } - } - } -} - // *** C4GameParameters diff --git a/src/control/C4GameParameters.h b/src/control/C4GameParameters.h index 9abb0cc71..32f08361a 100644 --- a/src/control/C4GameParameters.h +++ b/src/control/C4GameParameters.h @@ -22,6 +22,7 @@ #include "C4PlayerInfo.h" #include "C4LangStringTable.h" #include "C4Teams.h" +#include "C4InfoCore.h" class C4GameRes { @@ -95,40 +96,6 @@ protected: void LoadFoldersWithLocalDefs(const char *szPath); }; -// Definitions of custom parameters that can be set before scenario start -class C4ScenarioParameterDefs -{ - std::vector Parameters; - -public: - C4ScenarioParameterDefs() {} - ~C4ScenarioParameterDefs() {} - - void Clear() { Parameters.clear(); } - - const C4ScenarioParameterDef *GetParameterDefByIndex(size_t idx) const; - - bool Load(C4Group &hGroup, class C4LangStringTable *pLang); - void CompileFunc(StdCompiler *pComp); - - void RegisterScriptConstants(const class C4ScenarioParameters &values); // register constants for all parameters in script engine -}; - -// Parameter values that correspond to settings offered in C4ScenarioParameterDefs -class C4ScenarioParameters -{ - std::map Parameters; - -public: - C4ScenarioParameters() {} - ~C4ScenarioParameters() {} - - int32_t GetValueByID(const char *id, int32_t default_value) const; - void SetValue(const char *id, int32_t value); - - void CompileFunc(StdCompiler *pComp); -}; - class C4GameParameters { public: diff --git a/src/gamescript/C4GameScript.cpp b/src/gamescript/C4GameScript.cpp index 7ed3dd769..d5c37bbf0 100644 --- a/src/gamescript/C4GameScript.cpp +++ b/src/gamescript/C4GameScript.cpp @@ -2468,6 +2468,29 @@ static int32_t FnGetStartupPlayerCount(C4PropList * _this) return ::Game.StartupPlayerCount; } +static bool FnGainScenarioAchievement(C4PropList * _this, C4String *achievement_name, Nillable avalue, Nillable player, C4String *for_scenario) +{ + // safety + if (!achievement_name || !achievement_name->GetData().getLength()) return false; + // default parameter + long value = avalue.IsNil() ? 1 : avalue; + // gain achievement + bool result = true; + if (!player.IsNil() && player != NO_OWNER) + { + C4Player *plr = ::Players.Get(player); + if (!plr) return false; + result = plr->GainScenarioAchievement(achievement_name->GetCStr(), value, for_scenario ? for_scenario->GetCStr() : NULL); + } + else + { + for (C4Player *plr = ::Players.First; plr; plr = plr->Next) + if (!plr->GainScenarioAchievement(achievement_name->GetCStr(), value, for_scenario ? for_scenario->GetCStr() : NULL)) + result = false; + } + return true; +} + extern C4ScriptConstDef C4ScriptGameConstMap[]; extern C4ScriptFnDef C4ScriptGameFnMap[]; @@ -2634,6 +2657,7 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine) AddFunc(pEngine, "GetStartupPlayerCount", FnGetStartupPlayerCount); AddFunc(pEngine, "PlayerObjectCommand", FnPlayerObjectCommand); AddFunc(pEngine, "EditCursor", FnEditCursor); + AddFunc(pEngine, "GainScenarioAchievement", FnGainScenarioAchievement); F(GetPlrKnowledge); F(GetComponent); diff --git a/src/graphics/C4GraphicsResource.cpp b/src/graphics/C4GraphicsResource.cpp index 9e2514030..53456158a 100644 --- a/src/graphics/C4GraphicsResource.cpp +++ b/src/graphics/C4GraphicsResource.cpp @@ -29,6 +29,8 @@ #include +/* C4GraphicsResource */ + C4GraphicsResource::C4GraphicsResource(): idSfcCaption(0), idSfcButton(0), idSfcButtonD(0), idSfcScroll(0), idSfcContext(0), CaptionFont(FontCaption), TitleFont(FontTitle), TextFont(FontRegular), MiniFont(FontTiny), TooltipFont(FontTooltip) @@ -138,6 +140,8 @@ void C4GraphicsResource::Clear() // unhook deflist from font FontRegular.SetCustomImages(NULL); + Achievements.Clear(); + // closing the group set will also close the graphics.ocg // this is just for games that failed to init // normally, this is done after successful init anyway @@ -251,6 +255,9 @@ bool C4GraphicsResource::Init() if (!LoadFile(fctGamepad, "Gamepad", Files, 80)) return false; if (!LoadFile(fctBuild, "Build", Files)) return false; + // achievements + if (!Achievements.Init(Files)) return false; + // create ColorByOwner overlay surfaces if (fctCrew.idSourceGroup != fctCrewClr.idSourceGroup) { diff --git a/src/graphics/C4GraphicsResource.h b/src/graphics/C4GraphicsResource.h index 00af3cd84..fa488a5d4 100644 --- a/src/graphics/C4GraphicsResource.h +++ b/src/graphics/C4GraphicsResource.h @@ -25,6 +25,7 @@ #include #include #include +#include class C4GraphicsResource { @@ -101,6 +102,9 @@ public: CStdFont FontCaption; // used for title bars CStdFont FontTitle; // huge font for titles CStdFont FontTooltip; // normal, non-shadowed font (same as BookFont) + + // achievement graphics + C4AchievementGraphics Achievements; public: int32_t GetColorIndex(int32_t iColor, bool fLast=false); CStdFont &GetFontByHeight(int32_t iHgt, float *pfZoom=NULL); // get optimal font for given control size diff --git a/src/gui/C4GameLobby.cpp b/src/gui/C4GameLobby.cpp index 42ec5957e..137347b0d 100644 --- a/src/gui/C4GameLobby.cpp +++ b/src/gui/C4GameLobby.cpp @@ -495,7 +495,7 @@ namespace C4GameLobby case PID_SetScenarioParameter: // set a scenario parameter value { GETPKT(C4PacketSetScenarioParameter, Pkt); - ::Game.Parameters.ScenarioParameters.SetValue(Pkt.GetID(), Pkt.GetValue()); + ::Game.Parameters.ScenarioParameters.SetValue(Pkt.GetID(), Pkt.GetValue(), false); // reflect updated value immediately on clients if (pRightTab->GetActiveSheetIndex() == SheetIdx_Options) if (pOptionsList) pOptionsList->Update(); } diff --git a/src/gui/C4GameOptions.cpp b/src/gui/C4GameOptions.cpp index 6ad628f72..fe32f4a58 100644 --- a/src/gui/C4GameOptions.cpp +++ b/src/gui/C4GameOptions.cpp @@ -103,7 +103,7 @@ void C4GameOptionsList::OptionScenarioParameter::DoDropdownSelChange(int32_t idN C4GameLobby::C4PacketSetScenarioParameter pck(ParameterDef->GetID(), idNewSelection); ::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_SetScenarioParameter, pck)); // also process on host - ::Game.Parameters.ScenarioParameters.SetValue(ParameterDef->GetID(), idNewSelection); + ::Game.Parameters.ScenarioParameters.SetValue(ParameterDef->GetID(), idNewSelection, false); } void C4GameOptionsList::OptionScenarioParameter::Update() @@ -295,7 +295,8 @@ void C4GameOptionsList::InitOptions() // create options for custom scenario parameters size_t idx = 0; const C4ScenarioParameterDef *def; while (def = ::Game.ScenarioParameterDefs.GetParameterDefByIndex(idx++)) - new OptionScenarioParameter(this, def); + if (!def->IsAchievement()) // achievements are displayed in scenario selection. no need to repeat them here + new OptionScenarioParameter(this, def); // creates option selection components new OptionControlMode(this); new OptionControlRate(this); diff --git a/src/gui/C4StartupScenSelDlg.cpp b/src/gui/C4StartupScenSelDlg.cpp index f6faefabd..21c60dd2f 100644 --- a/src/gui/C4StartupScenSelDlg.cpp +++ b/src/gui/C4StartupScenSelDlg.cpp @@ -405,7 +405,7 @@ void C4MapFolderData::ResetSelection() // ------------------------------------ // Entry -C4ScenarioListLoader::Entry::Entry(Folder *pParent) : pNext(NULL), pParent(pParent), fBaseLoaded(false), fExLoaded(false) +C4ScenarioListLoader::Entry::Entry(class C4ScenarioListLoader *pLoader, Folder *pParent) : pLoader(pLoader), pNext(NULL), pParent(pParent), fBaseLoaded(false), fExLoaded(false) { // ctor: Put into parent tree node if (pParent) @@ -550,20 +550,20 @@ bool DirContainsScenarios(const char *szDir) return !!szChildFilename; } -C4ScenarioListLoader::Entry *C4ScenarioListLoader::Entry::CreateEntryForFile(const StdStrBuf &sFilename, Folder *pParent) +C4ScenarioListLoader::Entry *C4ScenarioListLoader::Entry::CreateEntryForFile(const StdStrBuf &sFilename, C4ScenarioListLoader *pLoader, Folder *pParent) { // determine entry type by file type const char *szFilename = sFilename.getData(); if (!szFilename || !*szFilename) return NULL; - if (WildcardMatch(C4CFN_ScenarioFiles, sFilename.getData())) return new Scenario(pParent); - if (WildcardMatch(C4CFN_FolderFiles, sFilename.getData())) return new SubFolder(pParent); + if (WildcardMatch(C4CFN_ScenarioFiles, sFilename.getData())) return new Scenario(pLoader, pParent); + if (WildcardMatch(C4CFN_FolderFiles, sFilename.getData())) return new SubFolder(pLoader, pParent); // regular, open folder (C4Group-packed folders without extensions are not regarded, because they could contain anything!) const char *szExt = GetExtension(szFilename); if ((!szExt || !*szExt) && DirectoryExists(sFilename.getData())) { // open folders only if they contain a scenario or folder if (DirContainsScenarios(szFilename)) - return new RegularFolder(pParent); + return new RegularFolder(pLoader, pParent); } // type not recognized return NULL; @@ -670,6 +670,39 @@ bool C4ScenarioListLoader::Scenario::LoadCustomPre(C4Group &rGrp) if (!rGrp.LoadEntryString(C4CFN_ScenarioCore, &sFileContents)) return false; if (!CompileFromBuf_LogWarn(mkParAdapt(C4S, false), sFileContents, (rGrp.GetFullName() + DirSep C4CFN_ScenarioCore).getData())) return false; + // ...and localized parameter definitions. needed for achievements and parameter input boxes. + C4LangStringTable ScenarioLangStringTable; + C4Language::LoadComponentHost(&ScenarioLangStringTable, rGrp, C4CFN_ScriptStringTbl, Config.General.LanguageEx); + ParameterDefs.Load(rGrp, &ScenarioLangStringTable); + // achievement images + const C4ScenarioParameterDef *def; size_t idx=0, aidx=0; nAchievements = 0; + while (def = ParameterDefs.GetParameterDefByIndex(idx++)) + if (def->IsAchievement()) + { + int32_t val = pLoader->GetAchievements().GetValueByID(C4ScenarioParameters::AddFilename2ID(rGrp.GetFullName().getData(), def->GetID()).getData(), def->GetDefault()); + if (val) + { + // player has this achievement - find graphics for it + const char *achievement_gfx = def->GetAchievement(); + StdStrBuf sAchievementFilename(C4CFN_Achievements); + sAchievementFilename.Replace("*", achievement_gfx); + if (!fctAchievements[aidx].Load(rGrp, sAchievementFilename.getData(), C4FCT_Height, C4FCT_Full)) + { + const C4FacetSurface *fct = ::GraphicsResource.Achievements.FindByName(achievement_gfx); + if (!fct) continue; // achievement graphics not found :( + fctAchievements[aidx].Set((const C4Facet &)*fct); + } + // section by achievement index (1-based, since zero means no achievement) + if (val>1) fctAchievements[aidx].Set(fctAchievements[aidx].GetSection(val-1)); + // description for this achievement is taken from option + const C4ScenarioParameterDef::Option *opt = def->GetOptionByValue(val); + if (opt) sAchievementDescriptions[aidx] = opt->Description; + // keep track of achievement count + ++aidx; ++nAchievements; + if (aidx == C4StartupScenSel_MaxAchievements) break; + ; + } + } return true; } @@ -694,6 +727,15 @@ bool C4ScenarioListLoader::Scenario::LoadCustom(C4Group &rGrp, bool fNameLoaded, return true; } +bool C4ScenarioListLoader::Scenario::GetAchievement(int32_t idx, C4Facet *out_facet, const char **out_description) +{ + // return true and fill output parameters if player got the indexed achievement + if (idx < 0 || idx >= nAchievements) return false; + *out_facet = fctAchievements[idx]; + *out_description = sAchievementDescriptions[idx].getData(); + return true; +} + bool C4ScenarioListLoader::Scenario::Start() { // gogo! @@ -978,7 +1020,7 @@ bool C4ScenarioListLoader::SubFolder::DoLoadContents(C4ScenarioListLoader *pLoad sChildFilename.Ref(ChildFilename); // okay; create this item //LogF("SubFolder \"%s\" loading \"%s\"", (const char *) sFilename, (const char *) sChildFilename); - Entry *pNewEntry = Entry::CreateEntryForFile(sChildFilename, this); + Entry *pNewEntry = Entry::CreateEntryForFile(sChildFilename, pLoader, this); if (pNewEntry) { // ...and load it @@ -1071,7 +1113,7 @@ bool C4ScenarioListLoader::RegularFolder::DoLoadContents(C4ScenarioListLoader *p if (names.find(szChildFilename) != names.end()) continue; names.insert(szChildFilename); // filename okay; create this item - Entry *pNewEntry = Entry::CreateEntryForFile(sChildFilename, this); + Entry *pNewEntry = Entry::CreateEntryForFile(sChildFilename, pLoader, this); if (pNewEntry) { // ...and load it @@ -1098,7 +1140,7 @@ void C4ScenarioListLoader::RegularFolder::Merge(const char *szPath) // ------------------------------------ // C4ScenarioListLoader -C4ScenarioListLoader::C4ScenarioListLoader() : pRootFolder(NULL), pCurrFolder(NULL), +C4ScenarioListLoader::C4ScenarioListLoader(const C4ScenarioParameters &Achievements) : Achievements(Achievements), pRootFolder(NULL), pCurrFolder(NULL), iLoading(0), iProgress(0), iMaxProgress(0), fAbortThis(false), fAbortPrevious(false) { } @@ -1158,7 +1200,7 @@ bool C4ScenarioListLoader::Load(const StdStrBuf &sRootFolder) // (unthreaded) loading of all entries in root folder if (!BeginActivity(true)) return false; if (pRootFolder) { delete pRootFolder; pRootFolder = NULL; } - pCurrFolder = pRootFolder = new RegularFolder(NULL); + pCurrFolder = pRootFolder = new RegularFolder(this, NULL); // Load regular game data if no explicit path specified if(!sRootFolder.getData()) for(C4Reloc::iterator iter = Reloc.begin(); iter != Reloc.end(); ++iter) @@ -1238,10 +1280,26 @@ C4StartupScenSelDlg::ScenListItem::ScenListItem(C4GUI::ListBox *pForListBox, C4S pIcon = new C4GUI::Picture(C4Rect(0, 0, iHeight, iHeight), true); pIcon->SetFacet(pScenListEntry->GetIconFacet()); pNameLabel = new C4GUI::Label(pScenListEntry->GetName().getData(), iHeight + IconLabelSpacing, IconLabelSpacing, ALeft, fEnabled ? ClrScenarioItem : ClrScenarioItemDisabled, &rUseFont, false, false); + // achievement components + for (int32_t i=0; iGetAchievement(i, &fct, &desc)) + { + ppAchievements[i] = new C4GUI::Picture(C4Rect(iHeight * (i+2), 0, iHeight, iHeight), true); // position will be adjusted later + ppAchievements[i]->SetFacet(fct); + ppAchievements[i]->SetToolTip(desc); + } + else + { + ppAchievements[i] = NULL; + } + } // calc own bounds - use icon bounds only, because only the height is used when the item is added SetBounds(pIcon->GetBounds()); // add components AddElement(pIcon); AddElement(pNameLabel); + for (int32_t i=0; iGetName().getData()); // add to listbox (will get resized horizontally and moved) - zero indent; no tree structure in this dialog @@ -1258,9 +1316,12 @@ void C4StartupScenSelDlg::ScenListItem::UpdateOwnPos() // parent for client rect typedef C4GUI::Window ParentClass; ParentClass::UpdateOwnPos(); - // reposition items + // reposition achievement items C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing); - // nothing to reposition for now... + for (int32_t i=0; iSetBounds(caBounds.GetFromRight(caBounds.GetHeight())); + } } void C4StartupScenSelDlg::ScenListItem::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) @@ -1453,9 +1514,11 @@ void C4StartupScenSelDlg::DrawElement(C4TargetFacet &cgo) void C4StartupScenSelDlg::OnShown() { C4StartupDlg::OnShown(); + // Collect achievements of all activated players + UpdateAchievements(); // init file list fIsInitialLoading = true; - if (!pScenLoader) pScenLoader = new C4ScenarioListLoader(); + if (!pScenLoader) pScenLoader = new C4ScenarioListLoader(Achievements); pScenLoader->Load(StdStrBuf()); //Config.General.ExePath)); UpdateList(); UpdateSelection(); @@ -1840,4 +1903,21 @@ void C4StartupScenSelDlg::AbortRenaming() if (pRenameEdit) pRenameEdit->Abort(); } -// NICHT: 9, 7.2.2, 113-114 +void C4StartupScenSelDlg::UpdateAchievements() +{ + // Extract all achievements from activated player files and merge them + Achievements.Clear(); + char PlayerFilename[_MAX_FNAME+1]; + C4Group PlayerGrp; + for (int i = 0; SCopySegment(Config.General.Participants, i, PlayerFilename, ';', _MAX_FNAME, true); i++) + { + const char *szPlayerFilename = Config.AtUserDataPath(PlayerFilename); + if (!FileExists(szPlayerFilename)) continue; + if (!PlayerGrp.Open(szPlayerFilename)) continue; + C4PlayerInfoCore nfo; + if (!nfo.Load(PlayerGrp)) continue; + Achievements.Merge(nfo.Achievements); + } +} + +// NICHT: 9, 7.2.2, 113-114, 8a diff --git a/src/gui/C4StartupScenSelDlg.h b/src/gui/C4StartupScenSelDlg.h index 66868e066..9c3f4ef67 100644 --- a/src/gui/C4StartupScenSelDlg.h +++ b/src/gui/C4StartupScenSelDlg.h @@ -21,6 +21,7 @@ #include "C4Startup.h" #include "C4Scenario.h" #include "C4Folder.h" +#include "C4ScenarioParameters.h" #include #include @@ -36,7 +37,8 @@ const int32_t C4StartupScenSel_DefaultIcon_Scenario = 14, C4StartupScenSel_TitlePictureWdt = 200, C4StartupScenSel_TitlePictureHgt = 150, C4StartupScenSel_TitlePicturePadding = 10, - C4StartupScenSel_TitleOverlayMargin = 10; // number of pixels to each side of title overlay picture + C4StartupScenSel_TitleOverlayMargin = 10, // number of pixels to each side of title overlay picture + C4StartupScenSel_MaxAchievements = 3; // maximum number of achievements shown next to entry // a list of loaded scenarios class C4ScenarioListLoader @@ -47,6 +49,7 @@ public: class Entry { protected: + class C4ScenarioListLoader *pLoader; Entry *pNext; class Folder *pParent; @@ -61,7 +64,7 @@ public: int iFolderIndex; public: - Entry(class Folder *pParent); + Entry(class C4ScenarioListLoader *pLoader, class Folder *pParent); virtual ~Entry(); // dtor: unlink from tree bool Load(C4Group *pFromGrp, const StdStrBuf *psFilename, bool fLoadEx); // load as child if pFromGrp, else directly from filename @@ -83,8 +86,9 @@ public: Entry *GetNext() const { return pNext; } class Folder *GetParent() const { return pParent; } virtual StdStrBuf GetTypeName() = 0; + virtual bool GetAchievement(int32_t idx, C4Facet *out_facet, const char **out_description) { return false; } // return true and fill output parameters if player got the indexed achievement - static Entry *CreateEntryForFile(const StdStrBuf &sFilename, Folder *pParent); // create correct entry type based on file extension + static Entry *CreateEntryForFile(const StdStrBuf &sFilename, C4ScenarioListLoader *pLoader, Folder *pParent); // create correct entry type based on file extension virtual bool CanOpen(StdStrBuf &sError) { return true; } // whether item can be started/opened (e.g. mission access, unregistered) virtual bool IsGrayed() { return false; } // additional condition for graying out - notice unreg folders are grayed but can still be opened @@ -104,11 +108,15 @@ public: { private: C4Scenario C4S; + C4ScenarioParameterDefs ParameterDefs; + C4FacetSurface fctAchievements[C4StartupScenSel_MaxAchievements]; + StdCopyStrBuf sAchievementDescriptions[C4StartupScenSel_MaxAchievements]; + int32_t nAchievements; bool fNoMissionAccess; int32_t iMinPlrCount; public: - Scenario(class Folder *pParent) : Entry(pParent), fNoMissionAccess(false), iMinPlrCount(0) {} + Scenario(class C4ScenarioListLoader *pLoader, class Folder *pParent) : Entry(pLoader, pParent), fNoMissionAccess(false), iMinPlrCount(0), nAchievements(0) {} virtual ~Scenario() {} virtual bool LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded); // do fallbacks for title and icon; check whether scenario is valid @@ -121,6 +129,7 @@ public: virtual StdStrBuf GetOpenText(); // get open button text virtual StdStrBuf GetOpenTooltip(); const C4Scenario &GetC4S() const { return C4S; } // get scenario core + virtual bool GetAchievement(int32_t idx, C4Facet *out_facet, const char **out_description); // return true and fill output parameters if player got the indexed achievement virtual StdStrBuf GetTypeName() { return StdCopyStrBuf(LoadResStr("IDS_TYPE_SCENARIO"), true); } @@ -140,7 +149,7 @@ public: friend class Entry; public: - Folder(Folder *pParent) : Entry(pParent), fContentsLoaded(false), pFirst(NULL), pMapData(NULL) {} + Folder(class C4ScenarioListLoader *pLoader, Folder *pParent) : Entry(pLoader, pParent), fContentsLoaded(false), pFirst(NULL), pMapData(NULL) {} virtual ~Folder(); virtual bool LoadCustomPre(C4Group &rGrp); // load folder core @@ -171,7 +180,7 @@ public: class SubFolder : public Folder { public: - SubFolder(Folder *pParent) : Folder(pParent) {} + SubFolder(class C4ScenarioListLoader *pLoader, Folder *pParent) : Folder(pLoader, pParent) {} virtual ~SubFolder() {} virtual const char *GetDefaultExtension() { return "ocf"; } @@ -187,7 +196,7 @@ public: class RegularFolder : public Folder { public: - RegularFolder(Folder *pParent) : Folder(pParent) {} + RegularFolder(class C4ScenarioListLoader *pLoader, Folder *pParent) : Folder(pLoader, pParent) {} virtual ~RegularFolder(); virtual StdStrBuf GetTypeName() { return StdCopyStrBuf(LoadResStr("IDS_TYPE_DIRECTORY"), true); } @@ -207,9 +216,10 @@ private: Folder *pCurrFolder; // scenario list in working directory int32_t iLoading, iProgress, iMaxProgress; bool fAbortThis, fAbortPrevious; // activity state + const C4ScenarioParameters &Achievements; public: - C4ScenarioListLoader(); + C4ScenarioListLoader(const C4ScenarioParameters &Achievements); ~C4ScenarioListLoader(); private: @@ -235,6 +245,8 @@ public: int32_t GetProgress() const { return iProgress; } int32_t GetMaxProgress() const { return iMaxProgress; } int32_t GetProgressPercent() const { return iProgress * 100 / Max(iMaxProgress, 1); } + + const C4ScenarioParameters &GetAchievements() const { return Achievements; } }; @@ -354,6 +366,7 @@ public: // subcomponents C4GUI::Picture *pIcon; // item icon C4GUI::Label *pNameLabel; // item caption + C4GUI::Picture *ppAchievements[C4StartupScenSel_MaxAchievements]; // achievement icons C4ScenarioListLoader::Entry *pScenListEntry; // associated, loaded item info public: @@ -409,6 +422,9 @@ private: C4GUI::RenameEdit *pRenameEdit; + // achievements of all activated players + C4ScenarioParameters Achievements; + public: static C4StartupScenSelDlg *pInstance; // singleton @@ -447,6 +463,7 @@ private: C4ScenarioListLoader::Entry *GetSelectedEntry(); void SetOpenButtonDefaultText(); void FocusScenList(); + void UpdateAchievements(); public: bool StartScenario(C4ScenarioListLoader::Scenario *pStartScen); diff --git a/src/landscape/C4Scenario.h b/src/landscape/C4Scenario.h index e488e72e8..39981a27e 100644 --- a/src/landscape/C4Scenario.h +++ b/src/landscape/C4Scenario.h @@ -221,50 +221,6 @@ public: void CompileFunc(StdCompiler *pComp); }; -// Definition for a custom setting for the scenario -class C4ScenarioParameterDef -{ -public: - // what kind of parameter? - enum ParameterType - { - SPDT_Enum, // only one type so far - }; - - // single option for an enum type parameter - struct Option - { - int32_t Value; // integer value that will be assigned to the script constant - StdCopyStrBuf Name; // localized name - StdCopyStrBuf Description; // localized description. to be displayed as hover text for this option. - - void CompileFunc(StdCompiler *pComp); - }; - -private: - StdCopyStrBuf Name; // localized name - StdCopyStrBuf Description; // localized description. to be displayed as hover text for this parameter input control - StdCopyStrBuf ID; // Identifier for value storage and script access - ParameterType Type; // Type of parameter. Always enum. - - std::vector