#####################################################################
#                SEXBOT LAB
#####################################################################

default shellslot = False
default addon_buttons = False
default togglefilters = True
default togglefilters2 = True
default partCat = ""
default immoBaseEffects = {
                                          1: [10, 5, 2],
                                          2: [20, 10, 5],
                                          3: [40, 20, 10]
                                      }
default prefImpact = {"subpar": 0.5, "basic": 1.0, "advanced": 1.5, "military grade": 2.5}

default partsFilter2 = {
            "prefs": {"subpar": True, "basic": True, "advanced": True,        
            "military grade": True},
            "cat": {1: True, 2: True, 3: True },
            "kind": {"sexbot parts": True, "sexbot addons": True, "sexbot shell": False},
            "modules": {"IQ": togglefilters, "Immo": togglefilters, "ANA": togglefilters, "BON": togglefilters, "ORA": togglefilters, "SPE": togglefilters, "VAG": togglefilters, "PER": togglefilters}
            }

image ani_thumb_auto:
    "10_anims/ani_thumb_auto_00.jpg"
    0.3
    "10_anims/ani_thumb_auto_01.jpg"
    0.1
    "10_anims/ani_thumb_auto_02.jpg"
    0.05
    "10_anims/ani_thumb_auto_03.jpg"
    0.05
    "10_anims/ani_thumb_auto_04.jpg"
    0.1
    "10_anims/ani_thumb_auto_05.jpg"
    0.2
    "10_anims/ani_thumb_auto_04.jpg"
    0.1
    "10_anims/ani_thumb_auto_03.jpg"
    0.05
    "10_anims/ani_thumb_auto_02.jpg"
    0.05
    "10_anims/ani_thumb_auto_01.jpg"
    0.1
    repeat  

image bot_doll = LiveComposite(
    (0,0),
    (0,0), "bot_smart",
    (0,0), "bot_head_smart",
    (0,0), "bot_torso_smart",
    (0,0), "bot_arms_smart",
    (0,0), "bot_legs_smart", 
    (346,144), "bot_eyes_smart"
    )

image lab_bg = ConditionSwitch(
    "GAME.lab.current.getShellConfig()in ['I']", "03_minigames/mgsb_bg_red.png",
    "True", "03_minigames/mgsb_bg.png"
)

image analyzer_lBot = ConditionSwitch(
    "botData['left'][0].getShellConfig() == 'P'", "03_minigames/mgsb_full_bot_p.png",
    "botData['left'][0].getShellConfig() == 'N'", "03_minigames/mgsb_full_bot_n.png",
    "botData['left'][0].getShellConfig() == 'V'", "03_minigames/mgsb_full_bot_v.png",
    "botData['left'][0].getShellConfig() == 'K'", "03_minigames/mgsb_full_bot_k.png",
    "botData['left'][0].getShellConfig() == 'C'", "03_minigames/mgsb_full_bot_c.png",
    "botData['left'][0].getShellConfig() == 'L'", "03_minigames/mgsb_full_bot_l.png",
    "botData['left'][0].getShellConfig() == 'I'", "03_minigames/mgsb_full_bot_i.png",
    "botData['left'][0].getShellConfig() == 'J'", "03_minigames/mgsb_full_bot_j.png",
    "True", "03_minigames/mgsb_full_bot.png"    
)

image analyzer_rBot = ConditionSwitch(
    "botData['right'][0].getShellConfig() == 'P'", "03_minigames/mgsb_full_bot_p.png",
    "botData['right'][0].getShellConfig() == 'N'", "03_minigames/mgsb_full_bot_n.png",
    "botData['right'][0].getShellConfig() == 'V'", "03_minigames/mgsb_full_bot_v.png",
    "botData['right'][0].getShellConfig() == 'K'", "03_minigames/mgsb_full_bot_k.png",
    "botData['right'][0].getShellConfig() == 'C'", "03_minigames/mgsb_full_bot_c.png",
    "botData['right'][0].getShellConfig() == 'L'", "03_minigames/mgsb_full_bot_l.png",
    "botData['right'][0].getShellConfig() == 'I'", "03_minigames/mgsb_full_bot_i.png",
    "botData['right'][0].getShellConfig() == 'J'", "03_minigames/mgsb_full_bot_j.png",
    "True", "03_minigames/mgsb_full_bot.png"    
)


image full_bot = ConditionSwitch(
    "GAME.lab.current.getShellConfig() == 'P'", "03_minigames/mgsb_full_bot_p.png",
    "GAME.lab.current.getShellConfig() == 'N'", "03_minigames/mgsb_full_bot_n.png",
    "GAME.lab.current.getShellConfig() == 'V'", "03_minigames/mgsb_full_bot_v.png",
    "GAME.lab.current.getShellConfig() == 'K'", "03_minigames/mgsb_full_bot_k.png",
    "GAME.lab.current.getShellConfig() == 'C'", "03_minigames/mgsb_full_bot_c.png",
    "GAME.lab.current.getShellConfig() == 'L'", "03_minigames/mgsb_full_bot_l.png",
    "GAME.lab.current.getShellConfig() == 'I'", "03_minigames/mgsb_full_bot_i.png",
    "GAME.lab.current.getShellConfig() == 'J'", "03_minigames/mgsb_full_bot_j.png",
    "True", "03_minigames/mgsb_full_bot.png"    
)

# background image
image bot_smart = ConditionSwitch(
    "GAME.lab.current.getShellConfig() == 'J'", "03_minigames/mgsb_doll_j.png",
    "True", "03_minigames/mgsb_doll.png"
)
image bot_arms_smart = ConditionSwitch(
    "GAME.lab.current.hasComp('arms') and GAME.lab.current.getShellConfig() == 'J'", "03_minigames/mgsb_arms_j.png",
    "GAME.lab.current.hasComp('arms')", "03_minigames/mgsb_arms.png",
    "True", "05_gui/px_1x1_empty.png"
)
image bot_legs_smart = ConditionSwitch(
    "GAME.lab.current.hasComp('legs') and GAME.lab.current.getShellConfig() == 'J'", "03_minigames/mgsb_legs_j.png",
    "GAME.lab.current.hasComp('legs')", "03_minigames/mgsb_legs.png",
    
    "True", "05_gui/px_1x1_empty.png")
    
image bot_head_smart = ConditionSwitch(
    "GAME.lab.current.hasComp('head') and GAME.lab.current.getShellConfig() == 'J'", "03_minigames/mgsb_head_j.png",
    "GAME.lab.current.hasComp('head')", "03_minigames/mgsb_head.png",
    "True", "05_gui/px_1x1_empty.png"
)

image bot_torso_smart = ConditionSwitch(
    "GAME.lab.current.hasComp('torso') and GAME.lab.current.getShellConfig() == 'J'", "03_minigames/mgsb_torso_j.png",
    "GAME.lab.current.hasComp('torso')", "03_minigames/mgsb_torso.png",
    "True", "05_gui/px_1x1_empty.png"
)

image bot_eyes_smart = ConditionSwitch(
    "GAME.lab.current.getCat('cortex')==1", "bot_eyes_a_ani",
    "GAME.lab.current.getCat('cortex')==2", "bot_eyes_b_ani",
    "GAME.lab.current.getCat('cortex')==3", "bot_eyes_c_ani",
    "True", "05_gui/px_1x1_empty.png"
)

image bot_eyes_a_ani:
    "03_minigames/mgsb_eyes_a_1.png"
    2.0
    "03_minigames/mgsb_eyes_a_2.png"
    0.2
    "03_minigames/mgsb_eyes_a_3.png"
    0.2
    "03_minigames/mgsb_eyes_a_2.png"
    0.2
    repeat

image bot_eyes_b_ani:
    "03_minigames/mgsb_eyes_b_1.png"
    2.0
    "03_minigames/mgsb_eyes_b_2.png"
    0.2
    "03_minigames/mgsb_eyes_b_3.png"
    0.2
    "03_minigames/mgsb_eyes_b_2.png"
    0.2
    repeat

image bot_eyes_c_ani:
    "03_minigames/mgsb_eyes_c_1.png"
    2.0
    "03_minigames/mgsb_eyes_c_2.png"
    0.2
    "03_minigames/mgsb_eyes_c_3.png"
    0.2
    "03_minigames/mgsb_eyes_c_2.png"
    0.2
    repeat
