Exemple #1
0
 def __init__(self, unit):
     self.tag = unit.tag
     self.unit = unit
     self.label = 'Idle'
     self.unitCounter = UnitCounter()
     self.defending = False
     self.ineed_workers = False
     self.main_base = False
     self.personal_cheese = False
     #pylon locations
     self.p1 = None
     self.p2 = None
     self.p3 = None
     self.p4 = None
     self.p5 = None
     self.p6 = None
     #debug locations
     self.mineral1 = None
     self.mineral2 = None
     #pylon communications
     self.pylons_needed = 0
     self.next_pylon_location = None
     #cannon communications
     self.cannons_needed = 0
     self.next_cannon_location = None
     #shield communications
     self.shields_needed = 0
     self.next_shield_location = None
     #first check
     self.firstframe = False
     self.overworked = False
     self.worker_allin = False
Exemple #2
0
 def __init__(self, unit):
     self.tag = unit.tag
     self.unit = unit
     self.label = 'Idle'
     self.queued = False
     self.unitCounter = UnitCounter()
     self.trainList = ['VoidRay', 'Phoenix', 'Tempest', 'Carrier']
Exemple #3
0
	def __init__(self, unit):
		self.tag = unit.tag
		self.unit = unit
		self.label = 'Idle'
		self.queued = False
		self.unitCounter = UnitCounter()
		self.trainList = ['Immortal', 'WarpPrism', 'Colossus', 'Disruptor', 'Observer']
Exemple #4
0
	def __init__(self, unit):
		self.tag = unit.tag
		self.unit = unit
		self.label = 'Idle'
		self.transform_started = False
		self.warpgate = False
		self.queued = False
		self.unitCounter = UnitCounter()
		self.trainList = ['Zealot', 'Stalker', 'Adept', 'Sentry', 'HighTemplar']
