# Released under the MIT License. See LICENSE for details.#"""Functionality related to individual levels in a campaign."""from__future__importannotationsimportcopyimportweakreffromtypingimportTYPE_CHECKING,overrideimportbabaseifTYPE_CHECKING:fromtypingimportAnyimportbascenev1
[docs]classLevel:"""An entry in a :class:`~bascenev1.Campaign`."""def__init__(self,name:str,gametype:type[bascenev1.GameActivity],settings:dict,preview_texture_name:str,*,displayname:str|None=None,):self._name=nameself._gametype=gametypeself._settings=settingsself._preview_texture_name=preview_texture_nameself._displayname=displaynameself._campaign:weakref.ref[bascenev1.Campaign]|None=Noneself._index:int|None=Noneself._score_version_string:str|None=None@overridedef__repr__(self)->str:cls=type(self)returnf"<{cls.__module__}.{cls.__name__} '{self._name}'>"@propertydefname(self)->str:"""The unique name for this level."""returnself._name
[docs]defget_settings(self)->dict[str,Any]:"""Returns the settings for this Level."""settings=copy.deepcopy(self._settings)# So the game knows what the level is called.# Hmm; seems hacky; I think we should take this out.settings['name']=self._namereturnsettings
@propertydefpreview_texture_name(self)->str:"""The preview texture name for this level."""returnself._preview_texture_name@propertydefdisplayname(self)->bascenev1.Lstr:"""The localized name for this level."""returnbabase.Lstr(translate=('coopLevelNames',(self._displaynameifself._displaynameisnotNoneelseself._name),),subs=[('${GAME}',self._gametype.get_display_string(self._settings))],)@propertydefgametype(self)->type[bascenev1.GameActivity]:"""The type of game used for this level."""returnself._gametype@propertydefcampaign(self)->bascenev1.Campaign|None:"""The campaign this level is associated with, or None."""returnNoneifself._campaignisNoneelseself._campaign()@propertydefindex(self)->int:"""The zero-based index of this level in its campaign. Access results in a RuntimeError if the level is not assigned to a campaign. """ifself._indexisNone:raiseRuntimeError('Level is not part of a Campaign')returnself._index@propertydefcomplete(self)->bool:"""Whether this level has been completed."""config=self._get_config_dict()val=config.get('Complete',False)assertisinstance(val,bool)returnval
[docs]defset_complete(self,val:bool)->None:"""Set whether or not this level is complete."""old_val=self.completeassertisinstance(old_val,bool)assertisinstance(val,bool)ifval!=old_val:config=self._get_config_dict()config['Complete']=val
[docs]defget_high_scores(self)->dict:"""Return the current high scores for this level."""config=self._get_config_dict()high_scores_key='High Scores'+self.get_score_version_string()ifhigh_scores_keynotinconfig:return{}returncopy.deepcopy(config[high_scores_key])
[docs]defset_high_scores(self,high_scores:dict)->None:"""Set high scores for this level."""config=self._get_config_dict()high_scores_key='High Scores'+self.get_score_version_string()config[high_scores_key]=high_scores
[docs]defget_score_version_string(self)->str:"""Return the score version string for this level. If a level's gameplay changes significantly, its version string can be changed to separate its new high score lists/etc. from the old. """ifself._score_version_stringisNone:scorever=self._gametype.getscoreconfig().versionifscorever!='':scorever=' '+scoreverself._score_version_string=scoreverassertself._score_version_stringisnotNonereturnself._score_version_string
@propertydefrating(self)->float:"""The current rating for this level."""val=self._get_config_dict().get('Rating',0.0)assertisinstance(val,float)returnval
[docs]defset_rating(self,rating:float)->None:"""Set a rating for this level, replacing the old ONLY IF higher."""old_rating=self.ratingconfig=self._get_config_dict()config['Rating']=max(old_rating,rating)
def_get_config_dict(self)->dict[str,Any]:"""Return/create the persistent state dict for this level. The referenced dict exists under the game's config dict and can be modified in place. """campaign=self.campaignifcampaignisNone:raiseRuntimeError('Level is not in a campaign.')configdict=campaign.configdictval:dict[str,Any]=configdict.setdefault(self._name,{'Rating':0.0,'Complete':False})assertisinstance(val,dict)returnvaldefset_campaign(self,campaign:bascenev1.Campaign,index:int)->None:"""Internal: Used by campaign when adding levels to itself. :meta private: """self._campaign=weakref.ref(campaign)self._index=index
# Docs-generation hack; import some stuff that we likely only forward-declared# in our actual source code so that docs tools can find it.fromtypingimport(Coroutine,Any,Literal,Callable,Generator,Awaitable,Sequence,Self)importasynciofromconcurrent.futuresimportFuture