#####################################################################
#					CLASSES ETC												  
#####################################################################
init python:
    class BotLab():
        def __init__(self):
            self.status = {"bio": 100, "energy": 100, "nano":100}
            self.cellsMax = 10
            self.cells = [False for i in range(self.cellsMax)]
            self.hiddenCells = [] # bots moved into hidden cells (quest, ...)
            self.parts = []
            self.orders = [] # tuples with [description, cond1, ..., condn]
            self.current = Sexbot() # empty bot holder
            self.specialOffers = []
            self.extensions = [False, False, False, False]
            self.botsCreated = set()

        def dumpData(self, bot):
            # base data 
            bData = {}
            bData["name"] = bot.name 
            bData["talents"] = bot.talents 
            bData["skills"] = bot.skills 
            bData["attitude"] = bot.attitude
            bData["traits"] = bot.traits 
            bData["likes"] = list(bot.likes) 
            bData["dislikes"] = list(bot.dislikes) 
            bData["attr"] = bot.attr 
            bData["personality"] = bot.personality
            bData["tags"] = bot.tags 
            bData["tally"] = bot.tally 
            bData["IQ"] = bot.IQ 
            bData["qc"] = bot.qc
            bData["price"] = bot.price 
            bData["partNames"] = bot.partNames 
            # array of components 
            partList = []
            for i in bot.parts.values():
                pData = {}
                pData["ID"] = "RecalcOnAssemble"
                pData["name"] = i.name
                pData["kind"] = i.kind
                pData["slot"] = i.slot 
                pData["art"] = i.art
                pData["cat"] = i.cat
                pData["pref"] = i.pref
                pData["price"] = i.price 
                pData["description"] = i.description
                pData["tags"] = i.tags 
                pData["attr"] = i.attr 
                pData["identified"] = True
                pData["wear"] = i.wear
                partList.append(pData) 

            aList = []
            for i in [x for x in bot.addOns if x != False]:
                pData = {}
                pData["ID"] = "RecalcOnAssemble"
                pData["name"] = i.name
                pData["kind"] = i.kind
                pData["slot"] = i.slot 
                pData["art"] = i.art
                pData["cat"] = i.cat
                pData["pref"] = i.pref
                pData["price"] = i.price 
                pData["description"] = i.description
                pData["tags"] = i.tags 
                pData["attr"] = i.attr 
                pData["identified"] = True
                pData["wear"] = i.wear
                aList.append(pData) 

            flat = json.dumps([bData, partList, aList])
            checksum = hash(flat)
            #structured = json.loads(flat)
            return([flat, checksum])

        def reconstruct(self, botData, version = False):
            botString = botData[0]
            checksum = botData[1]
            bData = json.loads(botString)[0]
            partsList = json.loads(botString)[1]
            addonList = json.loads(botString)[2]

            # create clean BOT 
            bot = Sexbot()
            # fill bot data 
            bot.name = bData["name"]
            bot.talents = bData["talents"] 
            bot.skills = bData["skills"] 
            bot.traits = bData["traits"] 
            bot.attitude = bData["attitude"] 
            bot.likes = set(bData["likes"])
            bot.dislikes = set(bData["dislikes"])
            bot.attr = bData["attr"]
            bot.tags = bData["tags"]
            bot.tally = bData["tally"]
            bot.IQ = bData["IQ"]
            bot.price = bData["price"] 
            bot.partNames = bData["partNames"]

            # CR-0831 START ============
            bot.qc = bData.get("qc",0) 
            # CR-0831 END ==============

            # set bot ID 
            bot.ID = "BOT-{}".format(GAME.getUniqueID())

            # for all parts
            for i in partsList:
                pID = "{}-{}".format(i["attr"]["BaseID"],GAME.getUniqueID())
                part = SexbotPart(pID, i["name"], i["art"], i["kind"], i["slot"], i["cat"], i["description"], i["price"], i["tags"], i["attr"])
                # attach part to bot
                part.wear = i["wear"] 
                part.identified = i["identified"]
                part.pref = i["pref"] 
                bot.parts[part.slot]= part

            # for all addons
            slot = 0
            for i in addonList:
                # legacy shell substitution
                if "SBPShellType" in i["attr"]["BaseID"]:
                    i["attr"]["BaseID"] = "SBPShell1Type"+i["attr"]["BaseID"][-1]

                # create part
                pID = "{}-{}".format(i["attr"]["BaseID"],GAME.getUniqueID())
                part = SexbotPart(pID, i["name"], i["art"], i["kind"], i["slot"], i["cat"], i["description"], i["price"], i["tags"], i["attr"])

                # attach part to bot
                part.wear = i["wear"] 
                part.identified = i["identified"]
                part.pref = i["pref"] 

                if part.isShell():
                    part.slot = "shell" # CR-0730
                    bot.addOns[5]= part # last slot for shells
                    
                else:   
                    bot.addOns[slot]= part
                    slot += 1



            # version specific extensions 
            if version in ["POD_VERSION_060", "WAREHOUSE_VERSION_100"]:
                bot.personality = bData["personality"]
                
            return(bot)

        def infiltratorStandby(self):
            for bot in [x for x in GAME.lab.cells if x]:
                if bot.getShellConfig() == "I": # infiltrator 
                    return(True) 
            return(False)

        def hasFreeCell(self):
            if False in self.cells:
                return(True)
            return(False)

        def clearBot(self, bot):
            for i in range(self.cellsMax):
                if self.cells[i] == bot:
                    self.cells[i] = False
                    return()
            return(False) 

        def getBotFromCell(self, bot): # take out bot from cells
            for i in range(self.cellsMax):
                if self.cells[i] == bot:
                    self.cells[i] = False
                    return(bot)
            return(False) 

        def retrieveBot(self, bot): # move bot into configurator
            for i in range(self.cellsMax):
                if self.cells[i] == bot:
                    self.cells[i] = False
                    self.current = bot 
                    return()
            return(False) 

        def storeBot(self, bot, slot = None):
            if not bot.hasComp("cortex"): # empty shell
                return()
            if slot != None:
                self.cells[slot] = bot
                self.current = Sexbot() # new empty bot holder            
            else: 
                for i in range(self.cellsMax):
                    if self.cells[i] == False:
                        self.cells[i] = bot
                        self.current = Sexbot() # new empty bot holder
                        return()
            return(False) 

        def charge(self,itemID):
            pod = itemID[6:].lower() # strip common ID item prefix
            GAME.lab.status[pod] = 100 
            GAME.ship.unloadWare(itemID)
            return()

        def clearLab(self):
            self.current = Sexbot()
            return()

        def upgradeParts(self, mode = "pref", include = ["any"], exclude_grade = []):

            P_NUM = 3
            eligible = []
            part = False
            for pref in ["subpar", "basic", "advanced"]:
                tally = {}
                for i in [x for x in GAME.lab.parts if x.pref == pref]: 
                    # build pref basket tally by baseID
                    if not i.attr["BaseID"] in tally.keys():
                        tally[i.attr["BaseID"]] = 0  
                    # ==== CR-0439 START =====
                    if i.slot not in ["cube"] and i.attr["BaseID"] not in ["SBPCortexPre"]: 
                        tally[i.attr["BaseID"]] += 1
                    # ==== CR-0439 END =====

                for k in tally.keys():
                    if tally[k] >= P_NUM:
                        eligible.append((pref,k,tally[k]))

            # select part to upgrade
            if eligible:
                target = eligible[0]
                prefs = {"subpar": "basic", "basic": "advanced", "advanced": "military grade"}
                # create one new part 
                pref = prefs[target[0]]

                # test for exceptional results jump 
                if "basic" in exclude_grade and pref == "basic":
                    pref = "advanced"
                if "advanced" in exclude_grade and pref == "advanced":
                    pref = "military grade"

                # create part
                part = GAME.sbPartsData[target[1]].clone({"pref": pref})
                # eliminate P_NUM old parts
                parts = [x for x in GAME.lab.parts if x.attr["BaseID"]==target[1] and x.pref == target[0]]
                for i in range(P_NUM):
                    GAME.lab.parts.remove(parts[i])
                GAME.lab.parts.append(part)

            return(part)

        def findOrder(self,bot, order="any"): # returns first fulfilled order found 
            if order == "any":
                for i in self.orders:
                    if all([eval(x, globals(), locals()) for x in i[1:]]): 
                        return(i)
            return(False)

        def findBots(self,order): # returns first fulfilled order found 
            bots = []
            for bot in [c for c in GAME.lab.cells if c]:
                if all([eval(x, globals(), locals()) for x in order[1:]]): 
                    bots.append(bot)
            return(bots)

        def getCompleted(self):
            completed = []
            for i in [x for x in GAME.lab.cells if x]:
                if i.isComplete():
                    completed.append(i)
            return(completed)

    class Sexbot():
        def __init__(self, attr={}):
            self.ID = "BOT-{}".format(GAME.getUniqueID()) 
            self.name = None
            self.doneToday = []
            self.earnedToday = []
            self.parts = {}
            self.talents = {}
            self.skills = {}
            self.traits = []
            self.likes = set()
            self.dislikes = set()
            self.attr = {}
            self.addOns = [False for i in range(6)] # 0 to 4 normal addons, 6 is shell slot
            self.attitude = "rebellious"
            self.personality = "Unknown Pattern"
            self.tags = []
            self.tally = {}
            self.IQ = 100
            self.price = 500
            self.autoTrain = False
            self.partNames = ["cortex", "head", "torso", "arms", "legs"]
            self.qc = 0 # used qc points


        # def getQC(self, attr="ANY"): 
        #     qc = 0
        #     for k in self.skills.keys():
        #         if attr == "ANY" or attr == k:
        #             qc += self.attr.get("qc_{}".format(k),0)
        #     return(qc)

        def getAttr(self, k):
            if k in self.attr.keys():
                return(self.attr[k])
            return(None)

        def rollNew(self, cortex, attr={}):

            # buff = {1: 0, 2:20, 3:40}[self.parts["cortex"].cat]  CR-0295

            # sexbot name 
            if "name" in attr.keys():
                self.name = attr["name"]
            else:
                self.name = random.choice(GAME.sbData["names"])

            # sexbot mandatory parts 
            self.parts["cortex"] = cortex
            buff = {1: 0, 2:20, 3:40}[self.parts["cortex"].cat]  # CR-0295
            self.IQ = int (capValue(random.gauss(120+buff,20),80,300))

            # rebelliousness 
            if "attitude" in attr.keys():
                self.attitude = attr["attitude"]
            else:
                self.attitude = random.choice(GAME.sbData["attitudes"][:-1])
             
            # sexbot talents (defined by cortex, augmented by parts)                
            for k in ["ANA", "ORA", "VAG", "BON", "SPE"]:
                self.talents[k] = random.randint(buff,100+buff)

            # personality 
            if "personality" in attr.keys():
                self.personality = attr["personality"]
            else:            
                self.personality = random.choice(GAME.sbData["personalities"])

            # sexbot skills 
            for k in ["ANA", "ORA", "VAG", "BON", "SPE"]:
                self.skills[k] = random.randint(0,self.talents[k]/5)

            # sexbot traits 
            if "traits" in attr.keys():
                self.traits = list(attr["traits"])
            else:
                traitPairs = random.sample(GAME.sbData["traits"],2) 
                self.traits.append(traitPairs[0][random.choice([0,1])])
                self.traits.append(traitPairs[1][random.choice([0,1])])
            # 3rd trait?
            if self.parts["cortex"].attr["BaseID"] == "SBPCortexPre" and len(self.traits)<3: # CR-0875
                self.traits.append(random.choice([x[2] for x in GAME.sbData["traits"]]))

            # sexbot likes / dislikes
            if "likes" in attr.keys():
                self.likes = set(attr["likes"])
            else: 
                likes = 1
                dislikes = 1
                if "perverted" in self.traits:
                    likes += 2
                elif "frigid" in self.traits:
                    likes -= 1
                    dislikes += 2
                self.likes.update(random.sample(GAME.sbData["likes"], likes))
                self.dislikes.update(random.sample(GAME.sbData["dislikes"], dislikes))
                for i in self.likes:
                    if i in self.dislikes:
                        self.dislikes.remove(i)

            # like/dislike adjustments
            if all([x in self.traits for x in ["frigid", "aggressive"]]):
                for k in self.talents.keys():
                    self.talents[k] = capValue(self.talents[k]-random.randint(1,20),0,100)
            if all([x in self.traits for x in ["flexible", "submissive"]]):
                self.likes.add("bondage")
                self.talents["BON"] = capValue(self.talents["BON"]+40,0,100)
            if all([x in self.traits for x in ["perverted", "aggressive"]]):
                self.likes.add("oral")
                self.talents["BON"] = capValue(self.talents["BON"]+20,0,100)
            if all([x in self.traits for x in ["curious"]]):
                self.likes.add("special")
            if all([x in self.traits for x in ["perverted"]]):
                self.talents["SPE"] = capValue(self.talents["SPE"]+20,0,100)
                self.talents["ANA"] = capValue(self.talents["ANA"]+20,0,100)
            if any([x in self.traits for x in ["conservative", "wholesome"]]):
                self.likes.discard("special")
                self.likes.discard("anal")

            # trait adjustments 
            if "inflexible" in self.traits:
                self.talents["BON"] = min(self.talents["BON"], 20)
                self.skills["BON"] = min(self.talents["BON"], self.skills["BON"])     

            if self.personality in ["princess", "celebrity"]:
                self.attitude = random.choice(["rebellious", "devious", "insidious"])
    
            return()

        def getWear(self): # 1.0 = 100% worn
            wear = 0.0
            # any part 100% wear -> full bot 100% (unusable)
            if any ([x.wear == 1.0 for x in self.parts.values()]):
                return(1.0)
            # calculate average wear
            partsCond = [x.wear for x in self.parts.values()]
            if partsCond: 
                wear = sum(partsCond)/float(len(partsCond))
            return(wear)

        def getShellConfig(self):
            addOns = [x for x in self.addOns if x]
            botShell = [x for x in addOns if "Shell" in x.ID] 
            if not botShell:
                return("A")
            shellType = botShell[0].attr["BaseID"][-1]
            return(shellType)

        def getTalents(self): # equipment corrected talent values
            talents = {}
            # base talents
            for k in self.talents.keys():
                talents[k] = self.talents[k] 
            # add-on bonus 
            for i in [x for x in self.addOns if x]:
                for k in self.talents.keys():
                    talents[k] += i.getTalentBoost(k)  
            # parts bonus 
            prefBonus = {"subpar": -1, "basic": 0, "advanced": 5, "military grade": 10, "elite": 20}
            partsMatrix = {"legs": ["BON"], "arms": ["BON"], "torso": ["ANA", "VAG", "SPE"], "head": ["ORA"], "cortex": ["SPE"]} 
            for k in self.parts.keys():
                bonus = 0
                if self.parts[k] not in [False, None]:
                    bonus += prefBonus[self.parts[k].pref]
                    for i in partsMatrix[k]:
                        talents[i] += bonus 
            #for k in self.talents.keys():
                #talents[k] = capValue(talents[k], 0, 100)
            return(talents)

        def getIQRating(self):
            if self.hasComp("cortex") == False:
                return("n/a")
            rating = "Of limited intelligence"
            if self.IQ >= 180:
                rating = "Dangerously intelligent"
            elif self.IQ >= 160:
                rating = "Highly intelligent"
            elif self.IQ >= 140:
                rating = "Very intelligent"
            elif self.IQ >= 120:
                rating = "Quite intelligent"
            elif self.IQ >= 100:
                rating = "Of average intelligence"
            return(rating)

        def getStars(self):
            stars = sum([1 for x in self.getTalents().values() if x >= 85])
            return(stars)

        def getShortDesc(self):
            desc = ""
            fWear = percentColorized(self.getWear()*100, [0.0,100.0], True)
            desc += "Gen {} | IQ {} | {} | {} \n{} | Risk {:.1f}% ".format(self.parts["cortex"].cat, self.getIQ(), self.attitude.title(), fWear[1]+" Wear "+fWear[2]+fWear[0], " | ".join(self.traits).title(), self.getEscapeProb()*100)
            return(desc)

        def assessUniformity(self):
            if "cortex" not in self.parts.keys():
                return(False)
            lead = self.parts["cortex"].cat
            # base parts
            for i in self.parts.values():
                if i.cat != lead:
                    return(False)
            # addons 
            for i in [x for x in self.addOns if x not in [None, False]]:
                if i.cat != lead:
                    return(False)
            return(True)

        def installAddOn(self,slot, item):
            self.addOns[iSlot]= item 
            GAME.lab.parts.remove(item)
            # adapt bot attributes in case of special shell 
            if item.attr["BaseID"] in ["SBPShell1TypeI", "SBPShell2TypeI", "SBPShell3TypeI"]:
                self.attr["BaseAttitude"] = str(self.attitude)
                self.attr["BasePersonality"] = str(self.personality)
                self.attr["BaseTraits"] = list(self.traits)
                self.attitude = "insidious"
                self.personality = "infiltrator" 
                self.traits = ["aggressive", "perverted"]
            return()

        def uninstallAddOn(self,slot):
            item = self.addOns[slot]
            GAME.lab.parts.append(item)
            self.addOns[slot] = False
            # restore bot attributes in case of special shell
            if "BaseID" in item.attr.keys():
                if item.attr["BaseID"] in ["SBPShell1TypeI", "SBPShell2TypeI", "SBPShell3TypeI"]:
                    self.attitude = self.attr["BaseAttitude"]
                    self.personality = self.attr["BasePersonality"]
                    self.traits = self.attr["BaseTraits"]
            # pare skills down 
            for k in self.getTalents().keys():
                self.skills[k] = min(self.skills[k], self.getTalents()[k])
            return()

        def uninstallPart(self, slot):
            if not self.hasComp(slot):
                return()
            # remove part
            part = self.parts[slot] 
            self.parts.pop(slot)
            # adjust overtrained skills (cut off)
            talents = self.getTalents()
            for k in self.skills.keys():
                self.skills[k] = min(self.skills[k], talents[k])
            # move part to storage
            GAME.lab.parts.append(part)
            return()

        def use(self, mode = "personal"):
            pass
            return()

        def train(self,skill, mode="manual"):
            hFactor = 1.0
            if mode == "hyper":
                hFactor = 5.0

            effect = 5 * hFactor

            if GAME.sbData["skillDict"][skill] in self.likes:
                effect *= 2.0
            elif GAME.sbData["skillDict"][skill] in self.dislikes:
                effect /= 2.0 

            effect = int(effect)
                
            self.skills[skill] = min(self.skills[skill]+effect, self.getTalents()[skill])
            if mode != "auto":
                GAME.lab.status["energy"] = max(0, GAME.lab.status["energy"]-10)
            return()

        def colorize(self, string):
            baskets = {
                "red": ["rebellious", "insidious" ],
                "orange": ["devious", "calculating"],
                "yellow": ["aggressive", "conservative"],
                "green": ["compliant", "submissive", "curious","flexible", "actress", "holo starlet", "executive", "socialite", "concubine", "krell concubine", "palace mistress", "explorative", "active"],
                "pink": ["perverted", "nympho", "princess", "supermodel", "hive queen", "protector", "celebrity", "infiltrator", "syrax concubine", "voronian concubine", "temptress", "pet", "playful"],
                "grey": ["chaste", "frigid", "inflexible","wholesome", "inflexible"]
            }
            for k in baskets.keys():
                if string.lower() in baskets[k]:
                    return(ACCENTS[k][0]+string+ACCENTS[k][1])
            return(string)

        def getCat(self, slot):
            if slot in self.parts.keys():
                if self.parts[slot] not in ["False"]:
                    return(self.parts[slot].cat)
            return(False)

        def getIQ(self):
            bonus = 0
            for i in [x for x in self.addOns if x]:
                bonus += i.getIQBoost()
            attBonus = {"insidious": 70, "devious": 30}
            if self.attitude.lower() in attBonus.keys():
                bonus += attBonus[self.attitude.lower()] 
            return(self.IQ+bonus)

        def getPrice(self, mode ="value"):
            analysis = {}
            # sum up base prices incl. wear condition
            analysis["Bot Name"] = self.name
            price = self.price
            analysis["baseprice"] = self.price
            analysis["partsPrice"] = 0.0
            analysis["addOnPrice"] = 0.0           

            for k in self.parts.keys():
                if self.parts[k] not in [None, False]:
                    element = self.parts[k].getPrice()
                    price += element
                    analysis["partsPrice"] +=  element
            for i in range(6): 
                if self.addOns[i]:
                    element = self.addOns[i].getPrice() 
                    price += self.addOns[i].getPrice()     
                    analysis["addOnPrice"] +=  element   

            # uniformity bonus 
            uBonus = 1.0
            if self.assessUniformity() and self.isComplete():
                uBonus = 1.3
            price = price * uBonus 
            analysis["facUniformity"] = uBonus

            # skill bonus 
            sBonus = 0.5 + sum(self.skills.values())/1500.0 # CR-0592
            price = price * sBonus 
            analysis["facSkill"] = sBonus

            # talent bonus 
            tBonus = 1.0 + sum(1 for x in self.talents.values() if x >= 90)/5.0*0.3
            price = price * tBonus 
            analysis["facTalents"] = tBonus

            # IQ bonus
            iBonus = (self.getIQ()/100.0)
            price = price * iBonus # * iBonus
            analysis["facIQ"] = iBonus

            # like/dislike effect 
            analysis["facLikes"] = 1.0
            analysis["facDislikes"] = 1.0
            for k in self.talents.keys():
                if GAME.sbData["skillDict"][k].lower() in self.likes:
                    price *= 1.05 
                    analysis["facLikes"] *= 1.05
                if GAME.sbData["skillDict"][k].lower() in self.dislikes:
                    price *= 0.95
                    analysis["facDislikes"] *= 0.95
                    
            # ==== CR-0752 START
            # special combos 
            combos = [
                [["nympho", "perverted"], {"ALL": 1.15}],
                [["submissive", "perverted"], {"ALL": 1.1}],
                [["curious", "perverted"], {"ALL": 1.05}],

                [["perverted", "aggressive"], {"ANA": 1.8, "BON": 1.5}],
                [["nympho", "aggressive"], {"ANA": 1.2}],
                [["flexible", "submissive"], {"BON": 1.2}],
                [["nympho", "submissive"], {"ORA": 1.2}],
                [["curious", "nympho"], {"SPE": 1.2}],
                [["nympho", "flexible"], {"VAG": 1.2}],

                [["wholesome", "curious"], {"SPE": 1.1}],
                [["frigid", "submissive"], {"ORA": 1.1}],
                [["conservative", "submissive"], {"VAG": 1.1}],
                [["frigid", "submissive"], {"BON": 1.1}],
                [["wholesome", "submissive"], {"ANA": 1.1}]
            ]

            # process combos
            analysis["facCombos"] = 1.0
            for combo in combos:
                bonus = 1.0
                if all([x in self.traits for x in combo[0]]):
                    for c in combo[1].keys():
                        for k in self.skills.keys():
                            if k == c or c == "ALL":
                                bonus += self.skills[k]/1500.0 * combo[1][c]
                    price *= bonus
                    analysis["facCombos"] *= bonus
            # ==== CR-0752 END

            # single positives
            analysis["facSinglesPos"] = 1.0
            for i in ["nympho", "perverted", "flexible", "curious", "submissive"]:
                if i in self.traits:
                    price = price * 1.05
                    analysis["facSinglesPos"] *= 1.05

            # single negatives
            analysis["facSinglesNeg"] = 1.0
            for i in ["frigid", "conservative", "inflexible", "wholesome", "aggressive"]:
                if i in self.traits:
                    price = price * 0.95
                    analysis["facSinglesNeg"] *= 0.95
            
            # third trait
            specialTraits = [x[2] for x in GAME.sbData["traits"]]
            specialTraitBonus = {
                "pet": 1.15, "playful": 1.1, "explorative": 1.05, "active": 1.05, "calculating": 0.95 
            }
            for i in self.traits:
                if i in specialTraits and i in specialTraitBonus.keys():
                    price = price * specialTraitBonus[i] 



            # personality profiles
            analysis["facPersonality"] = 1.0
            pBonus = {  "delinquent": 0.8,
                        "actress": 1.11, 
                        "executive": 1.1, 
                        "holo starlet": 1.1,
                        "socialite":1.15,
                        "celebrity": 1.4, 
                        "concubine": 1.4, 
                        "supermodel": 1.4, # CR-0848
                        "palace mistress": 1.5, 
                        "voronian concubine": 1.5,
                        "krell concubine": 1.5, 
                        "temptress": 1.7, 
                        "princess": 1.8,
                        "infiltrator": 2.0,
                        "protector": 2.0,
                        "hive queen": 2.5
                        }
            for k in pBonus.keys():
                if self.personality == k:
                    price = price * pBonus[k]
                    analysis["facPersonality"] *= pBonus[k]


            analysis["Grand Total"] = int(price)
            if mode == "analysis_log":
                with open(config.savedir + "/bot_analysis.json", "a") as f:
                    json.dump(analysis, f)   
            elif mode == "analysis":             
                return(analysis)

            return(int(price))


        def isComplete(self):
            for i in self.partNames:
                if self.hasComp(i) == False:
                    return(False)
            return(True)

        def isIllegal(self):
            if self.getIQ() >= 140:
                return(True)
            return(False)

        def isBlank(self):
            if self.hasComp("cortex"):
                return(False)
            return(True)

        def hasComp(self, slot):
            if slot not in self.parts.keys():
                return (False)
            if self.parts[slot] in [False, None]:
                return(False)
            return(self.parts[slot])

        def hasAddOn(self, ID):
            for i in self.addOns:
                if i:
                    if i.attr["BaseID"] == ID:
                        return(True)
            return(False)

        def tallyUp(self, k, amount):
            if k not in self.tally:
                self.tally[k] = amount 
            else:
                self.tally[k] += amount 
            return()

        def getTally(self, k):
            if k not in self.tally:
                return(0)
            return(self.tally[k])

        # def getEscapeProbOld(self): # DEPRECATED

        #     prob = 0.0
        #     IQ = self.getIQ()
        #     escapeStart = {"compliant": 140, "indifferent": 130, "rebellious": 120, "devious": 110, "insidious": 100}
        #     if self.attitude.lower() not in escapeStart.keys():
        #         return(prob)
        #     x_0 = escapeStart[self.attitude.lower()]
        #     offset = x_0 - 100
        #     x_1 = 200 + offset
        #     if self.getShellConfig() == 'I':           # CR-0305
        #         x_i = self.getIQ()                  # CR-0305
        #     else:                                   # CR-0305
        #         x_i = min(x_1, self.getIQ())        # CR-0305 
        #     x_i = min(x_1, self.getIQ()) 
        #     # prob = 0.25/(x_1-x_0)*(x_i-x_0)
        #     prob = 0.5/(x_1-x_0)*(x_i-x_0)

        #     # apply immo effects 
        #     immoBaseEffects = {
        #         1: [10, 5, 2],
        #         2: [20, 10, 5],
        #         3: [40, 20, 10]
        #     }
        #     prefImpact = {"subpar": 0.5, "basic": 1.0, "advanced": 1.5, "military grade": 2.5, "elite": 3.5}

        #     immoImpact = 0.0
        #     botGen = self.parts["cortex"].cat
        #     immoEquipped = []
        #     for i in [x for x in self.addOns if x and "addonimmo" in x.ID.lower()]: # all non empty slots
        #         immoImpact -= immoBaseEffects[i.cat][botGen-1]*prefImpact[i.pref]/100.0

        #     prob = capValue(prob+immoImpact, 0.0, 100.0)
        #     #GAME.log.append("immoImpact {}".format(immoImpact))

        #     return(round(prob,1))

        # =========== CR-0379 START ==========================
        def getEscapeProb(self): # 0.0 to 0.5 of 1.0 (=100%)

            if self.attitude == "devoted":
                return(0.0)
            prob = 0.0
            IQ = self.getIQ()
            incDict = {"devoted": 0.1, "compliant": 0.2, "indifferent": 0.3, "rebellious": 0.4, "devious": 0.5, "insidious": 0.7}
            if self.attitude.lower() not in incDict.keys():
                return(prob) 
            inc = incDict[self.attitude.lower()]
            if self.getShellConfig() == 'I':         
                inc = 1.0      

            x_0 = 100
            x_1 = 240
            x_i = self.getIQ()
            prob = inc/(x_1-x_0)*(x_i-x_0)

            # apply immo effects 
            immoBaseEffects = {
                1: [10, 5, 2],
                2: [20, 10, 5],
                3: [40, 20, 10]
            }
            prefImpact = {"subpar": 0.5, "basic": 1.0, "advanced": 1.5, "military grade": 2.5, "elite": 3.5}

            immoImpact = 0.0
            botGen = self.parts["cortex"].cat
            immoEquipped = []
            for i in [x for x in self.addOns if x and "addonimmo" in x.ID.lower()]: # all non empty slots
                immoImpact -= immoBaseEffects[i.cat][botGen-1]*prefImpact[i.pref]/100.0

            prob = capValue(prob+immoImpact, 0.0, 1.0)

            return(round(prob,3))
        # =========== CR-0379 END ==========================

    class SexbotPart():
        def __init__(self, ID, name, art, kind, slot, cat, description, price, tags, attr):
            # common data
            self.ID = ID
            self.name = name 
            self.kind = kind
            self.slot = slot 
            self.art = art
            self.description = description
            self.cat = cat # generation
            self.pref = False
            self.price = price
            self.tags = tags 
            self.attr = attr  
            self.identified = False
            # instance data / modifiable
            self.wear = 0.0 # to 1.0


        def update(self): # part scrubber to update master data of legacy parts
            # shell updates
            if self.isShell() and self.slot != "shell":
                self.slot = "shell"
            # price updates
            if self.attr["BaseID"] in GAME.sbPartsData.keys():
                self.price = GAME.sbPartsData[self.attr["BaseID"]].price
            return()

        def isMatch(self, x): # for parts filter
            # addon skill
            if x in ["ANA", "ORA", "VAG", "BON", "SPE"]:
                if x in self.attr.keys():
                    return(True) 
            # addon perv 
            elif x in ["PER"]:
                if "SBPAddOnPER" in self.ID:
                    return(True) 
            # addon iq
            elif x in ["IQ"]:
                if "SBPAddOnIQ" in self.ID:
                    return(True) 
            # addon immo
            elif x in ["Immo"]:
                if "SBPAddOnImmo" in self.ID:
                    return(True) 
            # addon shell
            elif x in ["Shell"]:
                if self.slot == "shell":
                 return(True) 
            # addon cube
            elif x in ["Cube"]:
                if self.slot == "cube":
                 return(True) 

            return(False)



        def isShell(self): # I, C, K, ... or False
            if "sbpshell" in self.ID.lower():
                return(self.attr["BaseID"][-1])
            return(False)

        def getAddOnType(self): # aaa
            if "sbpaddonskill" in self.ID.lower():
                return(self.attr["skill"])
            elif "sbpaddonper" in self.ID.lower():
                return("PER")
            elif "sbpaddonimmo" in self.ID.lower():
                return("IMMO")
            elif "sbpaddoniq" in self.ID.lower():
                return("IQ")
            elif "sbpshell" in self.ID.lower():
                return("SHELL")
            return(None) # no addon

        def getBasePart(self):
            if self.getAttr("BaseID"):
                return(GAME.sbPartsData[self.getAttr("BaseID")])
            return(None)

        def getAttr(self, k):
            if k in self.attr.keys():
                return(self.attr[k])
            return(None)

        def getPath(self):
            return("03_minigames/")

        def getSound(self):
            return("equip.wav")

        def getDesc(self):
            return(self.desc)

        def getIco(self):
            return("ico {}.png".format(self.attr["BaseID"]))

        def getKey(self):
            return(self.kind+self.ID)

        def getPrice(self):
            # add more detailed bonus impacts WIP
            
            # calculate price
            prefVal = {"subpar": 0.5, "basic": 1.0, "advanced": 1.5, "military grade": 2.5, "elite": 3.5}
            price = self.price * self.cat * (1.1-self.wear) * prefVal[self.pref] 
            return(int(price))

        # =====  CR-0394 START 
        def getName(self, para=[]):
            quali = ""                             
            if "addonskill" in self.ID.lower():        
                quali = " "+self.ID[13]            
            if "col" in para:
                name = "{}{} - {}, {}".format(self.name, quali, self.colorize("Gen "+str(self.cat)), self.colorize(self.pref.title()))
            else:
                name = "{}{} - Gen {}, {}".format(self.name, quali, self.cat, self.pref.title())
            return(name)
        # ===== CR-0394 END

        def colorize(self, expr):
            expr = str(expr)
            exprLookup = expr.lower()
            accent = "white"
            C_B, C_E = C_WH_B, C_WH_E 
            baskets = {
                "gold": ["advanced"],
                "orange": ["military grade", "3", "gen 3"],
                "bright blue": ["1", "gen 1"],
                "grey": ["subpar"],
                "green": ["2", "gen 2"],
                "white": ["basic"],
                "pink": ["elite"]
            }
            for k in baskets.keys():
                if exprLookup in baskets[k]:
                    accent = k
            C_B = ACCENTS[accent][0]
            C_E = ACCENTS[accent][1]    

            return(C_B+expr+C_E)

        def getTalentBoost(self, talent):
            if "ALL" in self.attr.keys():
                return(self.attr["ALL"])
            if talent in self.attr.keys():
                return(self.attr[talent])
            return(0)

        def getIQBoost(self):
            if "IQ" in self.attr.keys():
                return(self.attr["IQ"])
            return(0)

        def getBoostOneLiner(self):
            result = []
            if self.getIQBoost():
                result.append("IQ +{}".format(self.getIQBoost()))
            if "ALL" in self.attr.keys():
                result.append("ALL +{}".format(self.getTalentBoost("ALL")))
            else:        
                for k in GAME.sbData["skillDict"].keys():
                    if self.getTalentBoost(k):
                        result.append("{} +{}".format(k, self.getTalentBoost(k)))
            result = " | ".join(result)
            return(result)

        def getMods(self):
            mods = ""
            if self.getIQBoost():
                mods += "IQ: +{}".format(self.getIQBoost()) 
            for k in GAME.sbData["skillDict"].keys():
                if self.getTalentBoost(k):
                    mods += "{}: +{}".format(k, self.getTalentBoost(k)) 
            return(mods)

        def rollCube(self, bot):
            result = []

            if self.attr["BaseID"] == "SBPCubeAttitude":
                attitudeNew = random.choice(["compliant"]+["indifferent"]*3+["rebellious"]*3+["devious"]*2+["insidious"])
                bot.attitude = attitudeNew
                result = ["Attitude", attitudeNew ]

            elif self.attr["BaseID"] == "SBPCubeTraits":
                traitPairs = random.sample(GAME.sbData["traits"],2) 
                trait1 = traitPairs[0][random.choice([0,1])]
                trait2 = traitPairs[1][random.choice([0,1])]
                # adjust sexbot traits 
                bot.traits = []
                bot.traits.append(trait1)
                bot.traits.append(trait2)

                # adjust sexbot likes / dislikes
                bot.likes = set()
                bot.dislikes = set()
                likes = 1
                dislikes = 1
                if "perverted" in bot.traits:
                    likes += 2
                elif "frigid" in bot.traits:
                    likes -= 1
                    dislikes += 2
                bot.likes.update(random.sample(GAME.sbData["likes"], likes))
                bot.dislikes.update(random.sample(GAME.sbData["dislikes"], dislikes))
                for i in bot.likes:
                    if i in bot.dislikes:
                        bot.dislikes.remove(i)
                # like/dislike adjustments
                if all([x in bot.traits for x in ["frigid", "aggressive"]]):
                    for k in bot.talents.keys():
                        bot.talents[k] = capValue(bot.talents[k]-random.randint(1,20),0,100)
                if all([x in bot.traits for x in ["flexible", "submissive"]]):
                    bot.likes.add("bondage")
                    bot.talents["BON"] = capValue(bot.talents["BON"]+40,0,100)
                if all([x in bot.traits for x in ["perverted", "submissive"]]):
                    bot.likes.add("oral")
                    bot.talents["BON"] = capValue(bot.talents["BON"]+20,0,100)
                if all([x in bot.traits for x in ["curious"]]):
                    bot.likes.add("special")
                if all([x in bot.traits for x in ["perverted"]]):
                    bot.talents["SPE"] = capValue(bot.talents["SPE"]+20,0,100)
                    bot.talents["ANA"] = capValue(bot.talents["ANA"]+20,0,100)
                if any([x in bot.traits for x in ["conservative", "wholesome"]]):
                    bot.likes.discard("special")
                    bot.likes.discard("anal")

                # trait adjustments 
                if "inflexible" in bot.traits:
                    bot.talents["BON"] = min(bot.talents["BON"], 20)
                    bot.skills["BON"] = min(bot.talents["BON"], bot.skills["BON"])     

                result = ["Traits", [trait1, trait2] ]

            elif self.attr["BaseID"] == "SBPCubePersonality":
                personalities = ["celebrity"]*5+["concubine"]*5+["palace mistress"]*3
                if self.pref in ["advanced","military grade"]:
                    personalities += ["voronian concubine"]*2+ ["princess"]*3
                if self.pref in ["military grade"]:
                    personalities += ["hive queen"]
                personalityNew = random.choice(personalities)

                result = ["Personality", personalityNew] 
                bot.personality = personalityNew 

            return(result)

        def clone(self, attr={}): # rolling of new bot part from template
            # copy data
            ID = "{}-{}".format(self.ID, GAME.getUniqueID())
            name = str(self.name)
            art = str(self.art)
            kind = self.kind  
            slot = self.slot 
            cat = self.cat
            description = str(self.description)
            price = self.price 
            clone = SexbotPart(ID, name, art, kind, slot, cat, description, price, [], {})
            clone.attr["BaseID"] = self.ID

            # cube case
            if self.slot == "cube":
                clone.pref = random.choice(["basic"]*6+["advanced"]*3+["military grade"])
                clone.wear = 0.0
                return(clone)

            # normal case
            if "pref" in attr.keys():
                clone.pref = attr["pref"]
            elif "prefs" in attr.keys():
                clone.pref = random.choice(attr["prefs"])
            elif self.ID in ["SBPCortexPre", "SBPCubeAttitude", "SBPCubePersonality", "SBPCubeTraits"] :
                clone.pref = random.choice(["basic"]*4+["advanced"]*2+["military grade"])                
            else: 
                clone.pref = random.choice(["subpar"]*8+["basic"]*4+["advanced"]+["military grade"])

            # some random settings
            prefVal = {"subpar": 0.5, "basic": 1.0, "advanced": 1.4, "military grade": 2.5, "elite": 3.5} 
            C_S = 0.18
            C_BASE = {1: 2.0, 2: 5.0, 3: 12}[clone.cat]

            # Base parts
            if kind == "sexbot parts":
                if "wear" in attr:
                    clone.wear = attr["wear"]
                else:
                    clone.wear = random.uniform(0.0,1.0) 

            # Various addon types
            elif self.ID in ["SBPAddOnIQ", "SBPAddOnIQ2", "SBPAddOnIQ3"]:
                clone.attr["IQ"] = 1 + int( C_BASE*(prefVal[clone.pref]+ abs(random.gauss(0,C_S))))
            elif "SBPAddOnSkill" in self.ID:
                skill = self.attr["skill"]
                clone.attr[skill] = 1 + int(5*C_BASE*(prefVal[clone.pref]+ abs(random.gauss(0,C_S))))
            elif self.ID in ["SBPAddOnPER", "SBPAddOnPER2","SBPAddOnPER3" ]:
                clone.attr["ALL"] = 2 + int(C_BASE * (prefVal[clone.pref] + abs(random.gauss(0,C_S))))
            elif self.ID in ["SBPAddOnImmo","SBPAddOnImmo2","SBPAddOnImmo3"]:
                clone.attr["ESC"] = 1 + int(1.0*(prefVal[clone.pref]+ abs(random.gauss(0,C_S))))

            return(clone)

        def getBaseID(self):
            if "baseID" not in self.attr.keys():
                return(self.ID)
            return(self.attr["baseID"])

        def getDesc(self):
            desc = "{}".format(self.description)   
            return(desc)
        
        def getGrade(self):
            return(int((1.0-self.wear)*100))