Exemple #5
0
class Nexus:
    def __init__(self, unit):
        self.tag = unit.tag
        self.unit = unit
        self.label = 'Idle'
        self.unitCounter = UnitCounter()
        self.defending = False
        self.ineed_workers = False
        self.main_base = False
        self.personal_cheese = False
        #pylon locations
        self.p1 = None
        self.p2 = None
        self.p3 = None
        self.p4 = None
        self.p5 = None
        self.p6 = None
        #debug locations
        self.mineral1 = None
        self.mineral2 = None
        #pylon communications
        self.pylons_needed = 0
        self.next_pylon_location = None
        #cannon communications
        self.cannons_needed = 0
        self.next_cannon_location = None
        #shield communications
        self.shields_needed = 0
        self.next_shield_location = None
        #first check
        self.firstframe = False
        self.overworked = False
        self.worker_allin = False

    async def make_decision(self, game, unit):
        self.game = game
        self.unit = unit
        self.abilities = self.game.allAbilities.get(self.unit.tag)
        #add pylon locs if needed.
        if not self.firstframe:
            await self.pylonLocations()
            self.main_base = self.mainBaseCheck()
            self.firstframe = True
        #update our communication info.
        self.communicateNeeds()

        await self.runList()

        #debugging info
        if self.game.debugAllowed:
            if _debug or self.unit.is_selected:
                self.debugit()
                self.game._client.debug_text_3d(self.label,
                                                self.unit.position3d)

    async def runList(self):

        #if we are still warping in.
        if not self.unit.is_ready:
            #check for cannons near by and cancel as late as possible if true.
            if self.game.cached_enemies.filter(
                    lambda x: x.type_id in {PHOTONCANNON} and x.distance_to(
                        self.unit) < 9):
                #cannon has been found, leave opening stage.
                if not self.game._strat_manager.stage1complete:
                    self.game._strat_manager.build.can_build_pylons = True
                    self.game._strat_manager.build.can_build_assimilators = True
                    self.game._strat_manager.stage1complete = True
                    await self.game._client.chat_send(
                        self.unitCounter.cannonRushSaying(), team_only=False)
                #wait until we are at least 95% finished, then cancel ourselves.
                if self.unit.build_progress >= 0.98:
                    self.game.combinedActions.append(self.unit(CANCEL))
            return  #warping in

        self.checkUnderAttack()
        #check to see if we need workers
        self.checkNeedWorkers()
        #check for rush with workers.
        self.detectWorkerAllIn()

        #check to see if saving resources are being requested.
        if self.resourcesSaved():
            self.label = 'Resources being saved'
            return

        if self.recallDefense():
            self.label = 'Recalling'
            return
        #if self.main_base:
        # 	self.findRecallArmy(200)

        await self.chronoBoost()
        #check to make sure we aren't under attack, if we are trigger

        if self.game.can_spend:

            #build probes if we need them.
            if self.trainProbe():
                self.label = 'Training Unit'
                return

            #build a mothership if we can.
            if self.trainMothership():
                self.label = 'Training Unit'
                return

        self.label = 'Idle'

    def recallDefense(self):
        #if we are under attack, count the power of the attacking army.
        if self.main_base and self.defending and self.unit.energy >= 50:
            near_enemies = self.game.cached_enemies.filter(
                lambda x: not x.name in ['Probe', 'Drone', 'SCV'] and
                (x.can_attack_ground or x.can_attack_air) and x.distance_to(
                    self.unit) <= 40)
            def_enemies = near_enemies.closer_than(20, self.unit)
            score = self.game._strat_manager.score_rally(near_enemies)
            score2 = self.game._strat_manager.score_rally(def_enemies)
            #if we need more units, look to recall
            #make sure most of the enemy units are close.
            #if score2 >= score / 1.5:
            if score > 0:
                #enemies have moved in.
                #print ('enemies close enough for moving')
                #count the power of the army defending near us in a larger range.
                near_friends = self.game.units.filter(
                    lambda x: not x.name in ['Probe'] and
                    (x.can_attack_ground or x.can_attack_air
                     ) and x.distance_to(self.unit) <= 20)
                friend_score = self.game._strat_manager.score_rally(
                    near_friends)
                score_needed = score / 1.25
                if friend_score <= score / 1.25:
                    #print ('need more defense', score_needed)
                    #look to see if we have units in the midfield, or attacking.
                    self.findRecallArmy(score_needed)

                #else:
                #print ('above min:', score_needed)

                #print (str(self.unit.tag), str(len(near_friends)), friend_score)

            #see if there is a group of them in an area with enough power to defend.
            #recall those units to the nexus to defend.
            #print (str(self.unit.tag), str(len(near_enemies)), score, '-', str(len(def_enemies)), score2)

    def findRecallArmy(self, scoreMin):
        if self.game.defend_only and AbilityId.EFFECT_MASSRECALL_NEXUS in self.abilities:
            #score the army at the defensive position and see if stuff is there, if so, port it.
            near_friends = self.game.units.filter(
                lambda x: not x.name in ['Probe'] and
                (x.can_attack_ground or x.can_attack_air) and x.distance_to(
                    self.game.defensive_pos) <= 2.5)
            score = self.game._strat_manager.score_rally(near_friends)
            #print ('recall scoring:', score, 'min', scoreMin)
            if score >= scoreMin:
                print('recalling')
                #recall the units to our position.
                self.game.combinedActions.append(
                    self.unit(AbilityId.EFFECT_MASSRECALL_NEXUS,
                              self.game.defensive_pos))

    def findRecallArmyExp(self, scoreMin):
        #get the center of our fighting units.
        possibles = self.game.units.filter(
            lambda x: not x.is_structure and not x.name in ['Probe'] and
            (x.can_attack_ground or x.can_attack_air) and x.distance_to(
                self.unit) >= 30)
        if len(possibles) > 0:
            center = possibles.center
            self.game._client.debug_sphere_out(
                Point3((center.position.x,
                        center.position.y, self.unit.position3d.z)), 2.5,
                Point3((66, 69, 244)))  #blue
            #self.game._client.debug_text_3d('P1', Point3((self.p1.position.x, self.p1.position.y, self.unit.position3d.z)))
            print('center', center)
            print(len(possibles))

    def detectWorkerAllIn(self):
        self.worker_allin = False
        if self.game.time > 480:
            return  #too late in the game for a worker rush.
        #if we are under attack and if the enemy has brought a large number of workers, then we are being all-in rushed.
        if self.game.cached_enemies.exclude_type(
            [PROBE, DRONE, SCV]).closer_than(20, self.unit).amount > 10:
            #under attack, see if workers are included.
            if self.game.cached_enemies.of_type(
                [PROBE, DRONE, SCV]).closer_than(20, self.unit).amount > 10:
                self.worker_allin = True

    def detectPersonalCheese(self):
        self.personal_cheese = False
        return False
        if self.game.cached_enemies.of_type([REAPER, BANSHEE]).closer_than(
                20, self.unit).amount > 3:
            self.personal_cheese = True
            #self.reaperCheeseDef()
        else:
            self.personal_cheese = False

    async def reaperCheeseDef(self):
        #if reaper cheese is detected, build cannons at the mineral lines.
        #only works on the starting nexus, need to rewrite for the rest.
        if self.game.can_spend and self.game.reaper_cheese and self.game.units(
                FORGE).ready.exists and self.personal_cheese:
            if self.game.can_afford(
                    PHOTONCANNON
            ) and not self.game.already_pending(PHOTONCANNON):
                #put a cannon on p3 and p4.
                if self.game._build_manager.check_pylon_loc(
                        self.p3, searchrange=3) and self.game.units(
                            PHOTONCANNON).closer_than(3, self.p3).amount == 0:
                    pylon = self.game.units(PYLON).closer_than(3,
                                                               self.p3).random
                    await self.game.build(PHOTONCANNON, near=pylon)
                    self.game.can_spend = False

                if self.game._build_manager.check_pylon_loc(
                        self.p4, searchrange=3) and self.game.units(
                            PHOTONCANNON).closer_than(3, self.p4).amount == 0:
                    pylon = self.game.units(PYLON).closer_than(3,
                                                               self.p4).random
                    await self.game.build(PHOTONCANNON, near=pylon)
                    self.game.can_spend = False

    def trainMothership(self):
        #check to see if we are queued, if so, leave.
        if not self.unit.is_idle:
            return True

        #only build when queues are full to maximize real military production
        if not self.game._strat_manager.allAllowedQueued:
            self.label = 'Building Military Instead'
            return

        #build the mothership if we can.
        if self.game.units(
                FLEETBEACON
        ).ready.amount > 0 and self.game.units.not_structure.exclude_type(
            [PROBE]).amount > 10 and self.game.can_afford(
                MOTHERSHIP) and self.game.supply_left > 8:
            self.game.combinedActions.append(self.unit.train(MOTHERSHIP))
            return True

    def trainProbe(self):
        #check to see if we are queued, if so, leave.
        if not self.unit.is_idle:
            return True
        #check if it's the start of the game and we don't have a pylon yet.
        if self.game.time < 120 and self.game.units(
                PYLON).amount == 0 and self.game.units(PROBE).amount >= 13:
            return False  #don't build another probe until we have a pylon out.

        #check to see if the probe will just die if it's made.
        if self.game.units(
                PROBE).amount < 5 and self.game.cached_enemies.closer_than(
                    20, self.unit).amount > 5:
            return False  #wait until things clear hopefully.

        if self.game.cached_enemies(PHOTONCANNON).closer_than(
                10, self.unit).amount > 0:
            return False  #wait until cannon is cleared.

        #check to see if workers are needed.
        if self.game.buildingList.workersRequested:
            #build worker, sent can_spend false.
            if self.game.rush_detected and self.game.units(
                    GATEWAY).ready.exists:
                return False  #don't build probes.
            else:
                if self.game.can_afford(
                        PROBE
                ) and self.game.supply_left > 0 and self.game.units(
                        PROBE).amount < self.game._max_workers:
                    self.game.combinedActions.append(self.unit.train(PROBE))
                    self.game.can_spend = False
                    return True

    def checkNeedWorkers(self):
        #if we have no ideal workers, minerals are empty, we need nothing.
        if self.unit.ideal_harvesters == 0:
            self.ineed_workers = False
            return
        #find out if we need workers for the mineral lines.
        #workers_needed = self.unit.ideal_harvesters - self.unit.assigned_harvesters + 4
        #find out if we need workers for the assimilators.
        extra_workers = 0
        base_workers = 16
        if self.game.already_pending(NEXUS):
            base_workers = 21
        if self.game.units(ASSIMILATOR).ready.closer_than(
                10, self.unit).filter(lambda x: x.has_vespene):
            extra_workers = self.game.units(ASSIMILATOR).ready.closer_than(
                10, self.unit).filter(lambda x: x.has_vespene).amount * 2
        #add them together to find out how many total workers we need.
        total_workers_needed = self.unit.ideal_harvesters + extra_workers
        #oversaturate at the start.
        if self.game.units(PROBE).amount < (base_workers + extra_workers):
            total_workers_needed = base_workers + extra_workers

        #count the probes around us and see if we have enough.
        #if there is a worker doing a nexus build, we don't need workers.
        ineed_workers = False
        if self.game.unitList.nexusBuilderAssigned:
            return

        if self.game.units(PROBE).closer_than(
                20, self.unit).amount < total_workers_needed:
            #do a check to make sure workers aren't long distance mining giving us less.
            if (len(self.game.units(NEXUS).ready) *
                (base_workers + extra_workers)) >= len(self.game.units(PROBE)):
                self.ineed_workers = True
                self.overworked = False
        else:
            if self.game.units(PROBE).closer_than(
                    20, self.unit).amount >= total_workers_needed:
                self.ineed_workers = False
                self.overworked = True

    def resourcesSaved(self):
        if self.game._strat_manager.saving or not self.game.can_spend:
            return True

    def checkUnderAttack(self):
        if self.game.cached_enemies.exclude_type([
                PROBE, DRONE, SCV, OVERLORD
        ]).closer_than(30, self.unit).amount > 0:
            self.defending = True
        else:
            self.defending = False

    def mainBaseCheck(self):
        if self.unit.distance_to(
                self.game.game_info.player_start_location) < 6:
            return True
        return False

    async def chronoBoost(self):
        if self.game.time < 120 and self.game.units(PYLON).amount == 0:
            return False  #don't boost until we have a pylon out.
        #if it's beyond 5 minutes into the game, save for a recall.
        if self.main_base and self.game.time > 300 and self.unit.energy < 100:
            return False

        if AbilityId.EFFECT_CHRONOBOOSTENERGYCOST in self.abilities:
            #check if research is being done and buff it if so.
            #cyberneticcore
            #if we are being attacked early in game, do the gateway first to get the unit out faster.
            if self.game.cached_enemies.of_type([REAPER]).closer_than(
                    40, self.unit).amount > 0:
                for gateway in self.game.units(GATEWAY):
                    if not gateway.is_idle and gateway.orders[
                            0].progress < 0.75 and not gateway.has_buff(
                                BuffId.CHRONOBOOSTENERGYCOST):
                        self.game.combinedActions.append(
                            self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                                      gateway))
                        return True

            for core in self.game.units(CYBERNETICSCORE):
                if not core.is_idle and core.orders[
                        0].progress < 0.75 and not core.has_buff(
                            BuffId.CHRONOBOOSTENERGYCOST):
                    self.game.combinedActions.append(
                        self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                                  core))
                    return True
            #forge
            for forge in self.game.units(FORGE):
                if not forge.is_idle and forge.orders[
                        0].progress < 0.75 and not forge.has_buff(
                            BuffId.CHRONOBOOSTENERGYCOST):
                    self.game.combinedActions.append(
                        self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                                  forge))
                    return True
            #twilightcouncil
            for tw in self.game.units(TWILIGHTCOUNCIL):
                if not tw.is_idle and tw.orders[
                        0].progress < 0.65 and not tw.has_buff(
                            BuffId.CHRONOBOOSTENERGYCOST):
                    self.game.combinedActions.append(
                        self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, tw))
                    return True
            #roboticsbay
            for bay in self.game.units(ROBOTICSBAY):
                if not bay.is_idle and bay.orders[
                        0].progress < 0.65 and not bay.has_buff(
                            BuffId.CHRONOBOOSTENERGYCOST):
                    self.game.combinedActions.append(
                        self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, bay))
                    return True

            nexus_boost = False
            if not self.unit.is_idle and not self.unit.has_buff(
                    BuffId.CHRONOBOOSTENERGYCOST):
                if self.unit.orders[
                        0].ability.id == AbilityId.NEXUSTRAIN_PROBE and self.unit.orders[
                            0].progress < 0.25 and self.needWorkers:
                    nexus_boost = True
                if self.unit.orders[
                        0].ability.id == AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP and self.unit.orders[
                            0].progress < 0.75:
                    nexus_boost = True

            if not self.unit.is_idle and nexus_boost and await self.game.can_cast(
                    self.unit,
                    AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                    self.unit,
                    cached_abilities_of_unit=self.abilities):
                self.game.combinedActions.append(
                    self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                              self.unit))
            else:
                for building in self.game.units.structure:
                    if not building.is_idle and building.orders[
                            0].progress < 0.35 and await self.game.can_cast(
                                self.unit,
                                AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                                building,
                                cached_abilities_of_unit=self.abilities
                            ) and not building.has_buff(
                                BuffId.CHRONOBOOSTENERGYCOST):
                        self.game.combinedActions.append(
                            self.unit(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST,
                                      building))
                        return True

    async def pylonLocations(self):
        #find positions around the nexus for pylons, cannons and shields.
        #front = self.unit.position + Point2((cos(self.unit.facing), sin(self.unit.facing))) * 2

        #find the edge of the minerals around us.
        if self.game.mineral_field.closer_than(15, self.unit):

            mins = self.game.mineral_field.closer_than(15, self.unit)
            vasp = self.game.vespene_geyser.closer_than(15, self.unit)
            mf = Units((mins + vasp), self.game)
            f_distance = 0
            mineral_1 = None
            mineral_2 = None
            if mf:
                for mineral in mf:
                    #loop other minerals and find the 2 minerals that are furthest apart.
                    for n_mineral in mf:
                        #make sure it's not the same mineral.
                        if mineral.position == n_mineral.position:
                            continue
                        #get the distance between the 2.
                        t_dist = mineral.position3d.distance_to(
                            n_mineral.position3d)
                        if t_dist > f_distance:
                            mineral_1 = mineral
                            mineral_2 = n_mineral
                            f_distance = t_dist

            self.mineral1 = mineral_1
            self.mineral2 = mineral_2

            nf = [mineral_1, mineral_2]
            if len(nf) == 0:
                return
            center_pos = Point2((sum([item.position.x for item in nf]) / len(nf), \
              sum([item.position.y for item in nf]) / len(nf)))
            nexdis = self.unit.distance_to(center_pos)
            fmost = mf.furthest_to(center_pos)

            self.p1 = self.unit.position.towards(center_pos, -5.5)
            self.p2 = self.unit.position.towards(center_pos, 7)
            self.p3 = fmost.position.towards(self.p1, (3))
            fclose = mf.furthest_to(self.p3)
            self.p4 = fclose.position.towards(self.p1, (3))

            self.p5 = self.unit.position.towards(
                self.midpoint(self.p1, self.p4), 9)
            self.p6 = self.unit.position.towards(
                self.midpoint(self.p1, self.p3), 9)

    def communicateNeeds(self):
        #if we are warping in, we don't need anything.
        if not self.unit.is_ready:
            return False

        #check the pylons and see if we need any.
        #by default build pylon 1, then 3, then 4.
        #build pylon 2 if cheese is detected.
        pylons_needed = 0
        self.next_pylon_location = None

        if not self.game._build_manager.check_pylon_loc(self.p6,
                                                        searchrange=4):
            self.next_pylon_location = self.p6

        if not self.game._build_manager.check_pylon_loc(self.p5,
                                                        searchrange=4):
            self.next_pylon_location = self.p5

        if not self.game._build_manager.check_pylon_loc(self.p4,
                                                        searchrange=4):
            if self.main_base or self.personal_cheese:
                pylons_needed += 1
            self.next_pylon_location = self.p4

        if not self.game._build_manager.check_pylon_loc(self.p3,
                                                        searchrange=4):
            if self.main_base or self.personal_cheese:
                pylons_needed += 1
            self.next_pylon_location = self.p3

        if not self.game._build_manager.check_pylon_loc(self.p1,
                                                        searchrange=4):
            pylons_needed += 1
            self.next_pylon_location = self.p1
            #add 1 pylon needed

        if self.personal_cheese and not self.game._build_manager.check_pylon_loc(
                self.p2, searchrange=4):
            pylons_needed += 1
            self.next_pylon_location = self.p2

        self.pylons_needed = pylons_needed

        #cannon communications.
        #should always build 1 cannon near pylon 1 for detection and front defense.
        cannons_needed = 0
        if self.game.time > 90 and not self.game._build_manager.check_cannon_loc(
                self.p1) and self.game._build_manager.check_pylon_loc(
                    self.p1, searchrange=4):
            cannons_needed += 1
            self.next_cannon_location = self.p1

        if self.personal_cheese and not self.game._build_manager.check_cannon_loc(
                self.p3) and self.game._build_manager.check_pylon_loc(
                    self.p3, searchrange=4):
            cannons_needed += 1
            self.next_cannon_location = self.p3

        if self.personal_cheese and not self.game._build_manager.check_cannon_loc(
                self.p4) and self.game._build_manager.check_pylon_loc(
                    self.p1, searchrange=4):
            cannons_needed += 1
            self.next_cannon_location = self.p4
        self.cannons_needed = cannons_needed

        #shield communications.
        shields_needed = 0
        if self.personal_cheese and not self.game._build_manager.check_shield_loc(
                self.p2):
            shields_needed += 1
            self.next_shield_location = self.p2
        self.shields_needed = shields_needed

    def midpoint(self, pos1, pos2):
        return Point2(((pos1.position.x + pos2.position.x) / 2,
                       (pos1.position.y + pos2.position.y) / 2))

    def debugit(self):
        #base pylon 1 position.
        self.game._client.debug_sphere_out(
            Point3((self.p1.position.x, self.p1.position.y,
                    self.unit.position3d.z)), 1, Point3((66, 69, 244)))  #blue
        self.game._client.debug_text_3d(
            'P1',
            Point3((self.p1.position.x, self.p1.position.y,
                    self.unit.position3d.z)))

        #base pylon 2 position.
        self.game._client.debug_sphere_out(
            Point3((self.p2.position.x, self.p2.position.y,
                    self.unit.position3d.z)), 1, Point3((66, 69, 244)))  #blue
        self.game._client.debug_text_3d(
            'P2',
            Point3((self.p2.position.x, self.p2.position.y,
                    self.unit.position3d.z)))

        #base pylon 3 position.
        self.game._client.debug_sphere_out(
            Point3((self.p3.position.x, self.p3.position.y,
                    self.unit.position3d.z)), 1, Point3((66, 69, 244)))  #blue
        self.game._client.debug_text_3d(
            'P3',
            Point3((self.p3.position.x, self.p3.position.y,
                    self.unit.position3d.z)))

        #base pylon 4 position.
        self.game._client.debug_sphere_out(
            Point3((self.p4.position.x, self.p4.position.y,
                    self.unit.position3d.z)), 1, Point3((66, 69, 244)))  #blue
        self.game._client.debug_text_3d(
            'P4',
            Point3((self.p4.position.x, self.p4.position.y,
                    self.unit.position3d.z)))

        self.game._client.debug_sphere_out(self.mineral1.position3d, 1,
                                           Point3((66, 69, 244)))
        self.game._client.debug_sphere_out(self.mineral2.position3d, 1,
                                           Point3((66, 69, 244)))

    @property
    def underAttack(self) -> bool:
        return self.defending

    @property
    def underWorkerAllin(self) -> bool:
        return self.worker_allin

    @property
    def needWorkers(self) -> bool:
        return self.ineed_workers

    @property
    def overWorked(self) -> bool:
        return self.overworked

    @property
    def pylonsRequested(self) -> int:
        return self.pylons_needed

    @property
    def nextPylonPosition(self) -> Point2:
        return self.next_pylon_location

    @property
    def cannonsRequested(self) -> int:
        return self.cannons_needed

    @property
    def nextCannonPosition(self) -> Point2:
        return self.next_cannon_location

    @property
    def shieldsRequested(self) -> int:
        return self.shields_needed

    @property
    def nextShieldPosition(self) -> Point2:
        return self.next_shield_location
