1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Scholar is now configurable object (partial)

This commit is contained in:
Ivan Savenko 2023-10-04 19:32:16 +03:00
parent ca368f606f
commit e10de0594e
10 changed files with 113 additions and 165 deletions

View File

@ -1,7 +1,7 @@
{ {
"scholar" : { "scholar" : {
"index" :81, "index" :81,
"handler" : "scholar", "handler" : "configurable",
"base" : { "base" : {
"sounds" : { "sounds" : {
"visit" : ["GAZEBO"], "visit" : ["GAZEBO"],
@ -9,13 +9,89 @@
} }
}, },
"types" : { "types" : {
"object" : { "scholar" : {
"index" : 0, "index" : 0,
"aiValue" : 1500, "aiValue" : 1500,
"rmg" : { "rmg" : {
"value" : 1500, "value" : 1500,
"rarity" : 100 "rarity" : 100
} },
"compatibilityIdentifiers" : [ "object" ],
"visitMode" : "unlimited",
"blockedVisitable" : true,
"variables" : {
"spell" : {
"gainedSpell" : { // Note: this variable name is used by engine for H3M loading
}
},
"secondarySkill" : {
"gainedSkill" : { // Note: this variable name is used by engine for H3M loading
}
},
"primarySkill" : {
"gainedStat" : { // Note: this variable name is used by engine for H3M loading
}
}
},
"selectMode" : "selectFirst",
"rewards" : [
{
"appearChance" : { "min" : 0, "max" : 33 },
"message" : 115,
"limiter" : {
"canLearnSpell" : [
"@gainedSpell"
]
},
"spells" : [
"@gainedSpell"
]
"removeObject" : true
},
{
"appearChance" : { "min" : 33, "max" : 66 },
"message" : 115,
"limiter" : {
// Hero does not have this skill at expert
"noneOf" : [
{
"secondarySkill" : {
"@gainedSkill" : 3
}
}
],
// And have either free skill slot or this skill
"anyOf" : [
{
"canLearnSkills" : true
},
{
"noneOf" : [
{
"secondarySkill" : {
"@gainedSkill" : 1
}
}
]
}
]
},
"secondary" : {
"@gainedSkill" : 1
},
"removeObject" : true
},
{
// Always present - fallback if hero can't learn secondary / spell
"message" : 115,
"primary" : {
"@gainedStat" : 1
},
"removeObject" : true
}
]
} }
} }
} }

View File

@ -280,8 +280,19 @@ namespace JsonRandom
return ret; return ret;
} }
PrimarySkill loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
{
std::set<PrimarySkill> defaultSkills{
PrimarySkill::ATTACK,
PrimarySkill::DEFENSE,
PrimarySkill::SPELL_POWER,
PrimarySkill::KNOWLEDGE
};
std::set<PrimarySkill> potentialPicks = filterKeys(value, defaultSkills, variables);
return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
}
std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) std::vector<si32> loadPrimaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
{ {
std::vector<si32> ret; std::vector<si32> ret;
if(value.isStruct()) if(value.isStruct())

View File

@ -37,7 +37,8 @@ namespace JsonRandom
DLL_LINKAGE TResources loadResources(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); DLL_LINKAGE TResources loadResources(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);
DLL_LINKAGE TResources loadResource(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); DLL_LINKAGE TResources loadResource(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);
DLL_LINKAGE std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); DLL_LINKAGE PrimarySkill loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);
DLL_LINKAGE std::vector<si32> loadPrimaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);
DLL_LINKAGE SecondarySkill loadSecondary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); DLL_LINKAGE SecondarySkill loadSecondary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);
DLL_LINKAGE std::map<SecondarySkill, si32> loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); DLL_LINKAGE std::map<SecondarySkill, si32> loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);

View File