#####################################################################
#					INITIALIZE								
#####################################################################
label init_sexbots():
    python:
        #####################################################################
        #                Sexbot Data
        #####################################################################
        # ===== # CR-0848 START
        GAME.sbData = {
            "names": ['Aera', 'Aeryn', 'Algis', 'Alia', 'Alice', 'Alicia', 'Alisa', 'Alita', 'Alpha', 'Alsatia', 'Alsephina', 'Alya', 'Amaxia', 'Amelia', 'AnRyu', 'Andrea', 'Andromeda', 'Annalee', 'Anoka', 'Apollonia', 'April', 'Aquila', 'Arale', 'Arcee', 'Argenta', 'Arya', 'Ashley', 'Aster', 'Astrid', 'Athena', 'Atreya', 'Aura', 'Aurora', 'Auryn', 'Ava', 'Azure', 'Barbarella', 'Bella', 'Bellatrix', 'Betty', 'Bobbie', 'Buffy', 'Cameron', 'Carol', 'Cassandra', 'Cassopeia', 'Catherine', 'Celeste', 'Celia', 'Chacha', 'Chalmers', 'Cherry', 'Chii', 'Chrome', 'Cinder', 'Clade', 'Clare', 'Cloud', 'Copella', 'Cora', 'Cortina', 'Cybel', 'Danae', 'Danica', 'Darci', 'Dee', 'Demi', 'Diana', 'Dilara', 'Dina', 'Doctus', 'Doria', 'Dorothy', 'Dot', 'Dural', 'EDI', 'Eilon', 'Elanthe', 'Electra', 'Elektra', 'Elise', 'Elita', 'Elly', 'Elora', 'Elsa', 'Emma', 'Enigma', 'Eris', 'Estella', 'Eve', 'Ezri', 'Feel', 'Felice', 'Felicity', 'Fox', 'Freya', 'G2', 'Galatea', 'Galaxina', 'Gally', 'Haladay', 'Harumi', 'Helen', 'Helena', 'Helene', 'Hollow', 'Honey', 'Ida', 'Ilfa', 'Ilia', 'Inara', 'Indigo', 'Indra', 'Iris', 'Isabeau', 'Jacie', 'Jana', 'Jen', 'Jessica', 'Jin May', 'Joanna', 'Jocasta', 'Julie', 'Kosma', 'Kaelera', 'Karinka', 'Kay Em', 'Kheri', 'Kiku', 'KoRyu', 'Kurumi', 'Kyoko', 'Lal', 'Lana', 'Landaree', 'Leeloo', 'Lenore', 'Lesli', 'Lisa', 'Losira', 'Lucy', 'M4GAN', 'Mahoro', 'Maia', 'Maria', 'Mary', 'May', 'Maya', 'Meissa', 'Melfina', 'Mercury', 'Mia', 'Molly', 'Mona', 'Morgan', 'NAN', 'Naomi', 'Nashia', 'Nell', 'Nereid', 'Nerys', 'Neutronia', 'Nightbird', 'Nila', 'Niska', 'Nono', 'Nuku', 'Octavia', 'Olga', 'Olimpia', 'Oyza', 'Padme', 'Penny', 'Petra', 'Phantasma', 'Piper', 'Pixal', 'Pixie', 'Pris', 'Quicksilver', 'Rachael', 'Rayna', 'Reese', 'Rhoda', 'Ria', 'Riley', 'Robecca', 'Robotica', 'Rommie', 'Rosie', 'Ruatha', 'Ruth', 'Rya', 'Saki', 'Samantha', 'Sammi', 'Sari', 'Serenity', 'Serio', 'Seven', 'Sharon', 'Sibel', 'Sidra', 'Six', 'Skuld', 'Solty', 'Sonya', 'Sorsha', 'Sparkle', 'Stella', 'Strella', 'Sulla', 'Sylvia', 'Syrix', 'T-69', 'T-elos', 'Talitha', 'Tallulah', 'Tauriel', 'Teela', 'TenRyu', 'Tima', 'Tina', 'Titania', 'Tyela', 'Umbra', 'Valeria', 'Valerie', 'Vanessa', 'Vela', 'Verda', 'Vivienne', 'Xena', 'Xenia', 'Ycari', 'Ysabell', 'Yumeni', 'Yuria', 'Yvaine', 'Zamara', 'Zaniah', 'Zazie', 'Zephyra', 'Zhora'],
        # ===== # CR-0848 END



            "traits": [ ["nympho", "frigid", "playful"], ["submissive", "aggressive", "pet"], ["flexible", "inflexible", "active"], ["perverted", "wholesome", "explorative"], ["curious", "conservative", "calculating"]],
            "likes": ["anal", "oral", "vaginal", "bondage", "special"],
            "dislikes": ["anal", "oral", "vaginal", "bondage", "special"],
            "skillDict": {"ANA": "anal", "ORA": "oral", "VAG": "vaginal", "BON": "bondage", "SPE": "special"},
            "attitudes": ["compliant"]*3+ ["indifferent"]*3 +["rebellious"]*2 + ["devious"]*2 +["insidious"],
            "personalities" : ["delinquent", "housewife", "colonist", "politician", "merchant", 
                "business woman", "athlete", "teacher", "student", "explorer", "space marine", "cop"]*4 
                +   ["executive", "holo starlet", "actress"]*3
                +   ["socialite","celebrity", "supermodel"]*2
                +   ["princess"]
        }

        #####################################################################
        #                Part Data
        #####################################################################
        partsdata = [
        ["SBPCortex1", "Cortical Unit", "A", "sexbot parts", "cortex", 1, "A data processing unit providing levels of reasoning and environmental interaction", 4500, [],{"LVL": 5}],
        ["SBPHead1", "Head", "A", "sexbot parts", "head", 1, "A duralloy articulated head with syntheskin cover", 2000, [],{"LVL": 5}],
        ["SBPArms1", "Arms", "A", "sexbot parts", "arms", 1, "Articulated limbs for environmental interaction", 2000, [],{"LVL": 5}],
        ["SBPLegs1", "Legs", "A", "sexbot parts", "legs", 1, "Articulated limbs for locomotion and other purposes", 2000, [],{"LVL": 5}],
        ["SBPTorso1", "Torso", "A", "sexbot parts", "torso", 1, "A syntheskin covered torso unit providing all required interfaces for assembly and use", 3000, [],{"LVL": 5}],

        ["SBPCortex2", "Cortical Unit", "A", "sexbot parts", "cortex", 2, "A data processing unit providing levels of reasoning and environmental interaction", 8000, [],{"LVL": 5}],
        ["SBPHead2", "Head", "A", "sexbot parts", "head", 2, "A duralloy articulated head with syntheskin cover", 4000, [],{"LVL": 5}],
        ["SBPArms2", "Arms", "A", "sexbot parts", "arms", 2, "Articulated limbs for environmental interaction", 4000, [],{"LVL": 5}],
        ["SBPLegs2", "Legs", "A", "sexbot parts", "legs", 2, "Articulated limbs for locomotion and other purposes", 4000, [],{"LVL": 5}],
        ["SBPTorso2", "Torso", "A", "sexbot parts", "torso", 2, "A syntheskin covered torso unit providing all required interfaces for assembly and use", 6000, [],{"LVL": 5}],        

        ["SBPCortex3", "Cortical Unit", "A", "sexbot parts", "cortex", 3, "A data processing unit providing levels of reasoning and environmental interaction", 15000, [],{"LVL": 7}],
        ["SBPHead3", "Head", "A", "sexbot parts", "head", 3, "A duralloy articulated head with syntheskin cover", 8000, [],{"LVL": 7}],
        ["SBPArms3", "Arms", "A", "sexbot parts", "arms", 3, "Articulated limbs for environmental interaction", 8000, [],{"LVL": 7}],
        ["SBPLegs3", "Legs", "A", "sexbot parts", "legs", 3, "Articulated limbs for locomotion and other purposes", 8000, [],{"LVL": 7}],
        ["SBPTorso3", "Torso", "A", "sexbot parts", "torso", 3, "A syntheskin covered torso unit providing all required interfaces for assembly and use", 12000, [],{"LVL": 7}],        

        ["SBPCortexPre", "Precursor Cortex", "A", "sexbot parts", "cortex", 3, "A data processing unit assembled by the precursors. Providing superiopr levels of reasoning and environmental interaction", 25000, ["no_find"],{"LVL": 9}],

        ["SBPCubePersonality", "Personality Cube", "A", "sexbot parts", "cube", 3, "A data cube allowing for precise modification of precursor cortex personality properties.", 10000, ["no_find"],{"LVL": 9}],

        ["SBPCubeAttitude", "Attitude Cube", "A", "sexbot parts", "cube", 3, "A data cube allowing for precise modification of precursor cortex attitude properties.", 10000, ["no_find"],{"LVL": 9}],

        ["SBPCubeTraits", "Traits Cube", "A", "sexbot parts", "cube", 3, "A data cube allowing for precise modification of precursor cortex trait properties.", 10000, ["no_find"],{"LVL": 9}],
     

        # New Gen1 Shells
        ["SBPShell1TypeJ", "Type 'J' Shell", "A", "sexbot addons", "shell", 1, "A personalized bot shell kit for visual customization.", 15000, ["no_find", "no_sell"],{"LVL": 5}],
        ["SBPShell1TypeV", "Type 'V' Shell", "A", "sexbot addons", "shell", 1, "A Voronian bot shell kit for visual customization.", 15000, ["no_find"],{"LVL": 5, "personality": "voronian concubine"}],
        ["SBPShell1TypeL", "Type 'L' Shell", "A", "sexbot addons", "shell", 1, "A Supermodel shell kit for visual customization.", 20000, ["no_find"],{"LVL": 5, "personality": "supermodel"}],
        ["SBPShell1TypeN", "Type 'N' Shell", "A", "sexbot addons", "shell", 1, "A Ni'Larian Temptress shell for visual customization.", 17000, ["no_find"],{"LVL": 5, "personality": "temptress"}],
        ["SBPShell1TypeK", "Type 'K' Shell", "A", "sexbot addons", "shell", 1, "A Krell Palace Mistress shell kit for visual bot customization.", 17000, ["no_find"],{"LVL": 5, "personality": "palace mistress"}],
        ["SBPShell1TypeC", "Type 'C' Shell", "A", "sexbot addons", "shell", 1, "A Krell Concubine shell kit for visual bot customization.", 12000, ["no_find"],{"LVL": 5, "personality": "krell concubine"}],
        ["SBPShell1TypeI", "Infiltrator Shell", "A", "sexbot addons", "shell", 1, "A militarized bot shell kit with unique properties. Use at your own risk.", 40000, ["no_find", "no_sell"],{"LVL": 9}],        
        ["SBPShell1TypeP", "Protector", "A", "sexbot addons", "shell", 1, "A specialized bot shell kit with unique properties. Use at your own risk.", 20000, ["no_find", "no_sell"],{"LVL": 9}],  

        # New Gen 2 Shells
        ["SBPShell2TypeJ", "Type 'J' Shell", "A", "sexbot addons", "shell", 2, "A personalized bot shell kit for visual customization.", 25000, ["no_find", "no_sell"],{"LVL": 5}],
        ["SBPShell2TypeV", "Type 'V' Shell", "A", "sexbot addons", "shell", 2, "A Voronian bot shell kit for visual customization.", 30000, ["no_find"],{"LVL": 5, "personality": "voronian concubine"}],
        ["SBPShell2TypeL", "Type 'L' Shell", "A", "sexbot addons", "shell", 2, "A Supermodel shell kit for visual customization.", 40000, ["no_find"],{"LVL": 5, "personality": "supermodel"}],
        ["SBPShell2TypeN", "Type 'N' Shell", "A", "sexbot addons", "shell", 2, "A Ni'Larian Temptress shell for visual customization.", 35000, ["no_find"],{"LVL": 5, "personality": "temptress"}],
        ["SBPShell2TypeK", "Type 'K' Shell", "A", "sexbot addons", "shell", 2, "A Krell Palace Mistress shell kit for visual bot customization.", 35000, ["no_find"],{"LVL": 5, "personality": "palace mistress"}],
        ["SBPShell2TypeC", "Type 'C' Shell", "A", "sexbot addons", "shell", 2, "A Krell Concubine shell kit for visual bot customization.", 25000, ["no_find"],{"LVL": 5, "personality": "krell concubine"}],
        ["SBPShell2TypeI", "Infiltrator Shell", "A", "sexbot addons", "shell", 2, "A militarized bot shell kit with unique properties. Use at your own risk.", 60000, ["no_find", "no_sell"],{"LVL": 9}],  
        ["SBPShell2TypeP", "Protector", "A", "sexbot addons", "shell", 2, "A specialized bot shell kit with unique properties. Use at your own risk.", 40000, ["no_find", "no_sell"],{"LVL": 9}],  


        # New Gen3 Shells
        ["SBPShell3TypeJ", "Type 'J' Shell", "A", "sexbot addons", "shell", 3, "A personalized bot shell kit for visual customization.", 55000, ["no_find", "no_sell"],{"LVL": 5}],
        ["SBPShell3TypeV", "Type 'V' Shell", "A", "sexbot addons", "shell", 3, "A Voronian bot shell kit for visual customization.", 50000, ["no_find"],{"LVL": 5, "personality": "voronian concubine"}],
        ["SBPShell3TypeL", "Type 'L' Shell", "A", "sexbot addons", "shell", 3, "A Supermodel shell kit for visual customization.", 70000, ["no_find"],{"LVL": 5, "personality": "supermodel"}],
        ["SBPShell3TypeN", "Type 'N' Shell", "A", "sexbot addons", "shell", 3, "A Ni'Larian Temptress shell for visual customization.", 60000, ["no_find"],{"LVL": 5, "personality": "temptress"}],
        ["SBPShell3TypeK", "Type 'K' Shell", "A", "sexbot addons", "shell", 3, "A Krell Palace Mistress shell kit for visual bot customization.", 60000, ["no_find"],{"LVL": 5, "personality": "palace mistress"}],
        ["SBPShell3TypeC", "Type 'C' Shell", "A", "sexbot addons", "shell", 3, "A Krell Concubine shell kit for visual bot customization.", 50000, ["no_find"],{"LVL": 5, "personality": "krell concubine"}],
        ["SBPShell3TypeI", "Infiltrator Shell", "A", "sexbot addons", "shell", 3, "A militarized bot shell kit with unique properties. Use at your own risk.", 80000, ["no_find", "no_sell"],{"LVL": 9}],  
        ["SBPShell3TypeP", "Protector", "A", "sexbot addons", "shell", 3, "A specialized bot shell kit with unique properties. Use at your own risk.", 70000, ["no_find", "no_sell"],{"LVL": 9}],  

        # Gen 1
        ["SBPAddOnIQ", "IQ Booster", "A", "sexbot addons", "addon", 1, "A positron module, which enhances cognitive abilities", 3000, [],{"LVL": 5}],
        ["SBPAddOnImmo", "Immobilizer", "A", "sexbot addons", "addon", 1, "A module, which effectively suppresses escape tendencies", 1500, [],{"LVL": 8}],            
        ["SBPAddOnSkillANA", "Talent Booster", "A", "sexbot addons", "addon", 1, "A module, which improves a bot's anal talents", 3000, [],{"LVL": 5, "skill": "ANA"}],
        ["SBPAddOnSkillORA", "Talent Booster", "A", "sexbot addons", "addon", 1, "A module, which improves a bot's oral talents", 3000, [],{"LVL": 5, "skill": "ORA"}],
        ["SBPAddOnSkillVAG", "Talent Booster", "A", "sexbot addons", "addon", 1, "A module, which improves a bot's vaginal talents", 3000, [],{"LVL": 5, "skill": "VAG"}],          
        ["SBPAddOnSkillBON", "Talent Booster", "A", "sexbot addons", "addon", 1, "A module, which improves a bot's bondage talents", 3000, [],{"LVL": 5, "skill": "BON"}],              
        ["SBPAddOnSkillSPE", "Talent Booster", "A", "sexbot addons", "addon", 1, "A module, which improves a bot's special talents", 3000, [],{"LVL": 5, "skill": "SPE"}],   
        ["SBPAddOnPER", "Perversion Amp", "A", "sexbot addons", "addon", 1, "A module, which improves all of a bot's talents", 3000, [],{"LVL": 5}], # CR-0543

        # Gen 2
        ["SBPAddOnIQ2", "IQ Booster", "A", "sexbot addons", "addon", 2, "A positron module, which enhances cognitive abilities", 4500, [],{"LVL": 5}],
        ["SBPAddOnImmo2", "Immobilizer", "A", "sexbot addons", "addon", 2, "A module, which effectively suppresses escape tendencies", 3000, [],{"LVL": 8}],            
        ["SBPAddOnSkillANA2", "Talent Booster", "A", "sexbot addons", "addon", 2, "A module, which improves a bot's anal talents", 4500, [],{"LVL": 5, "skill": "ANA"}],
        ["SBPAddOnSkillORA2", "Talent Booster", "A", "sexbot addons", "addon", 2, "A module, which improves a bot's oral talents", 4500, [],{"LVL": 5, "skill": "ORA"}],          
        ["SBPAddOnSkillVAG2", "Talent Booster", "A", "sexbot addons", "addon", 2, "A module, which improves a bot's vaginal talents", 4500, [],{"LVL": 5, "skill": "VAG"}],          
        ["SBPAddOnSkillBON2", "Talent Booster", "A", "sexbot addons", "addon", 2, "A module, which improves a bot's bondage talents", 4500, [],{"LVL": 5, "skill": "BON"}],              
        ["SBPAddOnSkillSPE2", "Talent Booster", "A", "sexbot addons", "addon", 2, "A module, which improves a bot's special talents", 4500, [],{"LVL": 5, "skill": "SPE"}],   
        ["SBPAddOnPER2", "Perversion Amp", "A", "sexbot addons", "addon", 2 , "A module, which improves all of a bot's talents", 4500, [],{"LVL": 5}], # CR-0543

        # Gen 3
        ["SBPAddOnIQ3", "IQ Booster", "A", "sexbot addons", "addon", 3, "A positron module, which enhances cognitive abilities", 10000, [],{"LVL": 8}],
        ["SBPAddOnImmo3", "Immobilizer", "A", "sexbot addons", "addon", 3, "A module, which effectively suppresses escape tendencies", 10000, [],{"LVL": 8}],            
        ["SBPAddOnSkillANA3", "Talent Booster", "A", "sexbot addons", "addon", 3, "A module, which improves a bot's anal talents", 10000, [],{"LVL": 8, "skill": "ANA"}],
        ["SBPAddOnSkillORA3", "Talent Booster", "A", "sexbot addons", "addon", 3, "A module, which improves a bot's oral talents", 10000, [],{"LVL": 8, "skill": "ORA"}],          
        ["SBPAddOnSkillVAG3", "Talent Booster", "A", "sexbot addons", "addon", 3, "A module, which improves a bot's vaginal talents", 10000, [],{"LVL": 8, "skill": "VAG"}],          
        ["SBPAddOnSkillBON3", "Talent Booster", "A", "sexbot addons", "addon", 3, "A module, which improves a bot's bondage talents", 10000, [],{"LVL": 8, "skill": "BON"}],              
        ["SBPAddOnSkillSPE3", "Talent Booster", "A", "sexbot addons", "addon", 3, "A module, which improves a bot's special talents", 10000, [],{"LVL": 8, "skill": "SPE"}],   
        ["SBPAddOnPER3", "Perversion Amp", "A", "sexbot addons", "addon", 3 , "A module, which improves all of a bot's talents", 10000, [],{"LVL": 8}], # CR-0543


        ]
        GAME.sbPartsData = {}
        for i in partsdata:
            GAME.sbPartsData[i[0]] = SexbotPart(i[0],i[1],i[2],i[3],i[4], i[5], i[6], i[7], i[8], i[9])
    return()