Exemple #6
0
 def __init__(self):
     self.unit_objects = {}
     self.unitCounter = UnitCounter()
Exemple #7
0
class UnitList():
    def __init__(self):
        self.unit_objects = {}
        self.unitCounter = UnitCounter()

    def make_decisions(self, game):
        self.game = game
        self.update_units()
        for unit in self.game.units():
            obj = self.unit_objects.get(unit.tag)
            if obj:
                obj.make_decision(self.game, unit)

    def update_units(self):
        for unit in self.game.units():
            obj = self.unit_objects.get(unit.tag)
            if obj:
                obj.unit = unit

    def getObjectByTag(self, unit_tag):
        if self.unit_objects.get(unit_tag):
            return self.unit_objects.get(unit_tag)
        return None

    def remove_object(self, unit_tag):
        if self.unit_objects.get(unit_tag):
            unit_obj = self.unit_objects.get(unit_tag)
            #check to see if it's a probe, if so remove it from gathering.
            if unit_obj.unit.name == 'Probe':
                unit_obj.removeGatherer()
            if unit_obj.unit.name == 'DisruptorPhased':
                unit_obj.clearMines()
                unit_obj.clearLurkers()
            #check to see if it's our probe scout, if so create another.
            # if unit_obj.unit.name == 'Probe' and unit_obj.scout:
            # 	#was a scout, create a new one.
            # 	self.assignScout()
            del self.unit_objects[unit_tag]

    def load_object(self, unit):
        #print ('Unit Created:', unit.name, unit.tag)
        #check to see if an object already exists for this tag
        if self.getObjectByTag(unit.tag):
            return

        if unit.name == 'WarpPrism':
            obj = wpControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Immortal':
            obj = imControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Stalker':
            obj = skControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Zealot':
            obj = zlControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Sentry':
            obj = snControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Adept':
            obj = adControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Colossus':
            obj = coControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'VoidRay':
            obj = vrControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Phoenix':
            obj = pxControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Probe':
            obj = pbControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Tempest':
            obj = tpControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'AdeptPhaseShift':
            obj = sdControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'HighTemplar':
            obj = htControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Observer':
            obj = obControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Disruptor':
            obj = dsControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'DisruptorPhased':
            obj = dpControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Carrier':
            obj = crControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Mothership':
            obj = msControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'Archon':
            obj = arControl(unit)
            self.unit_objects.update({unit.tag: obj})
        elif unit.name == 'PhotonCannon':
            obj = cnControl(unit)
            self.unit_objects.update({unit.tag: obj})
        # else:
        # 	print ('Unit Created:', unit.name, unit.tag)

    def unitPosition(self, ownerUnit):
        if self.unit_objects.get(ownerUnit.tag):
            unit_obj = self.unit_objects.get(ownerUnit.tag)
            return unit_obj.saved_position
        return None

    def phaseTargets(self):
        phaseList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.name == 'DisruptorPhased'
        }
        targets = []
        for key, phase in phaseList.items():
            targets.append(phase.currentTarget)
        return targets

    def adeptChaseTarget(self, ownerUnit):
        #get the object by the unit_tag.
        if self.unit_objects.get(ownerUnit.tag):
            unit_obj = self.unit_objects.get(ownerUnit.tag)
            return unit_obj.chasePosition
        return None

    def unitDamaged(self, ownerUnit):
        if self.unit_objects.get(ownerUnit.tag):
            unit_obj = self.unit_objects.get(ownerUnit.tag)
            return unit_obj.wasDamaged
        return False

    def unitHomeTarget(self, ownerUnit):
        #get the object by the unit_tag.
        if self.unit_objects.get(ownerUnit.tag):
            unit_obj = self.unit_objects.get(ownerUnit.tag)
            return unit_obj.homeTarget
        return None

    def unitTarget(self, ownerUnit):
        #get the object by the unit_tag.
        if self.unit_objects.get(ownerUnit.tag):
            unit_obj = self.unit_objects.get(ownerUnit.tag)
            return unit_obj.last_target
        return None

    def disruptorBallCancel(self, owner_tag) -> bool:
        ballList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.type_id == DISRUPTORPHASED and v.requestCancel
            and v.ownerTag == owner_tag
        }
        if len(ballList) > 0:
            return True
        return False

    def adeptOrder(self, ownerUnit):
        #get the object by the unit_tag.
        if self.unit_objects.get(ownerUnit.tag):
            unit_obj = self.unit_objects.get(ownerUnit.tag)
            return unit_obj.shadeOrder
        return None

    def assignScout(self):
        #if it's late in the game and we aren't attacking, then don't make a replacement.
        if self.game.defend_only and self.game.time > 360:
            return
        #find a probe to assign as a scout.
        probeList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.name == 'Probe' and not v.collect_only and not v.scout
        }
        for key, probe in probeList.items():
            probe.becomeScout()
            probe.removeGatherer()
            return

    def unitCount(self, unit_name):
        unitList = {
            k: v
            for k, v in self.unit_objects.items() if v.unit.name == unit_name
        }
        return len(unitList)

    def shieldSafe(self, inc_unit):
        #check for other sentries near by with shields that are active.
        shieldingList = {
            k: v
            for k, v in self.unit_objects.items() if v.unit.name == 'Sentry'
            and v.shieldActive and v.unit.distance_to(inc_unit.unit) < 2.5
        }
        if len(shieldingList) > 0:
            return False
        return True

    def freeNexusBuilders(self):
        probeList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.name == 'Probe' and v.nexus_builder
        }
        if len(probeList) > 0:
            for key, probe in probeList.items():
                probe.nexus_builder = False
                probe.nexus_position = None

    @property
    def nexusBuilderAssigned(self) -> bool:
        probeList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.name == 'Probe' and v.nexus_builder
        }
        if len(probeList) > 0:
            return True
        return False

    @property
    def hallucinationScore(self) -> int:
        hallList = {
            k: v
            for k, v in self.unit_objects.items() if v.isHallucination
        }
        hall_score = 0
        for key, unit_obj in hallList.items():
            hall_score += self.unitCounter.getUnitPower(unit_obj.unit.name)
        return hall_score

    def phoenixScouting(self):
        phoenixList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.name == 'Phoenix' and v.isHallucination
        }
        if len(phoenixList) > 0:
            return True
        return False

    def getGravitonTarget(self, inc_unit):
        phoenixList = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.name == 'Phoenix' and v.isBeaming
        }
        #print (len(phoenixList), inc_unit.unit.name, len(self.unit_objects))
        target = None
        #get the closest.
        mindist = 1000
        for key, phoenix in phoenixList.items():
            #get the distance to th
            if inc_unit.unit.position.to2.distance_to(
                    phoenix.position.to2) < mindist:
                target = phoenix.beam_unit
                mindist = inc_unit.unit.position.to2.distance_to(
                    phoenix.unit.position.to2)
        if mindist < 10:
            return target
        return None

    def getWorkers(self):
        return {
            k: v
            for k, v in self.unit_objects.items() if v.unit.name == 'Probe'
        }.items()

    def friendlyEngagedFighters(self, closestEnemy, friendRange=10):
        #find all the units near the closest Enemy that aren't retreating.
        baselist = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.position.to2.distance_to(closestEnemy.position.to2) <
            friendRange
        }
        #find out how much DPS we have going on.
        friendDPStoGround = 0
        friendDPStoAir = 0
        friendAirHealth = 0
        friendGroundHealth = 0
        friendTotalDPS = 0
        for k, friendObj in baselist.items():
            if friendObj.unit.is_flying:
                friendAirHealth += friendObj.unit.health + friendObj.unit.shield
            else:
                friendGroundHealth += friendObj.unit.health + friendObj.unit.shield
            friendDPStoGround += friendObj.unit.ground_dps
            friendDPStoAir += friendObj.unit.air_dps
            if friendObj.unit.ground_dps > friendObj.unit.air_dps:
                friendTotalDPS += friendObj.unit.ground_dps
            else:
                friendTotalDPS += friendObj.unit.air_dps

        return [
            friendDPStoGround, friendDPStoAir, friendAirHealth,
            friendGroundHealth, friendTotalDPS
        ]

    def friendlyFighters(self, inc_unit, friendRange=10):
        #find all the units near the passed units position that aren't retreating.
        #baselist = {k : v for k,v in self.unit_objects.items() if not v.isRetreating and v.unit.position.to2.distance_to(inc_unit.position.to2) < friendRange }
        baselist = {
            k: v
            for k, v in self.unit_objects.items()
            if v.unit.position.to2.distance_to(inc_unit.position.to2) <
            friendRange
        }

        #find out how much DPS we have going on.
        friendDPStoGround = 0
        friendDPStoAir = 0
        friendAirHealth = 0
        friendGroundHealth = 0
        friendTotalDPS = 0
        for k, friendObj in baselist.items():
            if friendObj.unit.is_flying:
                friendAirHealth += friendObj.unit.health + friendObj.unit.shield
            else:
                friendGroundHealth += friendObj.unit.health + friendObj.unit.shield
            friendDPStoGround += friendObj.unit.ground_dps
            friendDPStoAir += friendObj.unit.air_dps
            if friendObj.unit.ground_dps > friendObj.unit.air_dps:
                friendTotalDPS += friendObj.unit.ground_dps
            else:
                friendTotalDPS += friendObj.unit.air_dps

        return [
            friendDPStoGround, friendDPStoAir, friendAirHealth,
            friendGroundHealth, friendTotalDPS
        ]

    #properties.
    @property
    def amount(self) -> int:
        return len(self.unit_objects)