@ -84,7 +84,6 @@ CObjectClassesHandler::CObjectClassesHandler()
SET_HANDLER("pandora", CGPandoraBox); SET_HANDLER("pandora", CGPandoraBox);
SET_HANDLER("prison", CGHeroInstance); SET_HANDLER("prison", CGHeroInstance);
SET_HANDLER("questGuard", CGQuestGuard); SET_HANDLER("questGuard", CGQuestGuard);
SET_HANDLER("scholar", CGScholar);
SET_HANDLER("seerHut", CGSeerHut); SET_HANDLER("seerHut", CGSeerHut);
SET_HANDLER("sign", CGSignBottle); SET_HANDLER("sign", CGSignBottle);
SET_HANDLER("siren", CGSirens); SET_HANDLER("siren", CGSirens);

View File

@ -874,132 +874,6 @@ void CGSignBottle::serializeJsonOptions(JsonSerializeFormat& handler)
handler.serializeStruct("text", message); handler.serializeStruct("text", message);
} }
void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
{
EBonusType type = bonusType;
int bid = bonusID;
//check if the bonus if applicable, if not - give primary skill (always possible)
int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1
if((type == SECONDARY_SKILL && ((ssl == 3) || (!ssl && !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot)
|| (type == SPELL && !h->canLearnSpell(SpellID(bid).toSpell())))
{
type = PRIM_SKILL;
bid = CRandomGenerator::getDefault().nextInt(GameConstants::PRIMARY_SKILLS - 1);
}
InfoWindow iw;
iw.type = EInfoWindowMode::AUTO;
iw.player = h->getOwner();
iw.text.appendLocalString(EMetaText::ADVOB_TXT,115);
switch (type)
{
case PRIM_SKILL:
cb->changePrimSkill(h,static_cast<PrimarySkill>(bid),+1);
iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, bid, +1, 0);
break;
case SECONDARY_SKILL:
cb->changeSecSkill(h,SecondarySkill(bid),+1);
iw.components.emplace_back(Component::EComponentType::SEC_SKILL, bid, ssl + 1, 0);
break;
case SPELL:
{
std::set<SpellID> hlp;
hlp.insert(SpellID(bid));
cb->changeSpells(h,true,hlp);
iw.components.emplace_back(Component::EComponentType::SPELL, bid, 0, 0);
}
break;
default:
logGlobal->error("Error: wrong bonus type (%d) for Scholar!\n", static_cast<int>(type));
return;
}
cb->showInfoDialog(&iw);
cb->removeObject(this, h->getOwner());
}
void CGScholar::initObj(CRandomGenerator & rand)
{
blockVisit = true;
if(bonusType == RANDOM)
{
bonusType = static_cast<EBonusType>(rand.nextInt(2));
switch(bonusType)
{
case PRIM_SKILL:
bonusID = rand.nextInt(GameConstants::PRIMARY_SKILLS -1);
break;
case SECONDARY_SKILL:
bonusID = rand.nextInt(static_cast<int>(VLC->skillh->size()) - 1);
break;
case SPELL:
std::vector<SpellID> possibilities;
cb->getAllowedSpells (possibilities);
bonusID = *RandomGeneratorUtil::nextItem(possibilities, rand);
break;
}
}
}
void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
{
if(handler.saving)
{
std::string value;
switch(bonusType)
{
case PRIM_SKILL:
value = NPrimarySkill::names[bonusID];
handler.serializeString("rewardPrimSkill", value);
break;
case SECONDARY_SKILL:
value = CSkillHandler::encodeSkill(bonusID);
handler.serializeString("rewardSkill", value);
break;
case SPELL:
value = SpellID::encode(bonusID);
handler.serializeString("rewardSpell", value);
break;
case RANDOM:
break;
}
}
else
{
//TODO: unify
const JsonNode & json = handler.getCurrent();
bonusType = RANDOM;
if(!json["rewardPrimSkill"].String().empty())
{
auto raw = VLC->identifiers()->getIdentifier(ModScope::scopeBuiltin(), "primSkill", json["rewardPrimSkill"].String());
if(raw)
{
bonusType = PRIM_SKILL;
bonusID = raw.value();
}
}
else if(!json["rewardSkill"].String().empty())
{
auto raw = VLC->identifiers()->getIdentifier(ModScope::scopeBuiltin(), "skill", json["rewardSkill"].String());
if(raw)
{
bonusType = SECONDARY_SKILL;
bonusID = raw.value();
}
}
else if(!json["rewardSpell"].String().empty())
{
auto raw = VLC->identifiers()->getIdentifier(ModScope::scopeBuiltin(), "spell", json["rewardSpell"].String());
if(raw)
{
bonusType = SPELL;
bonusID = raw.value();
}
}
}
}
void CGGarrison::onHeroVisit (const CGHeroInstance *h) const void CGGarrison::onHeroVisit (const CGHeroInstance *h) const
{ {
auto relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); auto relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);

View File

@ -57,26 +57,6 @@ protected:
void serializeJsonOptions(JsonSerializeFormat & handler) override; void serializeJsonOptions(JsonSerializeFormat & handler) override;
}; };
class DLL_LINKAGE CGScholar : public CGObjectInstance
{
public:
enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255};
EBonusType bonusType;
ui16 bonusID; //ID of skill/spell
CGScholar() : bonusType(EBonusType::RANDOM),bonusID(0){};
void onHeroVisit(const CGHeroInstance * h) const override;
void initObj(CRandomGenerator & rand) override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CGObjectInstance&>(*this);
h & bonusType;
h & bonusID;
}
protected:
void serializeJsonOptions(JsonSerializeFormat & handler) override;
};
class DLL_LINKAGE CGGarrison : public CArmedInstance class DLL_LINKAGE CGGarrison : public CArmedInstance
{ {
public: public:

View File

@ -1179,11 +1179,21 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
return object; return object;
} }
CGObjectInstance * CMapLoaderH3M::readScholar() CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)
{ {
auto * object = new CGScholar(); enum class ScholarBonusType : uint8_t {
object->bonusType = static_cast<CGScholar::EBonusType>(reader->readUInt8()); PRIM_SKILL = 0,
object->bonusID = reader->readUInt8(); SECONDARY_SKILL = 1,
SPELL = 2,
RANDOM = 255
};
auto * object = readGeneric(position, objectTemplate);
//auto * rewardable = dynamic_cast<CRewardableObject*>(object);
/*uint8_t bonusTypeRaw =*/ reader->readUInt8();
/*auto bonusType = static_cast<ScholarBonusType>(bonusTypeRaw);*/
/*auto bonusID =*/ reader->readUInt8();
reader->skipZero(6); reader->skipZero(6);
return object; return object;
} }
@ -1491,7 +1501,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
case Obj::WITCH_HUT: case Obj::WITCH_HUT:
return readWitchHut(mapPosition, objectTemplate); return readWitchHut(mapPosition, objectTemplate);
case Obj::SCHOLAR: case Obj::SCHOLAR:
return readScholar(); return readScholar(mapPosition, objectTemplate);
case Obj::GARRISON: case Obj::GARRISON:
case Obj::GARRISON2: case Obj::GARRISON2:

View File

@ -166,7 +166,7 @@ private:
CGObjectInstance * readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl); CGObjectInstance * readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
CGObjectInstance * readSign(const int3 & position); CGObjectInstance * readSign(const int3 & position);
CGObjectInstance * readWitchHut(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate); CGObjectInstance * readWitchHut(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
CGObjectInstance * readScholar(); CGObjectInstance * readScholar(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
CGObjectInstance * readGarrison(const int3 & mapPosition); CGObjectInstance * readGarrison(const int3 & mapPosition);
CGObjectInstance * readArtifact(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl); CGObjectInstance * readArtifact(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
CGObjectInstance * readResource(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl); CGObjectInstance * readResource(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);

View File

@ -54,7 +54,6 @@ void registerTypesMapObjects1(Serializer &s)
s.template registerType<CGMonolith, CGSubterraneanGate>(); s.template registerType<CGMonolith, CGSubterraneanGate>();
s.template registerType<CGMonolith, CGWhirlpool>(); s.template registerType<CGMonolith, CGWhirlpool>();
s.template registerType<CGObjectInstance, CGSignBottle>(); s.template registerType<CGObjectInstance, CGSignBottle>();
s.template registerType<CGObjectInstance, CGScholar>();
s.template registerType<CGObjectInstance, CGKeys>(); s.template registerType<CGObjectInstance, CGKeys>();
s.template registerType<CGKeys, CGKeymasterTent>(); s.template registerType<CGKeys, CGKeymasterTent>();
s.template registerType<CGKeys, CGBorderGuard>(); s.template registerType<IQuestObject, CGBorderGuard>(); s.template registerType<CGKeys, CGBorderGuard>(); s.template registerType<IQuestObject, CGBorderGuard>();
@ -131,7 +130,6 @@ void registerTypesMapObjectTypes(Serializer &s)
REGISTER_GENERIC_HANDLER(CGPandoraBox); REGISTER_GENERIC_HANDLER(CGPandoraBox);
REGISTER_GENERIC_HANDLER(CGQuestGuard); REGISTER_GENERIC_HANDLER(CGQuestGuard);
REGISTER_GENERIC_HANDLER(CGResource); REGISTER_GENERIC_HANDLER(CGResource);
REGISTER_GENERIC_HANDLER(CGScholar);
REGISTER_GENERIC_HANDLER(CGSeerHut); REGISTER_GENERIC_HANDLER(CGSeerHut);
REGISTER_GENERIC_HANDLER(CGShipyard); REGISTER_GENERIC_HANDLER(CGShipyard);
REGISTER_GENERIC_HANDLER(CGSignBottle); REGISTER_GENERIC_HANDLER(CGSignBottle);

View File

@ -115,7 +115,7 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan
limiter.resources = JsonRandom::loadResources(source["resources"], rng, variables); limiter.resources = JsonRandom::loadResources(source["resources"], rng, variables);
limiter.primary = JsonRandom::loadPrimary(source["primary"], rng, variables); limiter.primary = JsonRandom::loadPrimaries(source["primary"], rng, variables);
limiter.secondary = JsonRandom::loadSecondaries(source["secondary"], rng, variables); limiter.secondary = JsonRandom::loadSecondaries(source["secondary"], rng, variables);
limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng, variables); limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng, variables);
limiter.spells = JsonRandom::loadSpells(source["spells"], rng, variables); limiter.spells = JsonRandom::loadSpells(source["spells"], rng, variables);
@ -150,7 +150,7 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRand
reward.removeObject = source["removeObject"].Bool(); reward.removeObject = source["removeObject"].Bool();
reward.bonuses = JsonRandom::loadBonuses(source["bonuses"]); reward.bonuses = JsonRandom::loadBonuses(source["bonuses"]);
reward.primary = JsonRandom::loadPrimary(source["primary"], rng, variables); reward.primary = JsonRandom::loadPrimaries(source["primary"], rng, variables);
reward.secondary = JsonRandom::loadSecondaries(source["secondary"], rng, variables); reward.secondary = JsonRandom::loadSecondaries(source["secondary"], rng, variables);
reward.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng, variables); reward.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng, variables);
@ -221,9 +221,8 @@ void Rewardable::Info::configureVariables(Rewardable::Configuration & object, CR
// if (category.first == "resource") // if (category.first == "resource")
// value = JsonRandom::loadResource(input, rng, object.variables.values).getNum(); // value = JsonRandom::loadResource(input, rng, object.variables.values).getNum();
// TODO if (category.first == "primarySkill")
// if (category.first == "primarySkill") value = static_cast<int>(JsonRandom::loadPrimary(input, rng, object.variables.values));
// value = JsonRandom::loadCreature(input, rng, object.variables.values).getNum();
if (category.first == "secondarySkill") if (category.first == "secondarySkill")
value = JsonRandom::loadSecondary(input, rng, object.variables.values).getNum(); value = JsonRandom::loadSecondary(input, rng, object.variables.values).getNum();