#####################################################################
#					SUPPORT ROUTINES											
#####################################################################

label bot_roll_order(n = 1):
    $ orders = [
        ["A bot with all components being Generation 1 parts", 
        "all([x.cat == 1 for x in bot.parts.values()])",
        "bot.isComplete()"],
        ["A bot with all components being Generation 2 parts", 
        "all([x.cat == 2 for x in bot.parts.values()])",
        "bot.isComplete()"],
        ["A bot with all skills at least 70 points", 
        "all([x >= 70 for x in bot.skills.values()])",
        "bot.isComplete()"],
        ["A bot with at least three expert level skills (90+ points)", 
        "[x >= 70 for x in bot.skills.values()].count(True) >= 3",
        "bot.isComplete()"],
        ["A bot with IQ of 130 or higher and the 'Perversion' trait", 
        "'perverted' in bot.traits", "bot.getIQ() >= 130",
        "bot.isComplete()"],
        ["A bot that likes anal and bondage", 
        "'anal' in bot.likes", "'bondage' in bot.likes",
        "bot.isComplete()"],
        ["A bot with full military grade component quality", 
        "all([x.pref == 'military grade' for x in bot.parts.values()])",
        "bot.isComplete()"],
        ["Any kind of bot configuration", 
        "bot.isComplete()"]
    ]  
    while n:
        $n -= 1
        $ GAME.lab.orders += [random.choice(orders)] 
        $ GAME.lab.orders = GAME.lab.orders[:3] # max 3 in queue
    return()

label sexbot_arcade_earnings():
    $ earnings = 0 
    $ best = False
    python:
        for bot in GAME.arcade:
            # earnings adjustment factor
            fac = 1.0 
            if any([x.wear == 1.0 for x in bot.parts.values()]):
                fac = 0.0

            # earnings apply
            bot.earnedToday = 0
            # bot.earnedToday += int(bot.getPrice() * random.uniform(0.03,0.05) * fac) # CR-0592
            bot.earnedToday += int(bot.getPrice() * random.uniform(0.015,0.022) * fac) # CR-0592
            if best:
                if best.earnedToday < bot.earnedToday:
                    best = bot
            else:
                best = bot 
            earnings += bot.earnedToday  
            bot.tallyUp("Arcade Earnings",bot.earnedToday)
            GAME.mc.tallyUp("Arcade Earnings",bot.earnedToday)

            # bot wear effect apply
            for i in bot.parts.values():
                i.wear = min(1.0, i.wear + random.uniform(0.03,0.1))

            # repair bonus
            if "INV_ARCADE" in GAME.investments:
                for i in bot.parts.values():
                    i.wear = 0.0


    if earnings:
        queue sound "audio/notify.wav"
        call large_notify("ARCADE REPORT", "Your Arcade Bots have earned a total of {:,} Cr. Your best earner was {} with {:,} Cr.".format(earnings, C_SU_B+best.name+C_SU_E, best.earnedToday), ["Continue"], "03_minigames/ico_BotArcade.png", "pink") from _call_large_notify_57
        call transaction("Arcade Earnings", earnings, True) from _call_transaction_32
    if GAME.mc.getTally("Arcade Earnings") > 100000 and "arcade tycoon" not in GAME.mc.achievements: 
        call achievement_notify(GAME.mc, "arcade tycoon", "20_gallery/gallery 21.jpg") from _call_achievement_notify_12        
        $ GAME.galleryAdd(21)
    return()

label sexbot_autotrainer():
    # Init 
    $ complete = []
    # execute training on auto bots 
    python:
        for i in GAME.lab.getCompleted():
            if i.autoTrain:
                skillBefore = sum(i.skills.values()) 
                for k in i.skills.keys():
                    i.train(k, "auto")
                    i.train(k, "auto")
                    i.train(k, "auto")
                skillAfter = sum(i.skills.values())  
                if (skillBefore < skillAfter) and (skillAfter >= sum(i.getTalents().values())):
                    complete.append(i) # training finished

                
    # report on completed bots
    while complete:
        $ bot = complete.pop(0)
        play sound "audio/success.wav"
        call large_notify("AUTOTRAIN COMPLETE", "The auto-train unit has completed the training of {0}.\n\nShe is now ready to be sold or rented out.".format(C_OR_B+bot.name+C_OR_E), ["Continue"], "03_minigames/ico_BotAuto.png", "orange") from _call_large_notify_58 
        $ GAME.mc.tallyUp("Autotrained Bots", 1)
        if GAME.mc.getTally("Autotrained Bots") == 1: # WIP
            call achievement_notify(GAME.mc, "autotrainer", "20_gallery/gallery 23.jpg") from _call_achievement_notify_13
            $ GAME.galleryAdd(23)
    return()

label sexbot_escape():
    $ escapees = []
    $ arcadeEscapees = []

    python:
        for bot in GAME.lab.getCompleted():
            if random.uniform(0.01,1.0) < bot.getEscapeProb():
                if not (bot.getShellConfig() == "I" and GAME.questSys.inProgress('QID_KRELL_YLYEE_WAIT')): # quest exception
                    escapees.append(bot) 
        for bot in GAME.arcade:
            if random.uniform(0.01,1.0) < bot.getEscapeProb():
                arcadeEscapees.append(bot) 

    while escapees:
        $ GAME.ship.destroySubunit()
        $ bot = escapees.pop(0)
        #scene room_lab_smart
        queue sound "audio/red alert.wav"
        call large_notify("SEXBOT ESCAPE!", "Owing to {0}'s supreme intelligence and your negligence in securing her, {0} has escaped. She has damaged critical ship components in the process.".format(C_RE_B+bot.name+C_RE_E), ["Continue"], "03_minigames/ico SBPCortex2.png", "red") from _call_large_notify_59   
        $ GAME.lab.clearBot(bot)       

    while arcadeEscapees:
        $ fine = random.randint(80,350)*100
        $ bot = arcadeEscapees.pop(0)
        #scene room_lab_smart
        queue sound "audio/notify.wav"
        call large_notify("ARCADE ESCAPE!", "An arcade operator informs you, that owing to your negligence in securing her, {0} has escaped from the arcade. She has damaged arcade equipment worth {1:,} Cr in the process.".format(C_RE_B+bot.name+C_RE_E, fine), ["Continue"], "03_minigames/ico SBPCortex2.png", "red") from _call_large_notify_60   
        $ GAME.arcade.remove(bot)       

    return()
   