Exemple #8
0
class Stargate:
    def __init__(self, unit):
        self.tag = unit.tag
        self.unit = unit
        self.label = 'Idle'
        self.queued = False
        self.unitCounter = UnitCounter()
        self.trainList = ['VoidRay', 'Phoenix', 'Tempest', 'Carrier']

    async def make_decision(self, game, unit):
        self.game = game
        self.unit = unit
        self.abilities = self.game.allAbilities.get(self.unit.tag)
        self.queueStatus()

        if self.unit.is_idle:
            await self.runList()
        else:
            self.label = "Busy {}".format(str(len(self.abilities)))

        #debugging info
        if self.game.debugAllowed:
            if _debug or self.unit.is_selected:
                self.game._client.debug_text_3d(self.label,
                                                self.unit.position3d)

    async def runList(self):

        #check to see if saving resources are being requested.
        if self.resourcesSaved():
            self.label = 'Resources being saved'
            return

        if await self.trainUnit():
            return

    async def trainUnit(self):
        #make sure we can spend.
        if not self.game.can_spend:
            self.label = 'No spending allowed'
            return
        #get unit to train
        trainee = self.bestTrain()
        if trainee:
            self.game.combinedActions.append(
                self.unit.train(self.unitCounter.getUnitID(trainee)))
            self.game.can_spend = False
            return True

    def resourcesSaved(self):
        if self.game._strat_manager.saving:
            return True

    #utilities

    def canBuild(self, trainee):
        if self.game.can_afford(self.unitCounter.getUnitID(trainee)):
            return True

    def bestTrain(self):
        bestName = None
        bestCount = -1
        bestNeeded = False
        for name, count in self.game._strat_manager.able_army.items():
            #check if its one of our types.
            if name in self.trainList:
                #check if it's needed or not.
                if self.game._strat_manager.check_allowed(name):
                    bestNeeded = True
                    if self.canBuild(name) and count > bestCount:
                        bestName = name
                        bestCount = count
        if bestName:
            self.label = "Best {}".format(bestName)
            return bestName
        if bestNeeded:
            self.label = 'need resources'
            return None

        #apparently couldn't build anything in the ideal list that is being allowed, check for anything to build.
        #if minerals are backing up, then go ahead and build anything.
        if self.game.minerals > 550 and self.game.vespene > 500:
            bestName = None
            bestCount = -1
            for name, count in self.game._strat_manager.able_army.items():
                #check if its one of our types.
                if name in self.trainList:
                    #check if it's needed or not.
                    if self.canBuild(name):
                        if count > bestCount:
                            bestName = name
                            bestCount = count
            if bestName:
                self.label = "2nd {}".format(bestName)
                return bestName

        self.label = 'Allowing resources elsewhere'
        return None

    def queueStatus(self):
        if self.unit.is_idle:
            self.queued = False
        else:
            self.queued = True

    @property
    def inQueue(self) -> bool:
        return self.queued
