The devs need to revisit texts/buildings_descriptions.rpy, core/research.rpy and core/buildings.rpy since it looks like they are suffering from some bit rot due to update issues. The first thing I ran into was the upgrade of the Tavern from lvl 1 to 2 becoming available in the workshop even though I had only 95% of my first research of Fiendish Diplomacy finished.
The prereqs listed for this upgrade show requirements for both Fiendish Diplomacy and Silks and Smiles being done. I can't even see Silks and Smiles listed in the library research list yet because Fiendish Diplomacy isn't even done. So I shouldn't even see this lvl 2 upgrade being available in the workshop. Time for a code dive....
The popup screen for a given building or building upgrade in the workshop relies on data structures defined in the above three rpy files. The Prereqs section comes from the research tree classes defined in research.rpy and how the req_met method definition for a given research item class returns True or False (assumed True in the prototype if not overridden).
Code:
class FiendishDiplomacy(Research):
uid = 'fiendish_diplomacy'
name = 'Fiendish diplomacy'
category = 'diplomacy'
cost = 40
requires = 'Opulence, level 2 castle hall'
unlocks = 'Can recruit new soldier types.'
def on_complete(self):
castle.buildings['hall'].max_lvl = 2
castle.buildings['brothel'].max_lvl = 1
Fiendish Diplomacy is immediately available for research at game start since there is no override here for req_met. When done, it allows the castle hall to be upgraded to lvl 2 and allows the brothel to become available (going from lvl 0 to lvl 1). That means Rowan should be able to build the brothel next week since the research is 95% done and the castle probably will have the 200 gold available in castle.treasury (which is over 300 at the moment).
Code:
class SilksAndSmiles(Research):
uid = 'silks_and_smiles'
name = 'Silks and smiles'
category = 'diplomacy'
cost = 120
requires = 'Dark Subterfuge, brothel'
unlocks = 'Level 2 brothel, level 2 tavern.'
def req_met(self):
return castle.researches['fiendish_diplomacy'].completed and (castle.buildings['brothel'].lvl >= 1 or castle.buildings['tavern'].lvl >= 1)
def on_complete(self):
castle.buildings['brothel'].max_lvl = 2
castle.buildings['tavern'].max_lvl = 2
castle.buildings["brothel"].available_spies.append(Spy(renpy.random.choice(["m", "f"])).uid)
The req_met method override for SilksAndSmiles will only return True if both FiendishDiplomacy is completed and the castle already has either a brothel or a tavern built. So it will probably appear next week when the research is completed for Fiendish Diplomacy.
Going into the console, I can verify by hand to show what can be researched and what is completed by using the "uid" name attribute defined in each class to index the castle.researches list.
Code:
castle.researches['fiendish_diplomacy"].req_met()
True
castle.researches["fiendish_diplomacy"].completed
False
castle.researches["silks_and_smiles"].req_met()
False
The on_complete method is a callback that runs when the research is completed. This appears to be a "clean" way going forward for research events to make buildings and upgrades available in the workshop by setting the max_lvl values. Unfortunately, it appears that buildings.rpy still has some cruft left over from earlier releases.
There are two methods on the Building class "can_be_built" and "can_be_shown" that make a building or building upgrade available in the workshop menu and be active and shown in the castle when built respectively. Our problem for the Tavern is that a can_be_built() is returning True when it shouldn't for the lvl 2 upgrade.
Code:
class Building(object):
'''Base class for castle buildings'''
def __init__(self, uid):
self.uid = uid
self.name = all_buildings[uid]['name']
self.lvl = 0
# max level of this building, changing through game
self.max_lvl = 0
self.income = 0
self.maintenance = 0
self.morale = 0
self.research = 0
self._capacity = 0
self.recruitment = 0
self.description = all_buildings[uid]['name'] + ' description'
self.available = all_buildings[uid]['available']
.
.
.
def can_be_built(self):
'''Returns True if all conditions for upgrading to next level are met'''
# can be built if there is enough money and if this upgrade is not already scheduled and if current level is lesser than max level
#TODO
if self.name == "Summoning Chambers":
return False
return (castle.buildings[self.uid].up_cost <= castle.treasury) and (len(castle.scheduled_upgrades) == 0) and castle.buildings[self.uid].lvl < castle.buildings[self.uid].max_lvl and castle.buildings[self.uid].req_met()
def can_be_shown(self):
'''Returns True if building can be shown to player'''
return castle.buildings[self.uid].lvl <= castle.buildings[self.uid].max_lvl and castle.buildings[self.uid].req_met()
def req_met(self):
'''Returns True if building-specific conditions are met'''
if self.uid == "arena":
if self.lvl == 0:
return castle.researches['ordering_chaos'].completed
elif self.lvl == 1:
return castle.researches['military_logistics'].completed
elif self.uid == "pit" or self.uid == "kennel":
if self.lvl == 0:
return castle.researches['monster_taming'].completed
elif self.uid == "brothel":
if self.lvl == 0:
return castle.researches['fiendish_diplomacy'].completed
elif self.lvl == 1:
return castle.researches['silks_and_smiles'].completed
elif self.uid == "hall":
if self.lvl == 1:
return castle.researches['fiendish_diplomacy'].completed
elif self.lvl == 2:
return castle.researches['opulence'].completed
elif self.uid == "library":
if self.lvl == 1:
return castle.researches['contact_network'].completed
elif self.lvl == 2:
return castle.researches['research_infrastructure'].completed
elif self.uid == "barracks":
if self.lvl == 1:
return castle.researches['ordering_chaos'].completed
elif self.lvl == 2:
return castle.researches['military_logistics'].completed
return True
That whole req_met method definition definitely is old code that needs to be deleted and taken out of the can_be_built and can_be_shown methods. We have a new research.rpy that is setting up callbacks to adjust max_lvl values upwards as the given research is completed. The code checking here is redundant and can cause confusion if it gets out of synch with the values set up in the research completion methods over in research.rpy. It's also bad style to be putting subclass specific code like this in the prototype class Building. If anything, it should just be defined to return True and then be overridden as necessary in the subclasses like Forge and Barracks.
Speaking of subclasses.... Where's Hall, Quarters, Tavern, Summoning, Caravan and Nasimchamber defined as subclasses? They are only in the all_buildings datastructure at the bottom of buildings.rpy. It would be more proper form to have Building as a pure template class with everything else subclassing either it or its MagicBuilding subclass. That way the Castle class in core/castle.rpy won't need to be checking for one versus the other when instantiating.
But we are getting distracted with the whole thing about why castle.buildings["tavern"].can_be_built() is still returning True even after the lvl 1 Tavern has been built but before silks_and_smiles is ever researched and completed to set the Tavern max_lvl to 2. It turns out the real culprit is a really old section of code that's been around since probably before the whole "Rowan is a cuck" flame wars first broke out on F95. Look what we have way up at the tippy top of rowan_intro.rpy where the castle itself gets instantiated:
Code:
# temporary dict to store event's variables
# should only be used to save vars in event scope
default event_tmp = {}
label rowan_intro:
python:
# timestamp in weeks
week = 0
# adult mode (NSFW)
adult = True
# create avatar Rowan
avatar = Avatar('Rowan')
# set variable to show events that this scenario is Rowan's
Rowan_sc = True
# add some items to backpack
avatar.inventory.add_items(('iron_sword', 'leather_straps'))
# equip 'sword' to main hand
avatar.inventory.equip('iron_sword')
avatar.inventory.equip('leather_straps')
avatar.gold = 100
avatar.base_strength = 10
avatar.base_vitality = 10
avatar.base_reflexes = 5
avatar.base_intelligence = 5
avatar.base_luck = 5
avatar.heal()
# create castle
castle = Castle()
# hide temporary uid
python hide:
# create starting buildings
for uid in starting_buildings:
castle.buildings[uid].build()
# unlock first level of some buildings - they are available without research
for uid in castle.buildings.keys():
if uid in ["hall", "library", "barracks"]:
castle.buildings[uid].max_lvl = 3
elif uid in ["sanctum", "tavern", "forge", "brothel", "arena"]:
castle.buildings[uid].max_lvl = 2
Of all the stupid places to be setting max_lvl for a building nowadays.... We have the max_lvl value being set to the actual max level of the building here, and the devs apparently forgot that this old early code is still hanging around. This should be in each building subclass init method in building.rpy where lvl and max_lvl is set to be either 0 or 1 depending on whether the building is immediately available for building or already built at game start. There's the proper excuse for the devs to follow through and define a proper subclass for Hall, Tavern and the like that I mentioned earlier. Because Tavern wasn't a proper subclass, and its max_lvl was set to 2 here at game start, the level 2 becomes immediately available as an upgrade as soon as you have the lvl 1 built and the proper amount of scratch in your castle.treasury.