#####################################################################
#					LAB MANAGER TERMINAL		
#####################################################################
screen mg_lab_manager_scr(*args):
    image "05_gui/mask 50.png"
    $ lmMode = args[0]
    $ lmModes = args[1]
    $ partsFilter = args[2]
    $ yadj = args[3] # parts viewport manager y-pos
    $ LIST_Y = 260
    $ currentmoney = GAME.money

    $ markup = 1.0 
    if lmMode == "special offers":
        $ C_MARKUP = 7.0

    #####################################################################
    #						GUI
    #####################################################################
    
    text C_HI_B+"{}".format(lmMode.upper())+C_HI_E size FSIZE_LARGE pos 20, 90
    text C_HI_B+"Money: [currentmoney:,] Cr"size 30 xpos 1775 ypos 75 xalign 1.0

    #hbox pos 440, 150 spacing 10:
    hbox pos 1060, 70 spacing 10 xalign 0.5:
        for i in lmModes[:-1]:
            textbutton C_WH_B+"{}".format(i.title())+C_WH_E action Return(["mode", i]) text_style "text_std"
            text " | " yoffset 5
        textbutton C_WH_B+"{}".format(lmModes[-1].title())+C_WH_E action Return(["mode", lmModes[-1]]) text_style "text_std"        

    #####################################################################
    #                EXIT BUTTON
    #####################################################################
    frame xsize 350 ysize 64 pos 0,900 background Solid("#00000000") padding 0,0:
        image "05_gui/gui_menu_option.png" yalign 0.5
        button action Return(False)xpos 20
        textbutton "Exit (X)" yalign 0.5 action Return(False) xpos 20 # CR-0587
        key "x" action Return (False)
        key "X" action Return (False) 

    $ tooltip = GetTooltip()
    if tooltip and tooltip in parts:
        frame padding 20,20 xsize 560 ysize 920 pos 40 , 200 background Solid("#39210000"):
            image "08_items/ico bg1.png" zoom 0.5 xpos 50 ypos 100
            image "{}/{}".format(tooltip.getPath(),tooltip.getIco()) zoom 0.5 xpos 50 ypos 100
            vbox xsize 400 ypos 420 xpos -25 spacing 20:
                text tooltip.getName() xalign 0.5 size FSIZE
                text tooltip.getDesc() xalign 0.5 size FSIZE
                if tooltip.getBoostOneLiner():
                    text tooltip.getBoostOneLiner() xalign 0.5 size FSIZE
                text "Value: {:,} Cr".format(int(tooltip.getPrice())) xalign 0.5 size FSIZE

    #####################################################################
    #					PARTS STORAGE		
    #####################################################################
    if lmMode == "parts storage":
        $ parts = GAME.lab.parts
        text C_HI_B+"({:,} Units in Stock)".format(len(GAME.lab.parts))+C_HI_E size FSIZE_LARGE pos 40, 150
        hbox pos 1087, 115 xalign 0.5 spacing 5:
            for i in [1,2,3]:
                if partsFilter["cat"][i]:
                    $ bCol = "#0099ccff"
                else:
                    $ bCol = "#888888ff"
                frame xsize 100 ysize 30 background Solid (bCol):
                    text "Gen {}".format(i) xalign 0.5 yalign 0.5 size FSIZE_MED
                    button action Return(["filter", "cat", i])
            text " : " size FSIZE yalign 0.5
            for i in ["parts", "addons"]:
                if partsFilter["kind"]["sexbot "+i]:
                    $ bCol = "#0099ccff"
                else:
                    $ bCol = "#888888ff"
                frame xsize 100 ysize 30 background Solid (bCol):
                    text "{}".format(i.title()) xalign 0.5 yalign 0.5 size FSIZE_MED
                    button action Return(["filter", "kind", "sexbot "+i])
            text " : " size FSIZE yalign 0.5
            for i in ["subpar", "basic", "advanced", "military grade", "elite"]:
                if partsFilter["prefs"][i]:
                    $ bCol = "#0099ccff"
                else:
                    $ bCol = "#888888ff"
                frame xsize 140 ysize 30 background Solid (bCol):
                    text i.title() xalign 0.5 yalign 0.5 size FSIZE_MED # CR-0298
                    button action Return(["filter", "prefs", i])

        side "c r":
            area 475,160,1420,875
            viewport id "vp":
                draggable True
                mousewheel True
                yadjustment yadj

                vbox pos 500,120 xsize 1200 spacing 5:
                    for i in parts:
                        $ showPart = True
                        $ showPart = partsFilter["prefs"][i.pref] and partsFilter["cat"][i.cat] and partsFilter["kind"][i.kind] 

                        if showPart:
                            frame xsize 1228 ysize 68 background Solid("#0099cc88"):
                                image "{}{}".format("22_bot_thumb/",i.getIco()) zoom 0.75 xpos 20 yalign 0.5
                                text i.getName() size FSIZE yalign 0.5 xpos 160
                                text "{}".format(percentColorized(i.getGrade())[0]) size FSIZE yalign 0.5 xpos 620 
                                text "{:,} Cr".format(i.getPrice()) size FSIZE yalign 0.5 xpos 850 xalign 1.0
                                button action NullAction() tooltip i hover_background Solid("#00ccff22") ysize 68 
                                
                                $ framexsize = 50
                                if "Immobilizer" in i.name:
                                    $ framexsize = 80
                                frame xsize framexsize ysize 30 background None yalign 0.5 xpos 950 yoffset -20:

                                    if i.getBoostOneLiner():
                                        #if "IQ" in i.getBoostOneLiner()
                                        text i.getBoostOneLiner() xalign 0.5 xoffset 15 yoffset 5 text_align 0.5 size FSIZE


                                    elif "Immobilizer" in i.name:

                                        $ immopmod1 = str(int(immoBaseEffects[i.cat][0]*prefImpact[i.pref]/-1))

                                        $ immopmod2 = immoBaseEffects[i.cat][1]*prefImpact[i.pref]
                                        if immopmod2 > int(immopmod2):
                                            $ immopmod2 = str(immopmod2/-1)
                                        else:
                                             $ immopmod2 = str(int(immopmod2)/-1)

                                        $ immopmod3 = immoBaseEffects[i.cat][2]*prefImpact[i.pref]
                                        if immopmod3 > int(immopmod3):
                                            $ immopmod3 = str(immopmod3/-1)
                                        else:
                                             $ immopmod3 = str(int(immopmod3)/-1)
                                        text  immopmod1 + "{size=18} %{color=#0FF}  G1\n{/color}{/size}" + immopmod2 + "{size=18} %{color=#0FF}  G2\n{/color}{/size}" + immopmod3 + "{size=18} %{color=#0FF}  G3{/color}{/size}" xalign 1.0 text_align 1.0 size 22 line_spacing -2 yoffset 0 kerning -2
                                        #text immopmod + "%{size=18}{color=#0FF}*\nGen Mod\n1 | .5 | .25{/color}{/size}" xalign 0.5 size FSIZE



                                frame xsize 120 ysize 68 xpos 1230 background Solid("#0099cc88") yoffset -8:
                                    button action Return(["scrap",i]) tooltip i
                                    frame xsize 80 ysize 30 background Solid ("#aa0000") xalign 0.5 ypos 17:                            
                                       text "Scrap" xalign 0.5 yalign 0.5 size FSIZE_MED
                              
                                #frame xsize 80 ysize 30 background Solid ("#aa0000ff") yalign 0.5 xpos 1080:
                                    #button action Return(["scrap",i])
                                    #text "Scrap" xalign 0.5 yalign 0.5 size FSIZE_MED

            vbar value YScrollValue("vp")
        #image "08_items/ico ITMCrateBots.png" pos 40, 400 zoom 0.5

    #####################################################################
    #					SPECIAL OFFERS		
    #####################################################################
    elif lmMode == "special offers":
        $ parts = GAME.lab.specialOffers
    
        side "c r":
            area 475,220,1420,850
            yoffset -60
            viewport id "vp":
                draggable True
                mousewheel True
                
                vbox pos 500,120 xsize 1350 spacing 6:
                    for i in parts:
                        frame xsize 1350 ysize 72 background Solid("#0099cc88"):
                            image "{}{}".format("22_bot_thumb/",i.getIco()) zoom 0.75 xpos 20 yalign 0.5
                            text i.getName(["col"]) size FSIZE yalign 0.5 xpos 160
                            text "{}".format(percentColorized(i.getGrade())[0]) size FSIZE yalign 0.5 xpos 620 
                            text "{:,} Cr".format(int(i.getPrice()*C_MARKUP)) size FSIZE yalign 0.5 xpos 840 xalign 1.0
                            button action NullAction() tooltip i

                            $ framexsize = 50
                            if "Immobilizer" in i.name:
                                $ framexsize = 80
                            frame xsize framexsize ysize 30 background None ypos -2 xpos 900:

                                if i.getBoostOneLiner():
                                    text i.getBoostOneLiner() xoffset 15 text_align 0.5 size FSIZE

                                elif "Immobilizer" in i.name:

                                    $ immopmod1 = str(int(immoBaseEffects[i.cat][0]*prefImpact[i.pref]/-1))

                                    $ immopmod2 = immoBaseEffects[i.cat][1]*prefImpact[i.pref]
                                    if immopmod2 > int(immopmod2):
                                        $ immopmod2 = str(immopmod2/-1)
                                    else:
                                         $ immopmod2 = str(int(immopmod2)/-1)

                                    $ immopmod3 = immoBaseEffects[i.cat][2]*prefImpact[i.pref]
                                    if immopmod3 > int(immopmod3):
                                        $ immopmod3 = str(immopmod3/-1)
                                    else:
                                         $ immopmod3 = str(int(immopmod3)/-1)
                                    text  immopmod1 + "{size=18} %{color=#0FF}  G1{/color}{/size}\n" + immopmod2 + "{size=18} %{color=#0FF}  G2{/color}{/size}\n" + immopmod3 + "{size=18} %{color=#0FF}  G3{/color}{/size}" xalign 1.0 text_align 1.0 size 22 line_spacing -1 yoffset -5 kerning -2

                            frame xsize 80 ysize 30 background Solid ("#0099ccff") yoffset 15 xpos 1050:
                                button action Return(["buy_part", i])
                                text "Buy" xalign 0.5 yalign 0.5 size FSIZE_MED

                            $ partname = i.name
                            $ partsstored = 0
                            for x in GAME.lab.parts:
                                if i.name == x.name and i.cat == x.cat and i.pref == x.pref:
                                    $ partsstored += 1
                            if partsstored > 0:
                                text str(partsstored) xpos 1230 ypos 15 size 36 bold True xalign 1.0
                                text "in parts\nstorage" xpos 1250 ypos 10 size 20 color "#0FF"

            vbar value YScrollValue("vp")
        #image "08_items/ico ITMCrateBots.png" pos 40, 400 zoom 0.5
        
    #####################################################################
    #					ARCADE MANAGER		
    #####################################################################
    elif lmMode == "arcade manager":
        side "c r":
            area 475,220,1420,850
            yoffset -60
            viewport id "vp":
                draggable True
                mousewheel True
                
                vbox pos 500,120 xsize 1300 spacing 5:
                    $ arcadebotcount = 0
                    for i in GAME.arcade:
                        $ arcadebotcount += 1
                        frame xsize 1350 ysize 77 background Solid("#0099cc88"):
                            text str(arcadebotcount) xpos 5 ypos 5 size 30 bold True color "#0CF"
                            hbox yalign 0.5 xpos 5 ypos 50:
                                for s in range(i.getStars()):
                                    image "03_minigames/star.png"  zoom 0.5
                            text i.name size FSIZE yalign 0.5 xpos 100
                            text "{:,} Cr".format(i.getPrice()) size FSIZE yalign 0.5 xpos 420 xalign 1.0
                            vbox xsize 500 xpos 470 yalign 0.5:
                                text i.getShortDesc() yalign 0.5 size FSIZE
                            text "Earnings: {:,} Cr".format(i.getTally("Arcade Earnings")) size FSIZE yalign 0.5 xpos 1125 xalign 1.0
                            # Arcade Operation Mode
                            frame xsize 80 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 1180:
                                button action Return(["arcade_retrieve", i])
                                text "Retrieve" xalign 0.5 yalign 0.5 size FSIZE_MED
                     
            vbar value YScrollValue("vp")
        image "03_minigames/ico_BotArcade.png" pos 40, 400 zoom 0.5


    #####################################################################
    #	                BOT MANAGER						
    #####################################################################
    elif lmMode == "bot manager":

        side "c r":
            area 475,220,1420,850
            yoffset -60
            viewport id "vp":
                draggable True
                mousewheel True
                
                vbox pos 500,120 xsize 1350 spacing 5:
                    for i in [x for x in GAME.lab.cells if x]:
                        if i.isComplete():
                            frame xsize 1350 ysize 77 background Solid("#0099cc88"):
                                hbox yalign 0.5 xpos 10:
                                    for s in range(i.getStars()):
                                        image "03_minigames/star.png"  zoom 0.5
                                text i.name size FSIZE yalign 0.5 xpos 100
                                text "{:,} Cr".format(i.getPrice()) size FSIZE yalign 0.5 xpos 420 xalign 1.0
                                vbox xsize 500 xpos 470 yalign 0.5:
                                    text i.getShortDesc() yalign 0.5 size FSIZE

                                frame xsize 120 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 980:
                                    button action Return(["process", "sex arcade", i])
                                    text "Sex Arcade" xalign 0.5 yalign 0.5 size FSIZE_MED
                                if [order for order in GAME.lab.orders if i in GAME.lab.findBots(order)]:
                                    frame xsize 120 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 1120:
                                        button action Return(["bot_ship", i])
                                        text "Shipping" xalign 0.5 yalign 0.5 size FSIZE_MED    
                                else: 
                                    frame xsize 120 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 1120:
                                        button action Return(["bot_ship", i])
                                        text "Shipping" xalign 0.5 yalign 0.5 size FSIZE_MED
                     
            vbar value YScrollValue("vp")


        image "03_minigames/ico bg1.png" pos 40, 400 zoom 0.5
        image "03_minigames/ico_BotShip.png" pos 40, 400 zoom 0.5    

label mg_lab_manager():
    # initialize
    #show image "room_lab_smart" 
    $ lmModes = ["parts storage", "special offers", "bot manager", "arcade manager"]
    $ lmMode = "parts storage"
    $ partsFilter = {
            "prefs": {"subpar": True, "basic": True, "advanced": True,        
            "military grade": True, "elite": True},
            "cat": {1: True, 2: True, 3: True },
            "kind": {"sexbot parts": True, "sexbot addons": True}  
        }
    $ yadj = ui.adjustment() # to remember ypos in lab manager lists

    # stock special offers of the day (Basic or better)
    python:
        if not GAME.mc.hasDoneToday("special_offers"):
            parts = []
            GAME.lab.specialOffers = []
            n = random.randint(6,10)
            while n >= 0:
                # random stuff
                pFilter = random.choice(["sexbot addons", "sexbot parts", "sexbot parts"])
                if pFilter == "sexbot addons":
                    gFilter = random.choice([1,1,2,2,2,3,3])
                else:
                    gFilter = random.choice([1,2,2,2,2,3,3])

                part = random.choice([x for x in GAME.sbPartsData.values() if x.kind == pFilter and x.cat == gFilter and "no_find" not in x.tags])
                
                # ===== CR-0549 START
                # immobilizer luck roll
                if random.randint(1,100) > 90:
                    part = random.choice([x for x in GAME.sbPartsData.values() if "immo" in x.ID.lower() and x.cat == gFilter and "no_find" not in x.tags])
                # ===== CR-0549 END

                rolledPart = part.clone()
                rolledPart.wear = 0.0
                if rolledPart.pref in ["basic", "advanced", "military grade"]:
                    GAME.lab.specialOffers.append(rolledPart)
                    n -= 1
                

    # presort parts 
    $ GAME.lab.parts.sort(key = lambda x : (x.kind, x.ID, x.price) )

    # manager loop
    while True:
        call screen mg_lab_manager_scr(lmMode, lmModes, partsFilter, yadj) 
        if _return == False:  
            queue sound "audio/click.wav"
            #hide image "room_lab_smart" 
            return() 

        elif "filter" in _return:
            queue sound "audio/click.wav"
            $ fType = _return[1]
            $ fVal = _return[2]
            $ partsFilter[fType][fVal] = not partsFilter[fType][fVal]


        elif "arcade_retrieve" in _return:
            $ bot = _return[1]
            queue sound "audio/notify.wav"
            call large_notify("ARCADE RETRIEVAL", "You notify the arcade of {0}'s retrieval.\n\n{0} is uninstalled and packaged into a bot stasis pod. She will be dropped off tomorrow at your location.".format(C_SU_B+bot.name+C_SU_E), ["Continue"], "03_minigames/ico_BotArcade.png", "pink") from _call_large_notify_61
            queue sound "audio/install.wav"
            $ GAME.inQueuePending.append(["bot", bot, GAME.day+1])
            $ GAME.arcade.remove(bot)
            
        elif "scrap" in _return:
            $ part = _return[1]
            if "no_sell" in part.getBasePart().tags and part.getBasePart().ID not in ["SBPShell1TypeJ", "SBPShell2TypeJ", "SBPShell3TypeJ" ]:
                queue sound "audio/tech_fail.wav"
                mc "\"I should hold on to that part...\""
                mc "\"I might need it later...\""
            else:
                call transaction("Bot Parts Scrap", int(part.getPrice()/2.0) , True) from _call_transaction_33
                $ GAME.lab.parts.remove(part)

        elif "buy_part" in _return:
            $ part = _return[1]
            $ price = part.getPrice()*7 # markup
            if GAME.money < price:
                queue sound "audio/beep.wav"
                call simple_notify("LAB MANAGER", "Insufficient Funds.", ["Continue"]) from _call_simple_notify_81
            else:
                call transaction("Bot Parts Purchases", -price , True) from _call_transaction_34
                $ GAME.lab.specialOffers.remove(part)
                $ GAME.lab.parts.append(part)

        elif "bot_ship" in _return:
            $ bot = _return[1]
            call mg_sexbot_ship(bot) from _call_mg_sexbot_ship

            # $ orders = [x for x in GAME.lab.orders if bot in GAME.lab.findBots(x)]
            # if orders != []:
            #     $ fac = 1.0
            #     $ order = orders.pop(0)
            #     $ GAME.lab.orders.remove(order) 
            # else:
            #     $ fac = 0.5 # fire sale
            # if GAME.getSpaceLoc().ID not in ["LIDPirate", "LIDStationArellarti"] and bot.getIQ() >= 140:
            #     play sound "audio/tech_fail.wav"
            #     call simple_notify("ORDER MANAGER", "Illegal Configuration", ["Continue"])
            #     if not GAME.mc.hasDone("illegal_bot_config"):
            #         queue sound "audio/drone_beep.wav"
            #         bb2 "\"Sexbots with IQ of 140 or higher may not be {i}officially{/i} traded.\""
            #         mc "\"Too, bad, they fetch a nice premium...\""
            #         call quest_updater()
            #     else:
            #         queue sound "audio/drone_beep.wav"
            #         bb2 "\"Remember, high IQ Bots can only be traded at the Pirate Outpost.\""
            #         mc "\"Ah, right.\""
            # else:
            #     call mg_sexbot_sell(bot, fac)

                           
        elif "process" in _return and _return[1] == "sex arcade":
            $ bot = _return[2]
            # $ bot = GAME.lab.current
            if len(GAME.arcade) >= 10:
                queue sound "audio/beep.wav"
                "Lab Manager" "\"Arcade contracting limit reached.\""
            elif bot.getShellConfig() == "I":
                queue sound "audio/notify.wav"
                mc "\"I better not send an Infiltrator to the Arcade...\""
                mc "\"I can imagine a whole number of things going really wrong there...\""
            elif bot.isIllegal() and GAME.getSpaceLoc().ID not in ["LIDPirate", "LIDStationArellarti"]:
                queue sound "audio/beep.wav"
                "Lab Manager" "\"Bot configuration illegal. Arcade prohibited.\""
                queue sound "audio/drone_beep.wav"
                bb2 "\"Remember, high IQ Bots can only be traded at the Pirate Outpost.\""
                mc "\"Ah, right.\""
            elif bot.isIllegal() and GAME.questSys.isDone("QID_PIRATE_CONTACT"):
                $ GAME.mc.tallyUp("Arcade Deliveries/P",1)
                $ GAME.arcade.append(bot)
                $ GAME.lab.clearBot(bot)
                #$ GAME.lab.clearLab()
                queue sound "audio/install.wav"
                call large_notify("SEX ARCADE", "You install {} on an arcade platform and prepare her for shipping to an Illegal Sex Arcade.\n\nEarnings will be credited to your account once per day.".format(bot.name), ["Continue"], "03_minigames/ico_BotArcade.png") from _call_large_notify_62              
                $ GAME.mc.hasDone("arcade_delivery")
            else:
                $ GAME.mc.tallyUp("Arcade Deliveries",1)
                $ GAME.arcade.append(bot)
                $ GAME.lab.clearBot(bot)
                # $ GAME.lab.clearLab()
                queue sound "audio/install.wav"
                call large_notify("SEX ARCADE", "You install {} on an arcade platform and prepare her for shipping to a Sex Arcade.\n\nEarnings will be credited to your account once per day.".format(bot.name), ["Continue"], "03_minigames/ico_BotArcade.png") from _call_large_notify_63
                $ GAME.mc.hasDone("arcade_delivery")

            call quest_updater() from _call_quest_updater_178  

        elif "mode" in _return:
            queue sound "audio/click.wav"
            $ lmMode = _return[1]

    return()           

#####################################################################
#					PROCESS BOTS		
#####################################################################

label mg_sexbot_to_arcade(bot):
    if len(GAME.arcade) >= 10:
        queue sound "audio/beep.wav"
        "Lab Manager" "\"Arcade contracting limit reached.\""
    elif bot.isIllegal() and GAME.getSpaceLoc().ID != "LIDPirate":
        queue sound "audio/beep.wav"
        "Lab Manager" "\"Bot configuration illegal. Arcade prohibited.\""
        queue sound "audio/drone_beep.wav"
        bb2 "\"Remember, high IQ Bots can only be traded at the Pirate Outpost.\""
        mc "\"I forgot.\""        
    elif bot.isIllegal() and GAME.questSys.isDone("QID_PIRATE_CONTACT"):
        $ GAME.mc.tallyUp("Arcade Deliveries/P",1)
        $ GAME.arcade.append(bot)
        $ GAME.lab.clearLab()
        queue sound "audio/install.wav"
        call large_notify("SEX ARCADE", "You install {} on an arcade platform and prepare her for shipping to an Illegal Sex Arcade.\n\nEarnings will be credited to your account once per day.".format(bot.name), ["Continue"], "03_minigames/ico_BotArcade.png") from _call_large_notify_64              
        $ GAME.mc.hasDone("arcade_delivery")
    else:
        $ GAME.mc.tallyUp("Arcade Deliveries",1)
        $ GAME.arcade.append(bot)
        # $ GAME.lab.clearLab()
        queue sound "audio/install.wav"
        call large_notify("SEX ARCADE", "You install {} on an arcade platform and prepare her for shipping to a Sex Arcade.\n\nEarnings will be credited to your account once per day.".format(bot.name), ["Continue"], "03_minigames/ico_BotArcade.png") from _call_large_notify_65
        $ GAME.mc.hasDone("arcade_delivery")
    call quest_updater() from _call_quest_updater_179        
    return()

label mg_sexbot_ship(bot):
    queue sound "audio/install.wav"
    call large_notify("BOT PACKAGING", "You package {}{}{} into a Stasis Pod and transfer the pod into the Cargo Bay for shipping.\n\nYou can sell and ship her out via the Cargo Manager.".format(C_HI_B, bot.name,C_HI_E), ["Continue"]) from _call_large_notify_66
    $ GAME.ship.packBot(bot, "Shipping")
    $ GAME.lab.getBotFromCell(bot)
    queue sound "audio/load.wav"
    return()

label mgsb_quality_control(): #qc for current bot 

    # init
    $ TEC_MAX = 20
    $ qEffectDict = {"A": 20, "N": 25}
    $ qGalleryDict = {"A": 110, "N": 122}

    $ qAni = "ani_qc_bot_a"
    $ qAniDict = {"A": "ani_qc_bot_a", "N": "ani_qc_bot_n"}
    $ shellType = GAME.lab.current.getShellConfig()

    # shell available for qc?
    if shellType in qEffectDict.keys() and shellType in qAniDict.keys():
        $ TEC_MAX = qEffectDict[shellType] 
        $ qAni = qAniDict[shellType]
        $ GAME.galleryAdd(qGalleryDict[shellType])

    if shellType in ["A", "N"]:
        # play qc cutscene 
        "You prepare [C_HI_B][GAME.lab.current.name][C_HI_E] for Quality Control Testing..."
        scene expression("10_anims/ani_qc_bot_{}_00.jpg".format(GAME.lab.current.getShellConfig().lower())) with fade
        window hide 
        pause
        scene expression(qAni)
        window hide 
        pause
        # assess effect 
        #$ skillKey = random.choice(GAME.lab.current.skills.keys())
        $ qc_amount = max(0, GAME.mc.getAttr("TEC") - GAME.lab.current.qc)

        scene room_lab_smart with fade
        if qc_amount:
            # notify
            $ msgName = "[C_HI_B][GAME.lab.current.name][C_HI_E]"
            $ msgAmount = "[C_HI_B][qc_amount][C_HI_E]"

            queue sound "audio/success.wav"
            call large_notify("QUALITY CONTROL RESULT", "The Quality Control Procedure for {0} was successful.\n\nSelect Skill to apply {1} Bonus Points!".format(msgName, msgAmount),["ANA", "ORA", "VAG", "SPE", "BON"], "08_items/ico ITMSpanner.png", "blue", ["narrow_button"]) from _call_large_notify_67

            queue sound "audio/success.wav"


            $ skillKey = _return
            python:
                GAME.lab.current.qc += qc_amount   
                GAME.lab.current.talents[skillKey] += qc_amount 
                GAME.lab.current.skills[skillKey] += qc_amount 

            call simple_notify("SKILL UP - {}".format(GAME.lab.current.name), "{} +{}".format(skillKey, msgAmount),["Continue"], "08_items/ico ITMSpanner.png", "blue") from _call_simple_notify_82

            $ GAME.mc.incAttrFrac("TEC", 0.5, TEC_MAX)
            
                
        else:
            queue sound "audio/notify.wav"
            "Your TEC skills were not sufficient for further Skill increase of [C_SU_B][GAME.lab.current.name][C_SU_E]."
        queue sound "audio/install.wav"

    # bot shell not allowed
    else:
        "Lab Manager" "\"Quality Control Function not available.\""
    return()