Exemple #9
0
class Gateway:
    def __init__(self, unit):
        self.tag = unit.tag
        self.unit = unit
        self.label = 'Idle'
        self.transform_started = False
        self.warpgate = False
        self.queued = False
        self.unitCounter = UnitCounter()
        self.trainList = [
            'Zealot', 'Stalker', 'Adept', 'Sentry', 'HighTemplar'
        ]

    async def make_decision(self, game, unit):
        self.game = game
        self.unit = unit
        self.abilities = self.game.allAbilities.get(self.unit.tag)
        self.queueStatus()

        if unit.name == 'Gateway':
            self.warpgate = False
        else:
            self.warpgate = True

        if not self.queued:
            await self.runList()
        else:
            self.label = "Busy {}".format(str(len(self.abilities)))
        self.label += " {}".format(str(self.queued))
        #		self.label += "- {}".format(str(unit.is_idle))
        #debugging info
        if self.game.debugAllowed:
            if _debug or self.unit.is_selected:
                self.game._client.debug_text_3d(self.label,
                                                self.unit.position3d)

    async def runList(self):

        #check to see if we can become a warpgate.
        if self.transformGate():
            self.label = 'Transforming to Warpgate'
            return

        #check to see if saving resources are being requested.
        if self.resourcesSaved():
            self.label = 'Resources being saved'
            return

        if await self.trainUnit():
            return

    async def trainUnit(self):
        #make sure we can spend.
        if not self.game.can_spend:
            self.label = 'No spending allowed'
            return
        #get unit to train
        trainee = self.bestTrain()
        if trainee:
            if self.warpgate:
                warpAbil = self.unitCounter.getWarpAbility(trainee)
                if warpAbil in self.abilities:
                    placement = await self.warpgate_placement(warpAbil)
                    if placement:
                        self.game.combinedActions.append(
                            self.unit.warp_in(
                                self.unitCounter.getUnitID(trainee),
                                placement))
                        self.game.can_spend = False
                        return True
            else:
                self.game.combinedActions.append(
                    self.unit.train(self.unitCounter.getUnitID(trainee)))
                self.game.can_spend = False
                return True

    def resourcesSaved(self):
        if self.game._strat_manager.saving:
            return True

    def transformGate(self):
        #see if we can train into a warpgate.
        #if self.game._science_manager._warpgate_researched:
        if self.game.buildingList.warpgateAvail:
            #warp if we can.
            if AbilityId.MORPH_WARPGATE in self.abilities:
                self.game.combinedActions.append(
                    self.unit(AbilityId.MORPH_WARPGATE))
                self.transform_started = True
                return True
            #see if we are in the process of warping.
            if self.transform_started and not self.warpgate:
                if 'upgradetowarpgate' in str(self.unit.orders).lower():
                    return True

    #utilities

    def queueStatus(self):
        if self.warpgate:
            if len(self.abilities) < 2 and (
                    self.game.minerals > 100 or
                (self.game.vespene > 100
                 and self.game.minerals > 50)) and self.game.supply_left > 1:
                self.queued = True
            else:
                self.queued = False
        else:
            if self.unit.is_idle:
                self.queued = False
            else:
                self.queued = True

    def canBuild(self, trainee):
        if self.game.can_afford(self.unitCounter.getUnitID(trainee)):
            #make sure we can actually build it and the core is finished.
            if self.game.enemy_race == Race.Terran and trainee == 'Zealot' and not self.game.units(
                    CYBERNETICSCORE).ready.exists:
                return False  #don't build zealots to start so we can get stalkers out sooner.
            #don't build a sentry before other units.

            if self.game._strat_manager.army_power == 0 and trainee == 'Sentry':
                return False

            if trainee != 'Zealot':
                if not self.game.units(CYBERNETICSCORE).ready.exists:
                    return False
                if trainee == 'HighTemplar' and not self.game.units(
                        TEMPLARARCHIVE).ready.exists:
                    return False
            return True

    def bestTrain(self):
        bestName = None
        bestCount = -1
        bestNeeded = False
        for name, count in self.game._strat_manager.able_army.items():
            #check if its one of our types.
            if name in self.trainList:
                #check if it's needed or not.
                if self.game._strat_manager.check_allowed(name):
                    bestNeeded = True
                    if self.canBuild(name) and count > bestCount:
                        bestName = name
                        bestCount = count
        if bestName:
            self.label = "Best {}".format(bestName)
            return bestName
        if bestNeeded:
            self.label = 'need resources'
            return None

        #apparently couldn't build anything in the ideal list that is being allowed, check for anything to build.
        #if minerals are backing up, then go ahead and build anything.
        if self.game.minerals > 550 and self.game.vespene > 550:
            bestName = None
            bestCount = -1
            for name, count in self.game._strat_manager.able_army.items():
                if name == 'Sentry':
                    continue
                #check if its one of our types.
                if name in self.trainList:
                    #check if it's needed or not.
                    if self.canBuild(name):
                        if count > bestCount:
                            bestName = name
                            bestCount = count
            if bestName:
                self.label = "2nd {}".format(bestName)
                return bestName
        ####at some point, lets just build zealots to fill supply.
        if self.game.minerals > 2000 and self.game.vespene < 200:
            return 'Zealot'
        if self.game.minerals > 5000:
            return 'Zealot'

        self.label = 'Allowing resources elsewhere'
        return None

    async def warpgate_placement(self, unit_ability):
        #todo: first, check for a warp prism in pylon mode.
        if not self.game.under_attack and len(
                self.game.units(WARPPRISMPHASING).ready) > 0:
            pos = self.game.units(
                WARPPRISMPHASING).ready.random.position.to2.random_on_distance(
                    3)
            placement = await self.game.find_placement(unit_ability,
                                                       pos,
                                                       placement_step=1)
            if placement:
                return placement

        #find super pylons and sort them closest to the enemy.
        #loop the pylons and find one that isn't in range of an enemy.
        #if no super pylons exist, find any pylon and do it again.
        closestEnemy = None
        if len(self.game.cached_enemies) > 0:
            closestEnemy = self.game.cached_enemies.closest_to(
                self.game.start_location)
        if closestEnemy and self.game.units(PYLON).ready.exists:
            if len(self.game.units.of_type([NEXUS, WARPGATE])) > 0:

                for building in self.game.units().of_type([
                        NEXUS, WARPGATE
                ]).ready.sorted(lambda x: x.distance_to(closestEnemy)):
                    superPylons = self.game.units(PYLON).ready.closer_than(
                        6, building)
                    for pylon in superPylons:
                        if len(self.game.cached_enemies.closer_than(
                                12, pylon)) > len(
                                    self.game.units.exclude_type(PROBE).
                                    not_structure.closer_than(12, pylon)):
                            #print ('skipping super', str(len(self.game.cached_enemies.closer_than(12, pylon))), str(len(self.game.units.exclude_type(PROBE).not_structure.closer_than(12, pylon))))
                            continue
                        else:
                            #found a good pylon.
                            pos = pylon.position.to2.random_on_distance(4)
                            placement = await self.game.find_placement(
                                unit_ability, pos, placement_step=1)
                            if placement:
                                #print ('super placement', str(len(self.game.cached_enemies.closer_than(12, pylon))), str(len(self.game.units.exclude_type(PROBE).not_structure.closer_than(12, pylon))))
                                return placement
            #no valid super found, check all pylons.
            regPylons = self.game.units(PYLON).ready.sorted(
                lambda x: x.distance_to(closestEnemy))
            for pylon in regPylons:
                if len(self.game.cached_enemies.closer_than(12, pylon)) > len(
                        self.game.units.exclude_type(
                            PROBE).not_structure.closer_than(12, pylon)):
                    #print ('skipping regular', str(len(self.game.cached_enemies.closer_than(12, pylon))), str(len(self.game.units.exclude_type(PROBE).not_structure.closer_than(12, pylon))))
                    continue
                else:
                    #found a good pylon.
                    pos = pylon.position.to2.random_on_distance(4)
                    placement = await self.game.find_placement(
                        unit_ability, pos, placement_step=1)
                    if placement:
                        #print ('regular placement', str(len(self.game.cached_enemies.closer_than(12, pylon))), str(len(self.game.units.exclude_type(PROBE).not_structure.closer_than(12, pylon))))
                        return placement
            #enemies must be around everywhere, just pick a random pylon and place it.
            pylon = self.game.units(PYLON).ready.random
            if pylon:
                pos = pylon.position.to2.random_on_distance(4)
                placement = await self.game.find_placement(unit_ability,
                                                           pos,
                                                           placement_step=1)
                if placement:
                    #print ('random placement', str(len(self.game.cached_enemies.closer_than(12, pylon))), str(len(self.game.units.exclude_type(PROBE).not_structure.closer_than(12, pylon))))
                    return placement
        else:
            #just place the unit closest to the defensive position.
            if len(self.game.units(
                    PYLON).ready) > 0 and self.game.defensive_pos:
                pylon = self.game.units(PYLON).ready.closest_to(
                    self.game.defensive_pos)
                if pylon:
                    pos = pylon.position.to2.random_on_distance(4)
                    placement = await self.game.find_placement(
                        unit_ability, pos, placement_step=1)
                    if placement:
                        #print ('defensive placement')
                        return placement
        return None

    @property
    def inQueue(self) -> bool:
        return self.queued
