1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "sbrick_sheet.h"
24 #include "nel/georges/u_form_elm.h"
25 #include "nel/misc/common.h"
26 #include "nel/misc/algo.h"
27 #include "item_sheet.h"
28 #include "game_share/brick_flags.h"
31 using namespace NLMISC
;
32 using namespace NLGEORGES
;
35 // ***************************************************************************
36 // Easy Macro to translate .typ enum
37 #define TRANSLATE_ENUM( _Var_, _unknown_, _func_, _key_) \
39 if( !root.getValueByName(val, _key_)) \
40 debug("Key '" _key_ "' not found."); \
41 else if( (_Var_ = _func_(val)) == _unknown_ ) \
42 debug(#_Var_ " Unknown: " + val);
44 // Same but no error if the result of enum is _unknown_
45 #define TRANSLATE_ENUM_NODB( _Var_, _unknown_, _func_, _key_) \
47 if( !root.getValueByName(val, _key_)) \
48 debug( toString("Key '%s' not found.", _key_) ); \
53 // Easy macro to translate value from georges
54 #define TRANSLATE_VAL( _Var_, _key_ ) \
55 if(!root.getValueByName(_Var_, _key_)) \
56 debug( string("key '") + string(_key_) + string("' not found.") );
59 // ***************************************************************************
60 static void strRemoveChar(string
&str
, char c
)
62 str
.erase( remove(str
.begin(), str
.end(), c
), str
.end() );
65 // ***************************************************************************
66 void CSBrickSheet::build (const NLGEORGES::UFormElm
&root
)
71 // read the array of skills
73 TRANSLATE_VAL(skillUseStr
, "Basics.Skill");
74 while(strFindReplace(skillUseStr
, ":", " "));
75 std::vector
<string
> listSkill
;
76 splitString(skillUseStr
," ",listSkill
);
77 // build the req skill array
79 UsedSkills
.reserve(listSkill
.size());
80 for(i
=0;i
<listSkill
.size();i
++)
82 SKILLS::ESkills skill
= SKILLS::toSkill(listSkill
[i
]);
83 // Yoyo: patch to read auto generated bricks
84 if(skill
==SKILLS::unknown
)
86 skill
= (SKILLS::ESkills
)SKILLS::toSkill("S" + listSkill
[i
]);
88 // keep only whats work
89 if(skill
!=SKILLS::unknown
)
91 UsedSkills
.push_back(skill
);
94 // if empty, fill at least with unknown
95 if(UsedSkills
.empty())
96 UsedSkills
.push_back(SKILLS::unknown
);
99 root
.getValueByName (sTmp
, "Basics.FamilyId" );
100 BrickFamily
= BRICK_FAMILIES::toSBrickFamily (sTmp
);
101 if(BrickFamily
==BRICK_FAMILIES::Unknown
)
102 nlwarning("CSBrickSheet:build: BrickFamily Unknown '%s'.", sTmp
.c_str());
104 // Yoyo: patch to read auto generated bricks
105 if(BrickFamily==BRICK_FAMILIES::Unknown)
107 string sheetName= Id.toString();
108 std::string::size_type end= sheetName.find(".sbrick")-2;
109 BrickFamily = BRICK_FAMILIES::toSBrickFamily ( NLMISC::toUpperAscii(sheetName.substr(0,end)) );
110 if(BrickFamily==BRICK_FAMILIES::Unknown)
111 nlwarning("Unknown Family for SBrick: %s", sheetName.c_str());
114 root
.getValueByName (IndexInFamily
, "Basics.IndexInFamily" );
115 root
.getValueByName (Level
, "Basics.Level" );
118 root
.getValueByName (Icon
, "Client.Icon" );
119 Icon
= toLowerAscii(Icon
);
120 IdIcon
= ClientSheetsStrings
.add(Icon
);
123 root
.getValueByName (IconBack
, "Client.IconBack" );
124 IconBack
= toLowerAscii(IconBack
);
125 IdIconBack
= ClientSheetsStrings
.add(IconBack
);
128 root
.getValueByName (IconOver
, "Client.IconOver" );
129 IconOver
= toLowerAscii(IconOver
);
130 IdIconOver
= ClientSheetsStrings
.add(IconOver
);
133 root
.getValueByName (IconOver2
, "Client.IconOver2" );
134 IconOver2
= toLowerAscii(IconOver2
);
135 IdIconOver2
= ClientSheetsStrings
.add(IconOver2
);
137 root
.getValueByName (IconColor
, "Client.IconColor" );
138 root
.getValueByName (IconBackColor
, "Client.IconBackColor");
139 root
.getValueByName (IconOverColor
, "Client.IconOverColor");
140 root
.getValueByName (IconOver2Color
, "Client.IconOver2Color");
141 root
.getValueByName (SabrinaCost
, "Basics.SabrinaCost" );
142 root
.getValueByName (SabrinaRelativeCost
, "Basics.SabrinaRelativeValue" );
144 // mandatory families
145 MandatoryFamilies
.clear();
147 for(i
=0;i
<SBRICK_MAX_MANDATORY
;i
++)
149 sprintf(tmp
, "Mandatory.f%d", i
);
150 root
.getValueByName (sTmp
, tmp
);
153 BRICK_FAMILIES::TBrickFamily bf
= BRICK_FAMILIES::toSBrickFamily(sTmp
);
154 if(bf
!= BRICK_FAMILIES::Unknown
)
155 MandatoryFamilies
.push_back( bf
);
157 nlwarning("Unknown Mandatory family %s",sTmp
.c_str());
162 OptionalFamilies
.clear();
163 for(i
=0;i
<SBRICK_MAX_OPTIONAL
;i
++)
165 sprintf(tmp
, "Optional.f%d", i
);
166 root
.getValueByName (sTmp
, tmp
);
169 BRICK_FAMILIES::TBrickFamily bf
= BRICK_FAMILIES::toSBrickFamily(sTmp
);
170 if(bf
!= BRICK_FAMILIES::Unknown
)
171 OptionalFamilies
.push_back( bf
);
173 nlwarning("Unknown optional family %s",sTmp
.c_str());
177 // Parameter families
178 ParameterFamilies
.clear();
179 for(i
=0;i
<SBRICK_MAX_PARAMETER
;i
++)
181 sprintf(tmp
, "Parameter.f%d", i
);
182 root
.getValueByName (sTmp
, tmp
);
185 BRICK_FAMILIES::TBrickFamily bf
= BRICK_FAMILIES::toSBrickFamily(sTmp
);
186 if(bf
!= BRICK_FAMILIES::Unknown
)
187 ParameterFamilies
.push_back( bf
);
189 nlwarning("Unknown Parameter family %s",sTmp
.c_str());
194 CreditFamilies
.clear();
195 for(i
=0;i
<SBRICK_MAX_CREDIT
;i
++)
197 sprintf(tmp
, "Credit.f%d", i
);
198 root
.getValueByName (sTmp
, tmp
);
199 BRICK_FAMILIES::TBrickFamily bf
= BRICK_FAMILIES::toSBrickFamily(sTmp
);
200 if(bf
!= BRICK_FAMILIES::Unknown
)
201 CreditFamilies
.push_back( bf
);
205 root
.getValueByName (ForbiddenDef
, "Basics.ForbiddenDef" );
206 IdForbiddenDef
= ClientSheetsStrings
.add(ForbiddenDef
);
208 string ForbiddenExclude
;
209 root
.getValueByName (ForbiddenExclude
, "Basics.ForbiddenExclude" );
210 IdForbiddenExclude
= ClientSheetsStrings
.add(ForbiddenExclude
);
214 for(i
=0;i
<MaxProperties
;i
++)
217 root
.getValueByName(val
, toString("Basics.Property %d", i
).c_str() );
218 if(!val
.empty() && val
!="NULL")
222 Properties
.push_back(prop
);
227 // The FaberPlan are stored in Mandatory only, but the tool filter is in root
228 if( isFaber() && (isMandatory() || isRoot()) )
232 FaberPlan
.ItemPartMps
.clear();
233 FaberPlan
.FormulaMps
.clear();
236 TRANSLATE_VAL(val
, "faber.Create.Crafted Item");
237 FaberPlan
.ItemBuilt
.buildSheetId(val
);
240 TRANSLATE_ENUM ( FaberPlan
.ToolType
, TOOL_TYPE::Unknown
, TOOL_TYPE::toToolType
, "faber.Tool type");
242 // Get NB item built (for ammo)
243 TRANSLATE_VAL( FaberPlan
.NbItemBuilt
, "faber.Create.Nb built items");
245 // MPs. Try all MP1 .. 5 slots. Stop when not valid
246 for (uint k
=0; k
< MAX_FABER_REQ_MP
; ++k
)
248 sint32 mpQuantity
= 0;
249 root
.getValueByName(mpQuantity
, toString("faber.Create.Quantity %d", k
+1).c_str() );
250 // if the req quantity is not 0
253 CFaberPlan::CItemPartMP mpVal
;
254 mpVal
.Quantity
= mpQuantity
;
255 // No error if unknown: filter not used (all MPs match)
256 TRANSLATE_ENUM_NODB( mpVal
.FaberTypeFilter
, RM_FABER_TYPE::Unknown
, RM_FABER_TYPE::toFaberType
,
257 toString("faber.Create.MP %d", k
+1).c_str() );
260 FaberPlan
.ItemPartMps
.push_back(mpVal
);
262 // else subsequents MP slots are not used
267 // Formula MPs. Try all MP1 .. 5 slots. Stop when not valid
268 for (uint k
=0; k
< MAX_FABER_REQ_MP
; ++k
)
270 sint32 mpQuantity
= 0;
271 root
.getValueByName(mpQuantity
, toString("faber.Create.Quantity formula %d", k
+1).c_str() );
272 // if the req quantity is not 0
275 CFaberPlan::CFormulaMP mpVal
;
276 mpVal
.Quantity
= mpQuantity
;
277 // No error if unknown: filter not used (all MPs match)
278 TRANSLATE_VAL(val
, toString("faber.Create.MP formula %d", k
+1).c_str());
279 mpVal
.ItemRequired
.buildSheetId(val
);
282 FaberPlan
.FormulaMps
.push_back(mpVal
);
284 // else subsequents MP slots are not used
291 // read minmax range/cast time when guigui ready
292 TRANSLATE_VAL(MinCastTime
, "Basics.MinCastTime");
293 TRANSLATE_VAL(MaxCastTime
, "Basics.MaxCastTime");
294 TRANSLATE_VAL(MinRange
, "Basics.MinRange");
295 TRANSLATE_VAL(MaxRange
, "Basics.MaxRange");
298 TRANSLATE_VAL(SPCost
, "Basics.SPCost");
301 TRANSLATE_VAL(AvoidCyclic
, "Basics.AvoidCyclic");
303 // Read UsableWithEmptyHands
304 TRANSLATE_VAL(UsableWithEmptyHands
, "Basics.UsableWithEmptyHands");
306 // Read Action Nature
308 TRANSLATE_ENUM(ActionNature
, ACTNATURE::UNKNOWN
, ACTNATURE::toActionNature
, "Basics.Action Nature");
310 // Read CivRestriction
311 TRANSLATE_ENUM(CivRestriction
, EGSPD::CPeople::Common
, EGSPD::CPeople::fromString
, "Basics.CivRestriction");
314 // **** parse properties, to precompute the Bricks Flags.
315 BrickRequiredFlags
= 0;
316 for(i
=0;i
<Properties
.size();i
++)
318 string text
= NLMISC::toLowerAscii(Properties
[i
].Text
);
320 // If the property is an opening property
321 const string openingProp
[]= { "opening_1:", "opening_2:", "opening_3:" };
322 // or if the property is a general brick flag
323 const string neededBrickFlag
= "needed_brick_flag";
324 const uint nOpeningProp
= sizeof(openingProp
) / sizeof(openingProp
[0]);
325 for(uint j
=0;j
<nOpeningProp
;j
++)
327 const string
&prop
= openingProp
[j
];
328 // if found this property
329 if( text
.compare(0, prop
.size(), prop
)==0 ||
330 (j
==0 && text
.compare(0, neededBrickFlag
.size(), neededBrickFlag
)==0)
333 // get all the opening requirement
334 vector
<string
> strList
;
336 splitString(text
, ":", strList
);
337 for(uint k
=1;k
<strList
.size();k
++)
339 // remove empty space, and convert to a bit
340 strRemoveChar(strList
[k
], ' ');
341 BRICK_FLAGS::TBrickFlag evFlag
;
342 evFlag
= BRICK_FLAGS::toBrickFlag(strList
[k
]);
343 if(evFlag
!=BRICK_FLAGS::UnknownFlag
)
344 BrickRequiredFlags
|= (uint64(1)<<(uint
)evFlag
);
351 // **** parse required skills
352 // parse the sheet str
354 TRANSLATE_VAL(skillReqStr
, "Basics.LearnRequiresOneOfSkills");
355 while(strFindReplace(skillReqStr
, ":", " "));
357 splitString(skillReqStr
," ",listSkill
);
358 // build the req skill array
359 RequiredSkills
.clear();
360 RequiredSkills
.reserve(listSkill
.size()/2);
361 for(i
=0;i
<listSkill
.size()/2;i
++)
364 sv
.Skill
= SKILLS::toSkill(listSkill
[i
*2]);
365 fromString(listSkill
[i
*2+1], sv
.Value
);
366 // keep only whats work
367 if(sv
.Skill
!=SKILLS::unknown
)
369 RequiredSkills
.push_back(sv
);
373 // **** parse required bricks
374 // parse the sheet str
376 TRANSLATE_VAL(brickReqStr
, "Basics.LearnRequiresBricks");
377 while(strFindReplace(brickReqStr
, ":", " "));
378 std::vector
<string
> listBrick
;
380 splitString(brickReqStr
," ",listBrick
);
381 // build the req skill array
382 RequiredBricks
.clear();
383 RequiredBricks
.reserve(listBrick
.size());
384 for(i
=0;i
<listBrick
.size();i
++)
387 string str
= toLowerAscii(listBrick
[i
]);
388 if(str
.find(".sbrick")==string::npos
)
390 sheetId
.buildSheetId(str
);
391 if(sheetId
!=CSheetId::Unknown
)
393 RequiredBricks
.push_back(sheetId
);
399 root
.getValueByName (faction
, "Basics.Faction" );
400 FactionIndex
= CStaticFames::getInstance().getFactionIndex( faction
);
403 TRANSLATE_VAL(MinFameValue
, "Basics.Minimum fame");
405 // **** Magic only: try to get a ResistType against this brick
406 for(i
=0;i
<Properties
.size();i
++)
408 string text
= toLowerAscii(Properties
[i
].Text
);
410 // *** If the property is a DamageType
411 const string dmgTypeProp
= "ma_dmg_type:";
412 if( text
.compare(0, dmgTypeProp
.size(), dmgTypeProp
)==0 )
414 // extract the dmg type
415 string dtStr
= text
.substr(dmgTypeProp
.size());
416 strRemoveChar(dtStr
, ' ');
417 DMGTYPE::EDamageType dt
= DMGTYPE::stringToDamageType(dtStr
);
418 if(dt
!=DMGTYPE::UNDEFINED
)
420 // Convert to a resist type
421 RESISTANCE_TYPE::TResistanceType rt
= DMGTYPE::getAssociatedResistanceType(dt
);
422 if(rt
!=RESISTANCE_TYPE::None
)
427 // *** Do the same if the property is an effect family (affliction spells)
428 const string effectFamProp
= "ma_effect:";
429 if( text
.compare(0, effectFamProp
.size(), effectFamProp
)==0 )
431 // extract the effect family
432 string efStr
= text
.substr(effectFamProp
.size());
433 strRemoveChar(efStr
, ' ');
434 EFFECT_FAMILIES::TEffectFamily ef
= EFFECT_FAMILIES::toEffectFamily(efStr
);
435 if(ef
!=EFFECT_FAMILIES::Unknown
)
437 // Convert to a resist type
438 RESISTANCE_TYPE::TResistanceType rt
= EFFECT_FAMILIES::getAssociatedResistanceType(ef
);
439 if(rt
!=RESISTANCE_TYPE::None
)
449 // ***************************************************************************
450 bool CSBrickSheet::isRoot() const
452 return BRICK_FAMILIES::isRootFamily(BrickFamily
);
455 // ***************************************************************************
456 bool CSBrickSheet::isCredit() const
458 return BRICK_FAMILIES::isCreditFamily(BrickFamily
);
461 // ***************************************************************************
462 bool CSBrickSheet::isMandatory() const
464 return BRICK_FAMILIES::isMandatoryFamily(BrickFamily
);
467 // ***************************************************************************
468 bool CSBrickSheet::isOptional() const
470 return BRICK_FAMILIES::isOptionFamily(BrickFamily
);
473 // ***************************************************************************
474 bool CSBrickSheet::isParameter() const
476 return BRICK_FAMILIES::isParameterFamily(BrickFamily
);
479 // ***************************************************************************
480 bool CSBrickSheet::isCombat() const
482 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::COMBAT
;
485 // ***************************************************************************
486 bool CSBrickSheet::isMagic() const
488 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::MAGIC
;
491 // ***************************************************************************
492 bool CSBrickSheet::isFaber() const
494 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::FABER
;
497 // ***************************************************************************
498 bool CSBrickSheet::isHarvest() const
500 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::HARVEST
;
503 // ***************************************************************************
504 bool CSBrickSheet::isForageProspection() const
506 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::FORAGE_PROSPECTION
;
509 // ***************************************************************************
510 bool CSBrickSheet::isForageExtraction() const
512 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::FORAGE_EXTRACTION
;
515 // ***************************************************************************
516 bool CSBrickSheet::isSpecialPower() const
518 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::SPECIAL_POWER
;
521 // ***************************************************************************
522 bool CSBrickSheet::isProcEnchantment() const
524 return BRICK_FAMILIES::brickType(BrickFamily
) == BRICK_TYPE::PROC_ENCHANTEMENT
;
527 // ***************************************************************************
528 bool CSBrickSheet::mustDisplayLevel() const
530 // always but if root or mandatory, special interface, or if level is 0
531 // NB: Yoyo Hack. special interface with indexInFamily==63 means "want to display the level"
532 return !( isMandatory() ||
534 (BrickFamily
>= BRICK_FAMILIES::BeginInterface
&& BrickFamily
<= BRICK_FAMILIES::EndInterface
&& IndexInFamily
!=63) ||