#####################################################################
#					STORAGE PART / BOT SELECTION	
#####################################################################
screen mg_sexbot_part_storage_scr(*args):
    $ parts = args[0]
    #$ parts = GAME.lab.parts
    $ partsFilter2 = args[1]
    text C_HI_B+"PARTS STORAGE"+C_HI_E size FSIZE_LARGE pos 40, 40

    if not shellslot:
        if addon_buttons:
          text "Talent Boosters" pos 1350, 10 xalign 0.5 size FSIZE_MED
          hbox pos 1140, 40 xalign 0.5 spacing 5:
            if togglefilters:
                 $ bCol = "#888888ff"
            else:
                $ bCol = "#0099ccff"
            frame xsize 120 ysize 30 background Solid(bCol):
                text "Toggle All" xalign 0.5 yoffset 1 size FSIZE_MED
                #$ i = partsFilter2["modules"]:
                button action (ToggleVariable("togglefilters", true_value=True, false_value=False), SetDict(partsFilter2['modules'], 'IQ', togglefilters), SetDict(partsFilter2['modules'], 'Immo', togglefilters), SetDict(partsFilter2['modules'], 'ANA', togglefilters), SetDict(partsFilter2['modules'], 'BON', togglefilters), SetDict(partsFilter2['modules'], 'ORA', togglefilters), SetDict(partsFilter2['modules'], 'SPE', togglefilters), SetDict(partsFilter2['modules'], 'VAG', togglefilters), SetDict(partsFilter2['modules'], 'PER', togglefilters))
            text " > " size FSIZE yalign 0.5

            for i in ["IQ", "Immo", "ANA", "BON", "ORA", "SPE", "VAG", "PER"]:
                if partsFilter2["modules"][i]:
                    $ bCol = "#0099ccff"
                else:
                     $ bCol = "#888888ff"
                frame xsize 120 ysize 30 background Solid(bCol):
                    text i xalign 0.5 yoffset 1 size FSIZE_MED
                    button action Return(["filter", "modules", i])
                if i == "Immo":
                    text " : " size FSIZE yalign 0.5

        hbox pos 1140, 80 xalign 0.5 spacing 5:

            if togglefilters2:
                 $ bCol = "#888888ff"
            else:
                $ bCol = "#0099ccff"
            frame xsize 120 ysize 30 background Solid(bCol):
                text "Toggle All" xalign 0.5 yoffset 1 size FSIZE_MED
                #$ i = partsFilter2["modules"]:
                button action (ToggleVariable("togglefilters2", true_value=True, false_value=False), SetDict(partsFilter2['cat'], 1, togglefilters2), SetDict(partsFilter2['cat'], 2, togglefilters2), SetDict(partsFilter2['cat'], 3, togglefilters2), SetDict(partsFilter2['prefs'], 'subpar', togglefilters2), SetDict(partsFilter2['prefs'], 'basic', togglefilters2), SetDict(partsFilter2['prefs'], 'advanced', togglefilters2), SetDict(partsFilter2['prefs'], 'military grade', togglefilters2))
            text " > " size FSIZE yalign 0.5

            for i in [1,2,3]:
                if partsFilter2["cat"][i]:
                    $ bCol = "#0099ccff"
                else:
                    $ bCol = "#888888ff"
                frame xsize 100 ysize 30 background Solid (bCol):
                    text "Gen {}".format(i) xalign 0.5 yoffset 1 size FSIZE_MED
                    button action Return(["filter", "cat", i])
            text " : " size FSIZE yalign 0.5
            for i in ["subpar", "basic", "advanced", "military grade"]:
                if partsFilter2["prefs"][i]:
                    $ bCol = "#0099ccff"
                else:
                    $ bCol = "#888888ff"
                frame xsize 140 ysize 30 background Solid (bCol):
                    text i.title() xalign 0.5 yoffset 1 size FSIZE_MED # CR-0298
                    button action Return(["filter", "prefs", i])

    frame pos 475, 125 xsize 1350 ysize 40 background Solid("#0099cc88"):
        text "DESCRIPTION" xpos 160  size FSIZE
        text "VALUE" xpos 990  size FSIZE
        if addon_buttons:
            text "MODIFIER" xpos 705  size FSIZE
        elif not shellslot:
            text "CONDITION" xpos 700  size FSIZE

    side "c r":
        area 475,175,1420,835
        viewport id "vp":
            draggable True
            mousewheel True
            
            vbox pos 500,120 xsize 1350 spacing 5:
                for i in parts:
                  $ showPart = False
                  if shellslot:
                      if "Shell" in (i.name):
                          $ showPart = True
                  else:
                      if "Shell" in (i.name):
                          $ showPart = False
                      else:
                          if "sexbot addons" in (i.kind):
                          #$ showPart = True
                              $ showPart = partsFilter2["cat"][i.cat] and partsFilter2["prefs"][i.pref]
                              for x in {"IQ", "Immo", "ANA", "BON", "ORA", "SPE", "VAG", "PER"}:
                                   if x in (i.ID):
                                       if partsFilter2["modules"][x] == False:
                                           $showPart = False
                          else:
                              $ showPart = partsFilter2["cat"][i.cat] and partsFilter2["prefs"][i.pref]

                  if showPart:
                    fixed xsize 1350 ysize 65:
                      frame xsize 1228 ysize 65 background Solid("#0099cc88"):
                        if addon_buttons or shellslot:
                            image "{}{}".format(i.getPath(),i.getIco()) zoom 0.25 xpos 20 yalign 0.5
                        else:
                            image "{}{}".format(i.getPath(),i.getIco()) zoom 0.18 xpos 20 yalign 0.5
                        text i.getName() size FSIZE yalign 0.5 xpos 160
                        if not addon_buttons and not shellslot:
                            text "{}".format(percentColorized(i.getGrade())[0]) size FSIZE yalign 0.5 xalign 1.0 xpos 765 
                        text "{:,} Cr".format(i.getPrice()) size FSIZE yalign 0.5 xpos 1080 xalign 1.0
                        if not bot.hasComp("cortex") and partsKey != "cortex":
                            button action NullAction() tooltip i
                        else:
                            button action Return(i) tooltip i

                        if bot.hasComp("cortex"):
                            #frame xsize 80 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 965:
                                #button action Return(i)
                                #text "Use" xalign 0.5 yalign 0.5 size FSIZE_MED

                            frame xsize 80 ysize 30 background None yalign 0.5 xpos 710 yoffset -15:

                                if i.getBoostOneLiner():
                                    text i.getBoostOneLiner()[:3] xalign 0.5 size FSIZE xoffset 5
                                    text i.getBoostOneLiner()[3:] xalign 0.5 size FSIZE yoffset 24


                                elif "Immobilizer" in i.name: #bot.addOns[i].name:
                                    $ botGen = bot.parts["cortex"].cat
                                    $ immopmod = immoBaseEffects[i.cat][botGen-1]*prefImpact[i.pref]
                                    if immopmod > int(immopmod):
                                        $ immopmod = str(immopmod/-1)
                                    else:
                                         $ immopmod = str(int(immopmod)/-1)
                                    text " Risk\n" + immopmod + "%" xalign 0.5 size FSIZE

                      frame xsize 120 ysize 65 xpos 1230 background Solid("#0099cc88"):
                          button action Return(["scrap",i])
                          frame xsize 80 ysize 30 background Solid ("#aa0000") xalign 0.5 ypos 12:                            
                              text "Scrap" xalign 0.5 yalign 0.5 size FSIZE_MED

        vbar value YScrollValue("vp")



    $ tooltip = GetTooltip()
    if tooltip and tooltip in parts:
        frame padding 20,20 xsize 560 ysize 920 pos 40 , 200 background Solid("#39210000"):
            image "08_items/ico bg1.png" zoom 0.5 xpos 50 ypos 100
            image "{}/{}".format("03_minigames/",tooltip.getIco()) zoom 0.5 xpos 50 ypos 100
            vbox xsize 400 ypos 420 xpos -25 spacing 20:
                text tooltip.getName() xalign 0.5 size FSIZE
                text tooltip.getDesc() xalign 0.5 size FSIZE
                if tooltip.getBoostOneLiner():
                    text tooltip.getBoostOneLiner() xalign 0.5 size FSIZE
                text "Value: {:,} Cr".format(tooltip.getPrice()) xalign 0.5 size FSIZE

    #####################################################################
    #                EXIT BUTTON
    #####################################################################
    frame xsize 350 ysize 64 pos 0,900 background Solid("#00000000") padding 0,0:
        image "05_gui/gui_menu_option.png" yalign 0.5
        button action Return(False)xpos 20
        textbutton "Exit (X)" yalign 0.5 action Return(False) xpos 20  # CR-0587
        key "x" action Return (False)
        key "X" action Return (False)            

label mg_sexbot_part_storage(partsKey):  
    show image "room_lab_smart" 
    $ interactive = True
    #$ pFilter = {}

    $ GAME.lab.parts.sort(key = lambda x : (x.kind, x.ID, x.price) )

    #$ parts = sorted(parts, key=lambda x: x.ID, reverse = True)    

    while interactive:
        if partsKey == "addon":
            $ parts = [x for x in GAME.lab.parts if x.slot == "addon"]
        elif partsKey == "shell":
            $ parts = [x for x in GAME.lab.parts if x.slot == "shell"]
        elif partsKey == "cube":
            $ parts = [x for x in GAME.lab.parts if x.slot == "cube"]
        else:
            $ parts = [x for x in GAME.lab.parts if x.slot == partsKey]
        $ parts = sorted(parts, key=lambda x: x.ID, reverse = True)

        call screen mg_sexbot_part_storage_scr(parts, partsFilter2)
        if _return == False:
            $ interactive = False

        elif _return in parts:
            $ interactive = False

        elif "scrap" in _return:
            $ part = _return[1]
            call transaction("Bot Parts Scrap", part.getPrice() , True) from _call_transaction_35
            $ GAME.lab.parts.remove(part)

        elif "filter" in _return:
            queue sound "audio/click.wav"
            $ fType = _return[1]
            $ fVal = _return[2]
            $ partsFilter2[fType][fVal] = not partsFilter2[fType][fVal]

    hide image "room_lab_smart"
    # hide image  "05_gui/mask 50.png"
    return(_return)

#####################################################################
#					BOT STORAGE MANAGER					       	
#####################################################################
  
screen mg_sexbot_bot_storage_scr(*args):

    $ mode = args[0] # store / multiverse
    text C_HI_B+"BOT STORAGE"+C_HI_E size FSIZE_LARGE pos 960, 80 xalign 0.5

    side "c r":
        area 440,240,1420,684
        viewport id "vp":
            draggable True
            mousewheel True
            scrollbars "vertical" 
            
            vbox pos 500,120 xsize 1300 spacing 5:
                for i in [x for x in GAME.lab.cells if x]:
                    frame xsize 1300 ysize 80 background Solid("#0099cc88"):
                        hbox yalign 0.5 xpos 20:
                            for s in range(i.getStars()):
                                image "03_minigames/star.png"  zoom 0.5
                        text i.name size FSIZE yalign 0.5 xpos 140
                        text "{}".format({True: "Complete", False: "Unfinished"}[i.isComplete()]) size FSIZE yalign 0.5 xpos 260
                        vbox xsize 500 xpos 400 yalign 0.5:
                            text i.getShortDesc() yalign 0.5 size FSIZE
                        text "{:,} Cr".format(i.getPrice()) size FSIZE yalign 0.5 xpos 960 xalign 1.0
                        # LAB Operation Mode
                        if mode == "store":
                            frame xsize 80 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 1000:
                                button action Return(["config_bot", i])
                                text "Config" xalign 0.5 yalign 0.5 size FSIZE_MED
                            frame xsize 80 ysize 30 background Solid ("#cc6600ff") yalign 0.5 xpos 1200:
                                button action Return(["scrap_bot",i])
                                text "Scrap" xalign 0.5 yalign 0.5 size FSIZE_MED
                        # Export Bot Mode
                        else:
                            frame xsize 80 ysize 30 background Solid ("#0099ccff") yalign 0.5 xpos 1000:
                                button action Return(["export_bot", i])
                                text "Export" xalign 0.5 yalign 0.5 size FSIZE_MED                        

        vbar value YScrollValue("vp")

    #####################################################################
    #                EXIT BUTTON
    #####################################################################
    frame xsize 350 ysize 64 pos 0,900 background Solid("#00000000") padding 0,0:
        image "05_gui/gui_menu_option.png" yalign 0.5
        button action Return(False)xpos 20
        textbutton "Exit (X)" yalign 0.5 action Return(False) xpos 20 # CR-0587
        key "x" action Return (False)
        key "X" action Return (False)            

label mg_sexbot_bot_storage(mode="store"):

    show image "room_lab_smart"
    # show image  "05_gui/mask 50.png"
    $ interactive = True
    while interactive:
        call screen mg_sexbot_bot_storage_scr(mode)
        if _return == False:
            $ interactive = False

        elif "config_bot" in _return:
            $ newBot = _return[1]
            $ GAME.lab.storeBot(GAME.lab.current)
            $ GAME.lab.retrieveBot(newBot)
            queue sound "audio/load.wav"
            $ interactive = False

        # elif "sell_bot" in _return:
        #     $ bot = _return[1]
        #     call mg_sexbot_sell(bot)

        elif "export_bot" in _return:
            $ bot = GAME.lab.getBotFromCell(_return[1])
            return(bot)


    hide image "room_lab_smart"
    # hide image  "05_gui/mask 50.png"
    return(False)

#####################################################################
#					CONFIGURATION SCREENS		
#####################################################################
    