Exemple #10
0
class Robo:

	def __init__(self, unit):
		self.tag = unit.tag
		self.unit = unit
		self.label = 'Idle'
		self.queued = False
		self.unitCounter = UnitCounter()
		self.trainList = ['Immortal', 'WarpPrism', 'Colossus', 'Disruptor', 'Observer']
		
		
		
	async def make_decision(self, game, unit):
		self.game = game
		self.unit = unit
		self.abilities = self.game.allAbilities.get(self.unit.tag)
		self.queueStatus()

		if self.unit.is_idle:
			await self.runList()
		else:
			self.label = "Busy {}".format(str(len(self.abilities)))

		#debugging info
		if self.game.debugAllowed:
			if _debug or self.unit.is_selected:
				self.game._client.debug_text_3d(self.label, self.unit.position3d)

	async def runList(self):

		#check to see if saving resources are being requested.
		if self.resourcesSaved():
			self.label = 'Resources being saved'
			return

		if self.trainUnit():
			return
		
		if self.trainObservers():
			return
		
		
	def trainObservers(self):
		#train observers if under the min.
		if self.game.can_spend and not self.game.under_attack and self.game._strat_manager.army_power > 40 and self.game.units(OBSERVER).amount < 2:
			self.game.combinedActions.append(self.unit.train(OBSERVER))
			self.game.can_spend = False
			return True
		return False

	
	def trainUnit(self):
		#make sure we can spend.
		if not self.game.can_spend:
			self.label = 'No spending allowed'
			return
		#get unit to train
		trainee = self.bestTrain()
		if trainee == 'Observer' and len(self.game.units(OBSERVER)) >= 5:
			return False
		if trainee:
			self.game.combinedActions.append(self.unit.train(self.unitCounter.getUnitID(trainee)))
			self.game.can_spend = False
			return True
		return False

		
	def resourcesSaved(self):
		if self.game._strat_manager.saving:
			return True
	
		
	#utilities
	
	def canBuild(self, trainee):
		if self.game.can_afford(self.unitCounter.getUnitID(trainee)):
			return True
		
	
	
	def bestTrain(self):
		#if it's been at least 7 minutes in the game, make a warpprism if one doesn't exist.
		if not self.game.defend_only and self.game.trueGates >= 3 and len(self.game.units.of_type([WARPPRISMPHASING,WARPPRISM])) == 0 and not self.game.already_pending(WARPPRISM):
			return 'WarpPrism'
		
		bestName = None
		bestCount = -1
		bestNeeded = False
		for name, count in self.game._strat_manager.able_army.items():
			#check if its one of our types.
			if name in self.trainList:
				#check if it's needed or not.
				if self.game._strat_manager.check_allowed(name):
					bestNeeded = True
					if self.canBuild(name) and count > bestCount:
						bestName = name
						bestCount = count
		if bestName:
			self.label = "Best {}".format(bestName)
			return bestName
		if bestNeeded:
			self.label = 'need resources'
			return None
				
		#apparently couldn't build anything in the ideal list that is being allowed, check for anything to build.
		#if minerals are backing up, then go ahead and build anything.
		if self.game.minerals > 550 and self.game.vespene > 500:
			bestName = None
			bestCount = -1		
			for name, count in self.game._strat_manager.able_army.items():
				#check if its one of our types.
				if name in self.trainList:
					#check if it's needed or not.
					if self.canBuild(name):
						if count > bestCount:
							bestName = name
							bestCount = count
			if bestName:
				self.label = "2nd {}".format(bestName)
				return bestName
			
		self.label = 'Allowing resources elsewhere'
		return None
	

	def queueStatus(self):
		if self.unit.is_idle:
			self.queued = False
		else:
			self.queued = True		
			
	
	@property
	def inQueue(self) -> bool:
		return self.queued