Răsfoiți Sursa

Scholar is now configurable object (partial)

Ivan Savenko 2 ani în urmă
părinte
comite
e10de0594e

+ 79 - 3
config/objects/rewardableScholar.json

@@ -1,7 +1,7 @@
 {
 	"scholar" : {
 		"index" :81,
-		"handler" : "scholar",
+		"handler" : "configurable",
 		"base" : {
 			"sounds" : {
 				"visit" : ["GAZEBO"],
@@ -9,13 +9,89 @@
 			}
 		},
 		"types" : {
-			"object" : {
+			"scholar" : {
 				"index" : 0,
 				"aiValue" : 1500,
 				"rmg" : {
 					"value"		: 1500,
 					"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
+					}
+				]
 			}
 		}
 	}

+ 12 - 1
lib/JsonRandom.cpp

@@ -280,8 +280,19 @@ namespace JsonRandom
 		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;
 		if(value.isStruct())

+ 2 - 1
lib/JsonRandom.h

@@ -37,7 +37,8 @@ namespace JsonRandom
 
 	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 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 std::map<SecondarySkill, si32> loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables);
 

+ 0 - 1
lib/mapObjectConstructors/CObjectClassesHandler.cpp

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

+ 0 - 126
lib/mapObjects/MiscObjects.cpp

@@ -874,132 +874,6 @@ void CGSignBottle::serializeJsonOptions(JsonSerializeFormat& handler)
 	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
 {
 	auto relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);

+ 0 - 20
lib/mapObjects/MiscObjects.h

@@ -57,26 +57,6 @@ protected:
 	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
 {
 public:

+ 15 - 5
lib/mapping/MapFormatH3M.cpp

@@ -1179,11 +1179,21 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
 	return object;
 }
 
-CGObjectInstance * CMapLoaderH3M::readScholar()
+CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)
 {
-	auto * object = new CGScholar();
-	object->bonusType = static_cast<CGScholar::EBonusType>(reader->readUInt8());
-	object->bonusID = reader->readUInt8();
+	enum class ScholarBonusType : uint8_t {
+		PRIM_SKILL = 0,
+		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);
 	return object;
 }
@@ -1491,7 +1501,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
 		case Obj::WITCH_HUT:
 			return readWitchHut(mapPosition, objectTemplate);
 		case Obj::SCHOLAR:
-			return readScholar();
+			return readScholar(mapPosition, objectTemplate);
 
 		case Obj::GARRISON:
 		case Obj::GARRISON2:

+ 1 - 1
lib/mapping/MapFormatH3M.h

@@ -166,7 +166,7 @@ private:
 	CGObjectInstance * readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readSign(const int3 & position);
 	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 * readArtifact(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readResource(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);

+ 0 - 2
lib/registerTypes/RegisterTypes.h

@@ -54,7 +54,6 @@ void registerTypesMapObjects1(Serializer &s)
 			s.template registerType<CGMonolith, CGSubterraneanGate>();
 			s.template registerType<CGMonolith, CGWhirlpool>();
 	s.template registerType<CGObjectInstance, CGSignBottle>();
-	s.template registerType<CGObjectInstance, CGScholar>();
 	s.template registerType<CGObjectInstance, CGKeys>();
 		s.template registerType<CGKeys, CGKeymasterTent>();
 		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(CGQuestGuard);
 	REGISTER_GENERIC_HANDLER(CGResource);
-	REGISTER_GENERIC_HANDLER(CGScholar);
 	REGISTER_GENERIC_HANDLER(CGSeerHut);
 	REGISTER_GENERIC_HANDLER(CGShipyard);
 	REGISTER_GENERIC_HANDLER(CGSignBottle);

+ 4 - 5
lib/rewardable/Info.cpp

@@ -115,7 +115,7 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan
 
 	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.artifacts = JsonRandom::loadArtifacts(source["artifacts"], 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.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.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng, variables);
@@ -221,9 +221,8 @@ void Rewardable::Info::configureVariables(Rewardable::Configuration & object, CR
 			//	if (category.first == "resource")
 			//		value = JsonRandom::loadResource(input, rng, object.variables.values).getNum();
 
-			// TODO
-			//	if (category.first == "primarySkill")
-			//		value = JsonRandom::loadCreature(input, rng, object.variables.values).getNum();
+			if (category.first == "primarySkill")
+				value = static_cast<int>(JsonRandom::loadPrimary(input, rng, object.variables.values));
 
 			if (category.first == "secondarySkill")
 				value = JsonRandom::loadSecondary(input, rng, object.variables.values).getNum();