screen mg_sexbot_prep_scr():
    image "lab_bg"
    # image "03_minigames/mgsb_bg.png"
    # Bot composite image 
    $ bot = GAME.lab.current
    if bot.isComplete():
        image "full_bot" # completed bot
    else:    
        image "bot_doll" # gray doll

    #####################################################################
    #					DATA PANEL LEFT								  #
    #####################################################################
    # Bot Data
    vbox spacing 10 pos 50, 40:
        text C_HI_B+"BOT POTENTIAL"+C_HI_E 
        image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5
        hbox:
            for i in range(bot.getStars()):
                image "03_minigames/star.png" 

    vbox spacing 10 pos 50, 150:
        text C_HI_B+"DESIGNATION"+C_HI_E 
        image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5
        if bot.hasComp("cortex"):
            text "{}".format(bot.name)
        else:
            text "None"

    vbox spacing 10 pos 50, 260:
        text C_HI_B+"VALUE"+C_HI_E 
        image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5
        if bot.hasComp("cortex"):
            text "{:,} Cr".format(bot.getPrice())
        else:
            text "n/a"

    # Parts Data Fixed
    
    $ pPosY = [40, 160, 280, 440, 940]
    for i in range(5):

        vbox xsize 200 spacing 10 pos 600, pPosY[i]:
            text C_HI_B+bot.partNames[i].upper()+C_HI_E xalign 0.0 xoffset 108
            # image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5
            if bot.hasComp(bot.partNames[i]):
                $ part = bot.parts[bot.partNames[i]]
                frame xsize 140 ysize 40 xalign 1.0 background Solid ("#00000000"):
                    text "{}".format( percentColorized(part.getGrade())[0] ) xalign 1.0 xoffset 5 yoffset -8

    #####################################################################
    #					DATA PANEL RIGHT		  					  
    #####################################################################
    frame xsize 1080 ysize 1080 background Solid ("ffffff00") xalign 1.0: 
        text C_HI_B+"BOT CONFIGURATION & TRAINING"+C_HI_E xalign 0.5 ypos 20 size FSIZE_LARGE
        image "05_gui/px_1x1_orange.png" xsize 800 ysize 3 xalign 0.5 ypos 80

        # Lab charging status 
        $ labRes = ["ITMPodBio", "ITMPodEnergy", "ITMPodNano", "ITMOrePromethium", "ITMAICore", "ITMAICoreElite"]
        $ labResTips = {
            "ITMPodBio": "Used to repair bot parts. Consumed by repair.",
            "ITMPodEnergy": "Used to train bots. Consumed by training.", 
            "ITMPodNano": "Used to repair bot parts. Consumed by repair.",
            "ITMOrePromethium": "Required for Hypertrain. Click Hypertrain in left menu. Consumed.", 
            "ITMAICore": "Used to upgrade Cortex Gen. Upgrade Cortex in left menu to use. Consumed.", 
            "ITMAICoreElite": "Used to upgrade Gen3 Cortex to Precursor Type. Upgrade Cortex in left menu. Consumed."
            }

        $ labResCount = [GAME.ship.cargo.count(GAME.items[x]) for x in labRes]
        vbox xpos 700 ypos 120 xsize 300 spacing 10:
            text C_HI_B+"LAB RESOURCES"+C_HI_E
            $ gX,gY = 4,2
            grid gX gY spacing 10:
                for i in range(gX*gY):
                    frame xsize 64 ysize 64 background Solid("#0099cc00") padding 3,3:
                        if i < len(labRes):
                            image "08_items/ico {}.png".format(labRes[i]) xalign 0.5 yalign 0.5 zoom 0.2 
                            $ amount = labResCount[i]           
                            if amount:                 
                                text "{}x".format(amount) size FSIZE
                            else:
                                image "05_gui/ico_attention.png" xalign 0.5 yalign 0.5
                            button action NullAction() tooltip labResTips[labRes[i]]
                        else:
                            text " "
            image "05_gui/px_1x1_empty.png" 
            for k in ["Bio","Energy", "Nano"]:
                frame xsize 340 ysize 30 background Solid("#ffffff00"):
                    text "{}".format(k) size FSIZE yalign 0.5
                    bar xsize 120 ysize 20 range 100 value GAME.lab.status[k.lower()] yalign 0.5 xpos 160                       


        # Information     
        vbox xpos 80 ypos 120 spacing 20:
            text C_HI_B+"INFORMATION"+C_HI_E 
            if bot.hasComp("cortex"):
                $ colRange = ["green", "yellow", "orange", "red"]
                text "IQ {} | {} | Escape Risk (%): {}".format(
                    bot.getIQ(), 
                    bot.colorize(bot.attitude.title()), 
                    colMap (bot.getEscapeProb()*100, colRange, [-5, 20])
                    )
                text  "[C_HI_B]TRAITS:[C_HI_E] {}".format(" | ".join(
                    [ bot.colorize(x.title()) for x in bot.traits]))

                text  "[C_HI_B]PERSONALITY:[C_HI_E] {}".format(bot.colorize(bot.personality.title())) 

        # ADD-Ons
        text C_HI_B+"SHELL                             ADD-ON MODULES"+C_HI_E xpos 65 ypos 420

        image "05_gui/px_1x1_orange.png" xsize 3 ysize 64  xpos 175 ypos 490

        hbox xpos 205 ypos 475 spacing 30:
            if bot.hasComp("cortex"):
         
                # standard modules slots #0 to 4
                for i in range(6): # xxx1
                    if i < 5:
                        $ xoff = 0
                    else:
                        $xoff = - 780

                    fixed xsize 96 ysize 96 xoffset xoff:

                        #checking for higher grade and cat parts stuff
                      if bot.addOns[i]:

                        $ addonID = bot.addOns[i].attr["BaseID"]
                        #$ addonID = bot.addOns[i].ID
                        $ addonCat = int(bot.addOns[i].cat)
                        $ addonpreflevel = bot.addOns[i].pref[:8]
                        $ addonprefgrade = ["subpar", "basic", "advanced", "military"]
                        $ addonxpref = addonprefgrade.index(addonpreflevel)

                        $ addons2 = [x for x in GAME.lab.parts if x.attr["BaseID"] == addonID]
                        if addonxpref < 3 and len(addons2) > 0:
                            if "subpar" in bot.addOns[i].pref:
                                $ addonprefupgrade = ["basic", "advanced", "military grade"]
                            if "basic" in bot.addOns[i].pref:
                                $ addonprefupgrade = ["advanced", "military grade"]
                            else: #if addonxpref == 2:
                                $ addonprefupgrade = ["military grade"]

                            $ upgradeaddoncount = 0
                            $ upgradeaddoncountP = 0
                            for addon2 in addons2:
                                if addon2.pref in addonprefupgrade:
                                    $ upgradeaddoncount += 1
                                elif addon2.pref == bot.addOns[i].pref:
                                    $ upgradeaddoncountP += 1

                            if upgradeaddoncount > 0:
                                text "[addonpreflevel!c]" size 22 xalign 1.0 xoffset 15 yoffset 100 kerning 1.25 color "#0F0"
                            elif upgradeaddoncountP > 0:
                                text "[addonpreflevel!c]" size 22 xalign 1.0 xoffset 15 yoffset 100 kerning 1.25 color "#FF0"
                            elif len(addons2) > 0:
                                text "[addonpreflevel!c]" size 22 xalign 1.0 xoffset 15 yoffset 100 kerning 1.25 color "#FA0"
                            else:
                                pass
                        else:
                            text "[addonpreflevel!c]" size 22 xalign 1.0 xoffset 15 yoffset 100 kerning 1.25

                        $ addonprefupgrade2 = [""]
                        if len(addons2) > 0:
                            if "subpar" in bot.addOns[i].pref:
                                $ addonprefupgrade2 = ["basic", "advanced", "military grade"]
                            if "basic" in bot.addOns[i].pref:
                                $ addonprefupgrade2 = ["advanced", "military grade"]
                            else: #if xpref == 2:
                                $ addonprefupgrade2 = ["military grade"]

                        $ addons3 = [x for x in GAME.lab.parts if x.name == bot.addOns[i].name and x.cat > addonCat and x.pref in addonprefupgrade2]
                        $ addons4 = [x for x in GAME.lab.parts if x.name == bot.addOns[i].name and x.cat > addonCat and x.pref not in addonprefupgrade2]

                        if len(addons3) > 0:
                            add "/images/03_minigames/ico_32x32_better.png" xoffset -5 yoffset 97 xsize 24 ysize 16
                            add "/images/03_minigames/ico_32x32_better.png" xoffset -5 yoffset 102 xsize 24 ysize 20
                        elif len(addons4) > 0:
                            $ upgradeaddoncount2 = 0
                            $ upgradeaddoncount2P = 0
                            for addon4 in addons4:
                                if addon4.pref == bot.addOns[i].pref:
                                    $ upgradeaddoncount2P += 1
                                else:   #if addon4.pref not in addonprefupgrade2:
                                    $ upgradeaddoncount2 += 1
                            if upgradeaddoncount2P > 0:
                                add "/images/03_minigames/ico_32x32_better.png" xoffset -5 yoffset 98 xsize 24 ysize 22
                            else:
                                add "/images/03_minigames/ico_32x32_worse.png" xoffset -5 yoffset 98 xsize 24 ysize 22
                        elif len(addons4) < 1 and addonCat < 3:
                            add "/images/05_gui/ico_attention.png" xoffset -15 yoffset 88 xsize 50 ysize 40
                        elif len(addons2) < 1 and addonCat == 3:
                            add "/images/05_gui/ico_attention.png" xoffset -15 yoffset 88 xsize 50 ysize 40
                        else:
                            pass


                      frame xsize 96 ysize 96:
                          if bot.addOns[i]:
                              if bot.addOns[i].cat == 1:
                                  image "03_minigames/ico_gen1fadebg.png" xsize 90 ysize 90 yalign 0.5 xalign 0.5
                              elif bot.addOns[i].cat == 2:
                                  image "03_minigames/ico_gen2fadebg.png" xsize 90 ysize 90 yalign 0.5 xalign 0.5
                              else: #if bot.addOns[i].cat == 3:
                                  image "03_minigames/ico_gen3fadebg.png" xsize 90 ysize 90 yalign 0.5 xalign 0.5
                              image "{}{}".format(bot.addOns[i].getPath(), bot.addOns[i].getIco()) zoom 0.375 xalign 0.5 yalign 0.5 
                              if i == 5:
                                  button action (SetVariable("shellslot", True), SetVariable("addon_buttons", False), Return(["module", 5])) tooltip bot.addOns[5]
                              else:
                                  if bot.addOns[i].getBoostOneLiner():
                                      text bot.addOns[i].getBoostOneLiner()[3:] xalign 0.985 ypos 63 size FSIZE
                                  elif "Immobilizer" in bot.addOns[i].name:
                                      $ botGen = bot.parts["cortex"].cat
                                      $ immomod = immoBaseEffects[bot.addOns[i].cat][botGen-1]*prefImpact[bot.addOns[i].pref]
                                      if immomod > int(immomod):
                                          $ immomod = str(immomod/-1)
                                      else:
                                          $ immomod = str(int(immomod)/-1)
                                      text immomod xalign 0.985 ypos 63 size FSIZE
                                  button action (SetVariable("shellslot", False), SetVariable("addon_buttons", True), Return(["module", i])) tooltip bot.addOns[i]

                          ###if no part assigned
                          else:

                              if i < 5:
                                  $ addons2a = [x for x in GAME.lab.parts if "addon" in x.kind and "Shell" not in x.ID]
                                  if len(addons2a) < 1:
                                      add "/images/03_minigames/AEExclamation_red.png" xsize 96 ysize 98 xpos -8 yalign 0.4
                                      text "{b}{i}NONE{/i}{/b}" size 30 text_align 0.5 xalign 0.5 yalign 0.6 color "#ffff0090"
                                  else:
                                      button action (SetVariable("shellslot", False), SetVariable("addon_buttons", True), Return(["module", i]))

                              elif i == 5:
                                  $ addons2b = [x for x in GAME.lab.parts if "Shell" in x.ID]# and "Shell" in x.name]
                                  if len(addons2b) < 1:
                                      add "/images/03_minigames/AEExclamation_red.png" xsize 96 ysize 98 xpos -8 yalign 0.4
                                      text "{b}{i}NONE{/i}{/b}" size 30 text_align 0.5 xalign 0.5 yalign 0.6 color "#ffff0090"
                                  elif not bot.isComplete():
                                      add "/images/03_minigames/AEExclamation_red.png" xsize 96 ysize 98 xpos -8 yalign 0.4
                                      text "{b}{i}VIEW\nONLY{/i}{/b}" size 30 text_align 0.5 xalign 0.5 yalign 0.6 color "#ffff0090"
                                      button action (SetVariable("shellslot", True), SetVariable("addon_buttons", False), Return(["module", 5]))
                                  else:
                                      button action (SetVariable("shellslot", True), SetVariable("addon_buttons", False), Return(["module", 5]))
                
        # Training
        vbox xpos 80 ypos 640 spacing 20:
            $ hyperMsg, qcMsg = "", ""
            if bot.qc < GAME.mc.getAttr("TEC"):
                $ qcMsg = " - {}QC +{}{}".format(C_SU_B, GAME.mc.getAttr("TEC")-bot.qc,C_SU_E)
            if "hyper-train" in GAME.lab.status.keys():
                if GAME.lab.status["hyper-train"] > 0:
                    $ hyperMsg = " - {}HYPERTRAIN ON{}".format(C_OR_B, C_OR_E, GAME.lab.status["hyper-train"])
            text C_HI_B+"SKILLS{}{}".format(hyperMsg, qcMsg)+C_HI_E 
            if bot.hasComp("cortex"):
                for k in sorted(GAME.sbData["skillDict"].keys()):
                    $ C_B, C_E = "", "" 
                    if bot.talents[k] < bot.getTalents()[k]:
                        $ C_B, C_E = C_GR_B, C_GR_E 
                    frame xsize 600 ysize 40 background Solid("#ffffff00"):
                        if GAME.sbData["skillDict"][k].lower() in bot.dislikes:
                            image "03_minigames/gui_mini_dislike.png" xpos -64 ypos -10
                        elif GAME.sbData["skillDict"][k].lower() in bot.likes:
                            image "03_minigames/gui_mini_heart.png" xpos -64 ypos -10
                        text C_B+GAME.sbData["skillDict"][k].title()+C_E yalign 0.5 
                        if bot.skills[k] >= bot.getTalents()[k]:
                            text "{}".format(bot.skills[k]) xpos 280 xalign 0.5
                            if bot.skills[k] >= 90:
                                text C_SU_B+"{} Expert".format(GAME.sbData["skillDict"][k].title())+C_SU_E xpos 400
                        else:
                            text "{}".format(0) xpos 140 yalign 0.5
                            bar xsize 200 ysize 28 yalign 0.5 xpos 180 value bot.skills[k] range 100
                            text "{}".format(max(100,bot.skills[k])) xpos 400  yalign 0.5
                            # frame xsize 100 ysize 40 xpos 520 background Solid("#0099cc") yalign 0.5:
                            #     text "Train" xalign 0.5 yalign 0.5 size FSIZE 
                            #     button action Return(["train", k])
        
        # Autotrain Frame 
        if bot.isComplete():
            frame xsize 328 ysize 248 pos 700, 705:
                if bot.autoTrain:
                    image "ani_thumb_auto" zoom 0.5 xalign 0.5 yalign 0.5
                    #text "ACTIVE" xalign 0.5 yalign 0.1 size FSIZE 
                    image "05_gui/px_1x1_green.png" xsize 20 ysize 20 
                else:
                    image "10_anims/ani_thumb_auto_00.jpg" zoom 0.5 xalign 0.5 yalign 0.5
                    #text "INACTIVE" xalign 0.5 yalign 0.1 size FSIZE 
    #####################################################################
    #					TOOLTIPS						  
    #####################################################################
    $ tooltip = GetTooltip()
    if tooltip:
        # ressource hint 
        if isinstance(tooltip, str):
            vbox  pos 1300,990 xalign 0.5 spacing 10:
                bar xsize 300 ysize 2 xalign 0.5
                text "{}".format(tooltip) xalign 0.5 size FSIZE
                bar xsize 300 ysize 2 xalign 0.5

         
screen mg_sexbot_scr():
    $ bot = GAME.lab.current

    #####################################################################
    #                EXIT BUTTON
    #####################################################################
    vbox pos 0,960  spacing 20:
        frame xsize 350 ysize 64 background Solid("#00000000") padding 0,0:
            image "05_gui/gui_menu_option.png" yalign 0.5
            button action Return(False)xpos 20
            textbutton "Exit (X)" yalign 0.5 action Return(False) xpos 20 # CR-0587
            key "x" action Return (False)
            key "X" action Return (False)     

    #####################################################################
    #					INTERACTIVE											  #
    #####################################################################
    # bot cells
    vbox spacing 10 pos 50, 390:
        text C_HI_B+"BOT CELLS"+C_HI_E 
        image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5
        grid 5 GAME.lab.cellsMax/5 spacing 5:
            for i in range(GAME.lab.cellsMax):
                if GAME.lab.cells[i]:
                    frame xsize 25 ysize 25 background Solid("#0099ccff") padding 0,0:
                        button action Return(["bot", GAME.lab.cells[i], i]) tooltip GAME.lab.cells[i]
                        if GAME.lab.cells[i].autoTrain:
                            image "/05_gui/px_1x1_green.png" xsize 8 ysize 8
                else:
                    frame xsize 25 ysize 25 background Solid("#00000055") padding 0,0:
                        if GAME.lab.current:
                            if GAME.lab.current.hasComp("cortex"):
                                button action Return(["process", "store", i]) 
                            pass
                        pass
        #image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5 # CR-0323
        #text C_OR_B+"CLICK EMPTY DOT TO STORE"+C_OR_E size FSIZE_MED # CR-0323
 
    #####################################################################
    #					LAB OPTIONS		
    #####################################################################
    $ options = []


    if bot.isComplete() and bot.parts["cortex"].cat == 3 and any([GAME.ship.countCargo(x) for x in ["ITMAICoreElite"]]): # upgrade
        $ options = ["upgrade cortex"] + options 
    elif bot.isComplete() and any([GAME.ship.countCargo(x) for x in ["ITMAICoreElite", "ITMAICore"]]): 
        $ options = ["upgrade cortex"] + options

    if bot.isComplete() and GAME.ship.countCargo("ITMOrePromethium"): # training efficiency +
        $ options = ["hyper-train"] + options 
    $ options = options + ["lab manager"]
    if GAME.lab.current.isComplete() and "INV_YUTANI" in GAME.investments:
        $ options = ["bot analyzer"]+options
    if bot.isComplete():
        $ options = ["quality control"] + options


    vbox ypos 940 yalign 1.0 spacing 20:
        # frame xsize 100 ysize 300
        for i in options:
            frame xsize 350 ysize 64 background Solid("#00000000") padding 0,0:
                image "05_gui/gui_menu_option.png" yalign 0.5
                textbutton i.title() yalign 0.5 action NullAction() xpos 20
                button action Return(["process", i])xpos 20

    #####################################################################
    #					DATA PANEL LEFT								  #
    #####################################################################
    # Change Name / Reroll nme
    if bot.hasComp("cortex"):
        frame xsize 32 ysize 32 pos 220, 146 background Solid ("#00000000") padding 0,0:
            image "03_minigames/gui_mini_pen.png" xalign 0.5 yalign 0.5
            button action Return(["rename", None])
        frame xsize 32 ysize 32 pos 260, 146 background Solid ("#00000000") padding 0,0:
            image "05_gui/gui_mini_reroll.png" xalign 0.5 yalign 0.5
            button action Return(["reroll_name", None]) # xxx

    # Config screen /  Buttons and Active Elements 
    $ pPosY = [40, 160, 280, 440, 940]
    for i in range(5):
        $ parts2a = [x for x in GAME.lab.parts if x.slot == bot.partNames[i]] 
        if bot.hasComp(bot.partNames[i]):
            $ part = bot.parts[bot.partNames[i]]
            frame xsize 96 ysize 96 pos 600, pPosY[i]:
                image "{}{}".format(part.getPath(), part.getIco()) zoom 0.25 xalign 0.5 yalign 0.5
                button action (SetVariable("shellslot", False), SetVariable("addon_buttons", False), Return(["remove", bot.partNames[i]])) tooltip part 

        elif bot.partNames[i] != "cortex":# and bot.hasComp("cortex"):
            frame xsize 96 ysize 96 pos 600, pPosY[i]:      
                if len(parts2a) > 0:
                    if not bot.hasComp("cortex"):
                        add "/images/03_minigames/AEExclamation_red.png" xsize 96 ysize 98 xpos -8 yalign 0.4
                        text "{b}{i}VIEW ONLY{/i}{/b}" size 30 text_align 0.5 xalign 0.5 yalign 0.6 color "#ffff0090"
                    button action (SetVariable("shellslot", False), SetVariable("addon_buttons", False), Return(["add", bot.partNames[i]]))
                else:
                    add "/images/03_minigames/AEExclamation_red.png" xsize 96 ysize 98 xpos -8 yalign 0.4
                    text "{b}{i}NONE{/i}{/b}" size 30 text_align 0.5 xalign 0.5 yalign 0.6 color "#ffff0090"
        elif bot.partNames[i] == "cortex":
            frame xsize 96 ysize 96 pos 600, pPosY[i]:
                if len(parts2a) < 1:
                    add "/images/03_minigames/AEExclamation_red.png" xsize 96 ysize 98 xpos -8 yalign 0.4
                    text "{b}{i}NONE{/i}{/b}" size 30 text_align 0.5 xalign 0.5 yalign 0.6 color "#ffff0090"
                else:
                    button action (SetVariable("shellslot", False), SetVariable("addon_buttons", False), Return(["add", bot.partNames[i]]))


        vbox xsize 200 spacing 10 pos 600, pPosY[i]:
            text " "
            #image "05_gui/px_1x1_orange.png" xsize 200 ysize 3 yalign 0.5
            if bot.hasComp(bot.partNames[i]):
                frame xsize 140 ysize 40 xalign 1.0 background Solid ("#00000000"):
                    # text "{:.0f}%".format(bot.parts[bot.partNames[i]].getGrade()) xalign 1.0
                    if bot.parts[bot.partNames[i]].getGrade() < 100:
                        frame xsize 40 ysize 40 background Solid ("#00000000") ypos -8 xpos 138:
                            image "03_minigames/gui_mini_wrench.png" xalign 0.5 yalign 0.5
                            button action Return(["repair", bot.partNames[i]])

                #frame xsize 32 ysize 32 xoffset -10 yoffset -20:

                $ partID = bot.parts[bot.partNames[i]].attr["BaseID"]
                $ partCat = int(bot.parts[bot.partNames[i]].cat)
                $ preflevel = bot.parts[bot.partNames[i]].pref[:8]
                $  prefgrade = ["subpar", "basic", "advanced", "military"]
                $ xpref = prefgrade.index(preflevel)

                $ parts2 = [x for x in GAME.lab.parts if x.attr["BaseID"] == partID]
                if xpref < 3 and len(parts2) > 0:
                    if "subpar" in bot.parts[bot.partNames[i]].pref:
                        $ prefupgrade = ["basic", "advanced", "military grade"]
                    if "basic" in bot.parts[bot.partNames[i]].pref:
                        $ prefupgrade = ["advanced", "military grade"]
                    else: #if xpref == 2:
                        $ prefupgrade = ["military grade"]

                    $ upgradepartcount = 0
                    $ upgradepartcountP = 0
                    for part2 in parts2:
                        if part2.pref in prefupgrade:
                            $ upgradepartcount += 1
                        elif part2.pref == bot.parts[bot.partNames[i]].pref:
                            $ upgradepartcountP += 1

                    if upgradepartcount > 0:
                        text "[preflevel!c]" size 22 xalign 0.0 xoffset 108 yoffset -10 kerning 1.5 color "#0F0"
                    elif upgradepartcountP > 0:
                        text "[preflevel!c]" size 22 xalign 0.0 xoffset 108 yoffset -10 kerning 1.5 color "#FF0"
                    elif len(parts2) > 0:
                        text "[preflevel!c]" size 22 xalign 0.0 xoffset 108 yoffset -10 kerning 1.5 color "#FA0"
                    else:
                        pass
                else:
                    text "[preflevel!c]" size 22 xalign 0.0 xoffset 108 yoffset -10 kerning 1.5

                $ prefupgrade2 = [""]
                if len(parts2) > 0:
                    if "subpar" in bot.parts[bot.partNames[i]].pref:
                        $ prefupgrade2 = ["basic", "advanced", "military grade"]
                    if "basic" in bot.parts[bot.partNames[i]].pref:
                        $ prefupgrade2 = ["advanced", "military grade"]
                    else: #if xpref == 2:
                        $ prefupgrade2 = ["military grade"]

                $ parts3 = [x for x in GAME.lab.parts if x.name == bot.parts[bot.partNames[i]].name and x.cat > partCat and x.pref in prefupgrade2]
                $ parts4 = [x for x in GAME.lab.parts if x.name == bot.parts[bot.partNames[i]].name and x.cat > partCat and x.pref not in prefupgrade2]

                if len(parts3) > 0:
                    add "/images/03_minigames/ico_32x32_better.png" xoffset 103 yoffset -72 xsize 24 ysize 24
                    add "/images/03_minigames/ico_32x32_better.png" xoffset 103 yoffset -116 xsize 24 ysize 22
                elif len(parts4) > 0:
                    $ upgradepartcount2 = 0
                    $ upgradepartcount2P = 0
                    for part4 in parts4:
                        if part4.pref == bot.parts[bot.partNames[i]].pref:
                            $ upgradepartcount2P += 1
                        else:   #if part4.pref not in prefupgrade2:
                            $ upgradepartcount2 += 1
                    if upgradepartcount2P > 0:
                        add "/images/03_minigames/ico_32x32_better.png" xoffset 103 yoffset -72 xsize 24 ysize 24
                    else:
                        add "/images/03_minigames/ico_32x32_worse.png" xoffset 103 yoffset -72 xsize 24 ysize 24
                    #text "Gen [partCat] [listlen]" size 22 xalign 0.0 xoffset 108 yoffset -55 kerning 1.5
                elif len(parts4) < 1 and partCat < 3:
                    add "/images/05_gui/ico_attention.png" xoffset 85 yoffset -90 xsize 60 ysize 48
                elif len(parts2) < 1 and partCat == 3:
                    add "/images/05_gui/ico_attention.png" xoffset 85 yoffset -90 xsize 60 ysize 48
                else:
                    pass

            else:
                text "n/a" xalign 1.0

    # cortex cube slot 
    if bot.hasComp("cortex"):
        if bot.parts["cortex"].attr["BaseID"] == "SBPCortexPre":
            frame xsize 48 ysize 48 pos 532, pPosY[0]:
                button action (SetVariable("shellslot", False), SetVariable("addon_buttons", False),Return(["add", "cube"]))


    #####################################################################
    #					TRAINING BUTTONS				  
    #####################################################################

    # Training
    vbox xpos 80 ypos 645 spacing 20:
        text  " "
        if bot.isComplete() and not bot.autoTrain:
            for k in sorted(GAME.sbData["skillDict"].keys()):
                frame xsize 600 ysize 40 background Solid("#ffffff00"):
                    if bot.skills[k] < bot.getTalents()[k]:
                        frame xsize 100 ysize 40 xpos 1320 background Solid("#0099cc") yalign 0.5:
                            text "Train" xalign 0.5 yalign 0.5 size FSIZE 
                            button action Return(["train", k]) 
    # Autotrain Button
    if bot.isComplete():
        frame xsize 140 ysize 40 xpos 1630 background Solid("#0099cc") ypos 890:
            text "Auto-Train" xalign 0.5 yalign 0.5 size FSIZE 
            button action Return(["autotrain", k])


    #####################################################################
    #					TOOLTIPS						  
    #####################################################################
    $ tooltip = GetTooltip()
    if tooltip:
        # bot? 
        if tooltip in GAME.lab.cells +[GAME.lab.current]:
            vbox  pos 1300,990 xalign 0.5 spacing 10:
                bar xsize 300 ysize 2 xalign 0.5
                hbox spacing 10 xalign 0.5:
                    hbox:
                        for i in range(tooltip.getStars()):
                            image "03_minigames/star.png" zoom 0.5 yalign 0.5
                    text "{} -".format(tooltip.name)xalign 0.5 size FSIZE
                    text "Gen {} -".format(tooltip.parts["cortex"].cat)xalign 0.5 size FSIZE
                    text "{:,} Cr".format(tooltip.getPrice()) xalign 0.5 size FSIZE
                bar xsize 300 ysize 2 xalign 0.5

 
            
        # part?
        elif isinstance(tooltip, SexbotPart) :
            vbox  pos 1300,990 xalign 0.5 spacing 10:
                bar xsize 300 ysize 2 xalign 0.5
                if tooltip.getBoostOneLiner():
                    text "{} - {}".format(tooltip.getName(), tooltip.getBoostOneLiner()) xalign 0.5 size FSIZE
                else:
                    text "{}".format(tooltip.getName()) xalign 0.5 size FSIZE
                bar xsize 300 ysize 2 xalign 0.5

#####################################################################
#					MAIN LAB MODULE	
#####################################################################

label mg_sexbot_lab():

    #scene room_lab_smart with fade
    hide screen header
    play music "audio/ambient_dark.wav" volume 0.2
    show screen mg_sexbot_prep_scr() with dissolve
    $ fromSlot = None # remembers where the displayed Bot is from
    queue sound "audio/beep.wav"
    $ labWork = False

    # Lab Loop
    while True:
        $ bot = GAME.lab.current
        call screen mg_sexbot_scr()
        #queue sound "audio/click.wav"

        #####################################################################
        #					EXIT BOTLAB		
        #####################################################################
        if _return == False:

            if GAME.lab.current.hasComp("cortex"): # CR-0323
                queue sound "audio/beep.wav"
                call simple_notify("LAB STATUS", "Put {} back into storage first.".format(C_HI_B+GAME.lab.current.name+C_HI_E), ["Continue"]) from _call_simple_notify_83 # CR-0323
            else: # CR-0323
                stop music fadeout 1.0 
                hide screen mg_sexbot_prep_scr with fade # CR-0323
                if labWork:
                    call hour_passes() from _call_hour_passes_98
                scene room_lab_smart
                return() # CR-0323


        elif "rename" in _return:
            $ n = renpy.input("Rename {}".format(GAME.lab.current.name))
            $ n = n.strip() 
            $ n = n[:16]
            $ GAME.lab.current.name = n.title()

        elif "reroll_name" in _return: # xxx
            $ old_name = GAME.lab.current.name 
            queue sound "audio/click.wav"
            while old_name == GAME.lab.current.name:
                $ GAME.lab.current.name = random.choice(GAME.sbData["names"])

        #####################################################################
        #					NEW BOT / CORTEX		
        #####################################################################
        elif "add" in _return and len([x for x in GAME.lab.cells if x])==10:
            queue sound "audio/tech_fail.wav"
            "Lab Manager" "\"No free storage cell to start new bot!\""

        elif "add" in _return and _return[1] == "cortex":
            $ labWork = True
            queue sound "audio/click.wav"
            # get core 
            hide screen mg_sexbot_prep_scr
            call mg_sexbot_part_storage("cortex") from _call_mg_sexbot_part_storage
            if _return:
                $ cortex = _return          
                call activity_bar("Analyzing Core...", "{}{}".format(cortex.getPath(), cortex.getIco())) from _call_activity_bar
                queue sound "audio/success.wav"
                # install part 
                $ bot.parts["cortex"]=cortex 
                # remove part from storage     
                $ GAME.lab.parts.remove(cortex)     
                $ bot.rollNew(cortex)
            show screen mg_sexbot_prep_scr()

        elif "add" in _return and _return[1] == "cube":
            $ labWork = True
            queue sound "audio/click.wav"
            # get cube
            hide screen mg_sexbot_prep_scr
            call mg_sexbot_part_storage("cube") from _call_mg_sexbot_part_storage_1
            if _return:
                $ cube = _return       
                # warning 
                queue sound "audio/notify.wav"
                call large_notify("CUBE ACTIVATION", "Installation of this Precursor Cube will permanently alter cortex properties. The selected cube will be consumed in the process.\n\nDo you want to continue?", ["Continue", "Abort"], "03_minigames/ico {}.png".format(cube.attr["BaseID"])) from _call_large_notify_68
                if _return == "Continue":
                    play sound "audio/success.wav"
                    $ result = cube.rollCube(bot)
                    if result[0] == "Traits":
                        $ msg = bot.colorize(result[1][0].title())+" / "+ bot.colorize(result[1][1].title())
                    else:
                        $ msg = bot.colorize(result[1].title()) 
                    call simple_notify("NEW {}".format(result[0].upper()), "{}".format(msg),["Continue"], "03_minigames/ico {}.png".format(cube.attr["BaseID"])) from _call_simple_notify_84
                    # clean up data
                    $GAME.lab.parts.remove(cube)
                    queue sound "audio/tech_success.wav"

            show screen mg_sexbot_prep_scr()


        #####################################################################
        #					PART ADDITION / MODULE ADDITION		
        #####################################################################
        #elif "add" in _return and not bot.hasComp("cortex"):
            #queue sound "audio/beep.wav"
            #call simple_notify("CONFIGURATION", "You need to insert a core first.", ["Continue"]) from _call_simple_notify_85

        elif "add" in _return:   
            $ labWork = True
            queue sound "audio/click.wav"
            $ partsKey = _return[1]
            # get part
            hide screen mg_sexbot_prep_scr
            call mg_sexbot_part_storage(partsKey) from _call_mg_sexbot_part_storage_2
            show screen mg_sexbot_prep_scr()
            if _return:
                $ part = _return          
                # install part 
                $ bot.parts[partsKey]=part 
                # remove part from storage 
                $ GAME.lab.parts.remove(part)        
                queue sound "audio/install.wav"

                # bot completion triggers 
                if bot.isComplete(): 
                    # first bot
                    if GAME.questSys.inProgress("QID_BOT_FIRST"):
                        pause 2.0
                        $ GAME.mc.hasDone("first_bot") 
                        call quest_updater() from _call_quest_updater_180
                    # bot creation log 
                    $ GAME.lab.botsCreated.add(bot.parts["cortex"].ID)
                    if len(GAME.lab.botsCreated) >= 20 and not GAME.mc.hasAchievement("master builder"):
                        call achievement_notify(GAME.mc, "master builder") from _call_achievement_notify_14 
                    

        elif "module" in _return:
            $ labWork = True
            $ iSlot = _return[1]
            $ bot = GAME.lab.current
            # base bot configured?
            if not bot.hasComp("cortex"):
                queue sound "audio/tech_fail.wav"
            # uninstall?    
            elif bot.addOns[iSlot]:
                $ bot.uninstallAddOn(iSlot)
                queue sound "audio/install.wav"

            # select new from storage? 
            else:
                queue sound "audio/click.wav"
                hide screen mg_sexbot_prep_scr
                if iSlot == 5:
                    call mg_sexbot_part_storage("shell") from _call_mg_sexbot_part_storage_3
                else:
                    call mg_sexbot_part_storage("addon") from _call_mg_sexbot_part_storage_4
                show screen mg_sexbot_prep_scr() 
                if _return:   
                    $ item = _return
                    if "Shell" in item.attr["BaseID"] and not bot.isComplete():
                        queue sound "audio/tech_fail.wav"
                    elif item.isShell() == "I" and not ( all([x.cat > 1 for x in bot.parts.values()]) and all([x.pref in ["military grade", "elite"] for x in bot.parts.values()])): # CR-0876
                        queue sound "audio/tech_fail.wav"
                        "Lab Manager" "\"Shell requires full Military Grade Bot, Gen2 or above.\"" # CR-0537
  
                    else: 
                        $ bot.installAddOn(i, item)    
                        queue sound "audio/tech_success.wav"
             
        #####################################################################
        #					BOT RETRIEVAL $ STORAGE
        #####################################################################
        elif "bot" in _return:
           
            if GAME.lab.current.hasComp("cortex"): # CR-0323
                queue sound "audio/tech_fail.wav" # CR-0323

            # retrieve bot into empty configurator # CR-0323
            else: # CR-0323
                $ retrieveBot = _return[1] # CR-0323
                $ fromSlot = _return[2] # CR-0323
                $ cellNum = GAME.lab.cells.index(retrieveBot) # CR-0323
                queue sound "audio/load.wav" # CR-0323
                $ GAME.lab.current = retrieveBot # CR-0323
                $ GAME.lab.cells[cellNum] = False # CR-0323               

        #####################################################################
        #					LAB OPERATION		
        #####################################################################

        elif "charge" in _return:
            queue sound "audio/load.wav"
            $ GAME.lab.charge(_return[1])

        elif "repair" in _return:
            $ labWork = True
            $ slot = _return[1] 
            $ repairMatReq = {"cortex": "bio", "torso": "nano", "legs": "nano", "arms": "nano", "head": "nano" }
            $ mat = repairMatReq[slot]
            $ bot = GAME.lab.current
            $ skillKey = capValue(GAME.mc.attr["TEC"],5,10)
            $ repTo = {5: 0.4, 6: 0.3, 7: 0.2, 8: 0.1, 9: 0.05, 10: 0.0 }

            if GAME.lab.status[mat] < 20:
                queue sound "audio/tech_fail.wav"
                call simple_notify("CONFIGURATOR", "20% {} required.".format(mat.title()), ["Continue"]) from _call_simple_notify_86

            elif repTo[skillKey] >= bot.parts[slot].wear:
                queue sound "audio/notify.wav"
                "Your Tech Skills are not sufficient to repair that part further."
                "With some more repair practice that should improve..."
            else:
                queue sound "audio/install.wav"
                $ bot.parts[slot].wear = repTo[skillKey]
                $ GAME.lab.status[mat] = max(0, GAME.lab.status[mat]-20) 
                $ GAME.mc.incAttrFrac("TEC", 0.1,10)

        elif "remove" in _return and GAME.lab.current.getShellConfig() in ["I","J", "V"]:
            queue sound "audio/tech_fail.wav"

        elif "remove" in _return:
            $ labWork = True
            $ slot = _return[1]
            $ bot = GAME.lab.current
            if slot == "cortex":
                queue sound "audio/notify.wav"
                call large_notify("WARNING", "Removal will destroy the current cortex. All parts will be returned to storage. \n\nDo you want to proceed?", ["Proceed", "Abort"], "08_items/ico ITMSpanner.png", "red") from _call_large_notify_69
                if _return == "Proceed":
                    queue sound "audio/install.wav"
                    # return all parts and modules except core to storage
                    python:
                        for k in bot.parts.keys():
                            if k != "cortex":
                                bot.uninstallPart(k) 
                        for i in range(len(bot.addOns)):
                            if bot.addOns[i]:
                                GAME.lab.parts.append(bot.addOns[i])
                                bot.addOns[i] = False
                    # wipe doll 
                    $ GAME.lab.clearLab()
                    call simple_notify("CONFIGURATION", "Parts disassembled and stored.", ["Continue"]) from _call_simple_notify_87
                else:
                    pass
            else:
                # remove part and return to storage
                queue sound "audio/install.wav"
                $ bot.uninstallPart(slot)               

        elif "autotrain" in _return:
            queue sound "audio/switch.wav"
            $ GAME.lab.current.autoTrain = not  GAME.lab.current.autoTrain

        elif "train" in _return:
            $ labWork = True
            $ skill = _return[1]
            $ bot = GAME.lab.current
            if not bot.isComplete():
                queue sound "audio/beep.wav"
                call simple_notify("CONFIGURATOR", "Complete configuration first.", ["Continue"]) from _call_simple_notify_88
            elif GAME.lab.status["energy"] >= 10:
                $ sPre = bot.skills[skill]
                queue sound "audio/train_tech.wav"
                $ tMode = "manual"
                if "hyper-train" in GAME.lab.status.keys():
                    if GAME.lab.status["hyper-train"] > 0:
                        $ tMode = "hyper" 
                        $ GAME.lab.status["hyper-train"] = max(0,GAME.lab.status["hyper-train"]-1)
                $ GAME.lab.current.train(skill, tMode)
                $ sPost = bot.skills[skill]
                if bot.skills[skill] >= bot.getTalents()[skill] and sPost >= 90:
                    play sound "audio/success.wav"
            else:
                queue sound "audio/tech_fail.wav"

        #####################################################################
        #					UPGRADE BOT  / HYPERTRAIN
        #####################################################################
        elif "upgrade cortex" in _return:

            $ bot = GAME.lab.current
            $ cortex = bot.parts["cortex"]
            $ botGen = cortex.cat

            queue sound "audio/notify.wav"

            call large_notify("IMPORTANT UPGRADE NOTE", "Upgrading your bot's cortex will consume one AI-Core. If the cortex' generation is upgradeable, that upgrade is guaranteed. Bot IQ, traits etc. will not be retained and randomly change.\n\nDo you want to continue?", ["Upgrade", "Abort"], "08_items/ico ITMAICore.png", "orange") from _call_large_notify_70

            if _return == "Upgrade":


                call activity_bar("Upgrading Core...", "{}{}".format(cortex.getPath(), cortex.getIco())) from _call_activity_bar_1
                # removing shells 
                python:
                    for i in range(len(bot.addOns)):
                        if bot.addOns[i]:
                            if "shell" in bot.addOns[i].ID.lower():
                                bot.uninstallAddOn(i)


                # construct new cortex 
                $ cGen = {1: "SBPCortex1", 2: "SBPCortex2", 3: "SBPCortex3"}[capValue(botGen+1,1,3)]

                if GAME.ship.countCargo("ITMAICoreElite") and botGen == 3:
                    $ cGen = "SBPCortexPre"

                # prepare attributes to be retained 
                $ attr = {}
                $ attr["pref"] = bot.parts["cortex"].pref 
                $ attr["wear"] = bot.parts["cortex"].wear

                # prepare new cortex 
                $ newCortex = GAME.sbPartsData[cGen].clone(attr) # take over prefix
            
                # prepare new bot and transfer parts and addons
                $ attr = {}
                $ attr["name"] = bot.name 
                $ attr["tally"] = bot.tally
                $ newBot = Sexbot()
                $ newBot.rollNew(newCortex, attr)
    
                python:
                    for i in ["head", "arms", "torso", "legs"]:
                        newBot.parts[i] = bot.parts[i]
                    newBot.addOns = bot.addOns
                queue sound "audio/tech_success.wav"

                # Swap out active bot / consume core
                $ GAME.lab.current = newBot
                if cGen == "SBPCortexPre":
                    $ GAME.ship.cargo.remove(GAME.items["ITMAICoreElite"])
                elif GAME.ship.countCargo("ITMAICore") == 0: # Upgrade for gen 1,2 bot pressed but no normal AICores available
                    $ GAME.ship.cargo.remove(GAME.items["ITMAICoreElite"])
                else:
                    $ GAME.ship.cargo.remove(GAME.items["ITMAICore"])
                            
        elif "hyper-train" in _return:
            queue sound "audio/tech_success.wav"
            $ GAME.lab.status["hyper-train"] = 20 
            $ GAME.ship.unloadWare("ITMOrePromethium")
            # $ i =  GAME.lab.extensions.index(GAME.items["ITMOrePromethium"])
            # $ GAME.lab.extensions[i] = False    

        elif "quality control" in _return: 
            hide screen mg_sexbot_prep_scr
            queue sound "audio/beep.wav"
            call mgsb_quality_control() from _call_mgsb_quality_control
            show screen mg_sexbot_prep_scr

        elif "bot analyzer" in _return:
            hide screen mg_sexbot_prep_scr
            queue sound "audio/beep.wav"
            call mgsb_analyzer() from _call_mgsb_analyzer

            show screen mg_sexbot_prep_scr                

        elif "lab manager" in _return:
            hide screen mg_sexbot_prep_scr
            call mg_lab_manager() from _call_mg_lab_manager_1
            show screen mg_sexbot_prep_scr

        elif "process" in _return and _return[1] == "store":
            $ slot = _return[2] 
            if GAME.lab.hasFreeCell(): 
                queue sound "audio/load.wav"
                $ bot = GAME.lab.current          
                $ GAME.lab.storeBot(GAME.lab.current, slot) 
                $ fromSlot = None

                #show image "fullscreen_wip.png" with fade
                if not GAME.mc.hasDone("stored_bot"):
                    hide screen mg_sexbot_prep_scr
                    call large_notify("STORAGE", "You deactivate {0} and move her into the Lab's storage area.\n\nYou can retrieve your bot for configuration and training at any time from within the configurator menu.".format(C_HI_B+bot.name+C_HI_E), ["Continue"]) from _call_large_notify_71
                    show screen mg_sexbot_prep_scr() with fade

                # quest hooks
                if bot.getIQ() > 140 and bot.isComplete() and not GAME.questSys.isActive("QID_PIRATE_KICKOFF"):
                    queue sound "audio/drone_beep.wav"
                    bb2 "\"Captain [GAME.mc.name], this bot you created might pose a problem.\""
                    mc "\"How so?\"" 
                    bb2 "\"Iltari System Regulations prohibit engaging in trade with too intelligent sex bots.\""
                    mc "\"Too intelligent?\""
                    bb2 "\"Everything above 140 is prohibited.\""
                    mc "\"That's too bad.\""
                    mc "\"They'd fetch a nice premium...\""
                    mc "\"Can I still make them?\""
                    bb2 "\"Production for research purposes is allowed, trade is illegal.\""
                    mc "\"Is there any way around that?\""
                    bb2 "\"I suggest you talk with Nimhe.\""
                    bb2 "\"Her business associates might not look too closely on regulations.\""
                    bb2 "\"On the contrary, one might even say....\""
                    mc "\"I see...\""
                    mc "\"Maybe Nimhe can help selling higher end models.\""
                    mc "\"I'll talk to her.\""
                    $ GAME.mc.hasDone('illegal_bot_warning')
                    call quest_updater() from _call_quest_updater_181

                if bot.getIQ() >= 150 and bot.isComplete() and bot.getPrice() > 100000 and GAME.questSys.inProgress("QID_PIRATE_PRESENT1"):
                    queue sound "audio/notify.wav"
                    mc "\"Excellent, that was just the bot I need for the Pirates.\""
                    mc "\"I better put [C_HI_B][bot.name][C_HI_E] aside and talk to Nimhe...\"" 
                    queue sound "audio/load.wav"
                    $ GAME.lab.clearBot(bot)  
                    $ GAME.mc.hasDone("bot_present_made")  
                    call quest_updater() from _call_quest_updater_182           
             
                if all([x.pref == "military grade" for x in bot.parts.values()]) and all([x.cat > 1 for x in bot.parts.values()]) and GAME.questSys.inProgress("QID_NIMHE_RETRIEVE_CREATE") and bot.isComplete(): # CR-0401
                    $ GAME.mc.hasDone("bot_frame_made")
                    # put bot aside
                    $ GAME.lab.hiddenCells.append(bot)
                    $ GAME.lab.clearBot(bot)
                    call quest_updater() from _call_quest_updater_183

            else:
                queue sound "audio/notify.wav"
                call simple_notify("STORAGE", "No free storage cell.".format(GAME.lab.current.name), ["Continue"]) from _call_simple_notify_89

        #####################################################################
        #					CHECK MAT AUTO REFILLS		
        #####################################################################
        python:
            refill = False
            for i in ["bio", "energy", "nano"]:
                if GAME.lab.status[i] == 0:
                    if GAME.items["ITMPod{}".format(i.title())] in GAME.ship.cargo:
                        refill = True
                        GAME.lab.status[i] = 100
                        GAME.ship.cargo.remove(GAME.items["ITMPod{}".format(i.title())])
        if refill:
            queue sound "audio/load.wav"

    return()