Exemple #1
0
class SimpleTower(AbstractTower):
    """A simple tower with short range that rotates towards enemies and shoots projectiles at them"""
    name = 'Simple Tower'
    colour = '#E94A1F'  # Coquelicot

    range = CircularRange(1.5)
    cool_down_steps = 0

    base_cost = 30
    level_cost = 20

    rotation_threshold = (1 / 6) * math.pi

    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=5, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        target = self.get_unit_in_range(data.enemies)

        if target is None:
            return

        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle, self.rotation_threshold)
        self.rotation = partial_angle

        if partial_angle == angle:
            target.damage(self.get_damage(), 'projectile')
Exemple #2
0
class EnergyTower(SimpleTower):
    """A tower that deals energy damage"""
    name = "Energy Tower"

    range = CircularRange(1.5)
    cool_down_steps = 0
    base_cost = 70
    level_cost = 50

    colour = 'yellow'

    rotation_threshold = (1 / 6) * math.pi

    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=7, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        target = self.get_unit_in_range(data.enemies)

        if target is None:
            return

        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle, self.rotation_threshold)
        self.rotation = partial_angle

        if partial_angle == angle:
            target.damage(self.get_damage(), 'energy')
class EnergyTower(AbstractTower):
    """Short range engergy tower"""
    name = 'Energy Tower'
    colour = 'yellow'

    range = CircularRange(4.0)
    cool_down_steps = 0

    base_cost = 25
    level_cost = 15

    rotation_threshold = (1/10)*math.pi

    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=4, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        target = self.get_unit_in_range(data.enemies)

        if target is None:
            return

        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle, self.rotation_threshold)
        self.rotation = partial_angle

        if partial_angle == angle:
            target.damage(self.get_damage(), 'energy')
class IceTower(AbstractTower):
    """A tower that slows SimpleEnemy, kills FireEnemy, 
    doesn't do anything to WoodEnemy.
    When upgraded, IceTower does damage to SimpleEnemy as well."""
    name = 'Ice Tower'
    colour = 'cyan'

    cool_down_steps = 5

    base_cost = 60
    level_cost = 45
    base_damage = 150
    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=150, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)
        self.upgraded = False
    range = CircularRange(1.3)
    def get_fire_targets(self, units):
        '''Gets all enemies in range'''
        target = []
        count = 1
        try:
            next_target = self.get_units_in_range(units.enemies)
            target.append(next_target.__next__())
            while count < 15:
                i = len(target)
                new_target = next_target.__next__()
                if new_target not in target:
                    target.append(new_target)
                    if i == len(target):
                        break
                count += 1
        except StopIteration:
            return target

        return target

    def step(self, units):
        """Deals damage to nearby FireEnemies, slows SimpleEnemies"""
        self.cool_down.step()

        if not self.cool_down.is_done():
            return

        target = self.get_fire_targets(units)
        if target == []:
            return
        for enemy in target:
            if(enemy.name == 'Fire Enemy' and not enemy.slowed):
                enemy.slowed = True
                enemy.damage(self.get_damage(), 'projectile')
            elif(enemy.name == 'Fire Enemy' and enemy.slowed):
                enemy.damage(self.get_damage(), 'projectile')
            elif (enemy.name == 'Simple Enemy' and self.upgraded):
                enemy.damage(15, 'projectile')
            elif (enemy.grid_speed == 5/60 and enemy.name == 'Simple Enemy'):
                enemy.grid_speed *= 0.80
            
            self.cool_down.start()
Exemple #5
0
class SlowTower(AbstractTower):
    """Task 3.1 (Advanced Tower): A tower that slows enemies in the vicinity"""
    name = 'Slow Tower'
    colour = '#000000'

    cool_down_steps = 20

    # expensive because the effectiveness increases exponentially with level/number of towers
    base_cost = 400
    level_cost = 300

    rotation_threshold = (1 / 6) * math.pi

    range = CircularRange(1.5)

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=50,
                 level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

        # contains all enemies slowed by this tower along with their speed multiplier
        self._enemies_slowed = []

        # contains all enemies that have been restored to their normal speeds
        self._enemies_restored = []

        # effect of multiple tower = 0.7**(number of towers)
        # we want effect of level up < effect of extra towers
        self._speed_multiplier = 0.7**(0.5 * self.level)

    def step(self, data):
        self.cool_down.step()

        # remove the slow speeds effect on enemies once they have left the tower's range
        for tup in self._enemies_slowed:
            enemy = tup[0]
            if not self.is_position_in_range(
                    enemy.position) and enemy not in self._enemies_restored:
                enemy.grid_speed = enemy.grid_speed / tup[1]
                self._enemies_restored.append(enemy)

        # slow the enemies' speeds to self._speed_multiplier% of normal
        enemies = self.get_units_in_range(data.enemies, limit=10000)
        for enemy in enemies:
            enemies_slowed_only = []
            for tup in self._enemies_slowed:
                enemies_slowed_only.append(tup[0])

            if not enemy in enemies_slowed_only:
                enemy.grid_speed = enemy.grid_speed * self._speed_multiplier
                self._enemies_slowed.append((enemy, self._speed_multiplier))
Exemple #6
0
class EnerygyTower(SimpleTower):
    """
        A Energy Tower only attack energy enemy
    """
    name = 'Energy Tower'

    range = CircularRange(2.5)
    cool_down_steps = 2
    base_cost = 60
    level_cost = 50

    colour = 'orange'

    rotation_threshold = (1 / 3) * math.pi

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=10,
                 level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

        self._target: EnergyEnemy = None

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        target = self.get_unit_in_range(data.enemies)

        "only attack energy enemies"
        for enemy in self.get_units_in_range(data.enemies):
            if not enemy.name == "Energy Enemy":
                return
            else:
                target = enemy
                break
        if target is None:
            return

        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle,
                                      self.rotation_threshold)
        self.rotation = partial_angle

        if partial_angle == angle:
            target.damage(self.get_damage(), 'energy')
Exemple #7
0
class EnergyTower(SimpleTower):
    """An energy tower with short range that rotates towards
        enemies and shoots energy at them.

        Attacks custom enemy immune to projectile and missile
    """
    #Set name and colour,range,cooldown and cost value
    name = 'Energy Tower'
    colour = '#FFFF00'  #Yellow

    range = CircularRange(2)
    cool_down_steps = 5
    base_cost = 40
    level_cost = 50

    rotation_threshold = (1 / 6) * math.pi

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=2,
                 level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)
        """Initialise the tower"""

        self._target: EnergyEnemy = None

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        #If no enemy in range does nothing
        if self.get_unit_in_range(data.enemies) is None:
            return

        #If energy enemy is in range it faces and damages it
        target = self.get_unit_in_range(data.enemies)
        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle,
                                      self.rotation_threshold)
        self.rotation = partial_angle
        if partial_angle == angle:
            if target.name == "Energy Enemy":
                target.damage(self.get_damage(), 'energy')
        else:
            return
Exemple #8
0
class SimpleTower4(SimpleTower):
    """A fully upgraded simple tower with both improved range and damage that rotates towards enemies and shoots projectiles at them"""
    name = 'Simple Tower v4'
    colour = 'purple' 

    range = CircularRange(5)
    cool_down_steps = 0

    base_cost = 80
    level_cost = 15

    rotation_threshold = (1 / 6) * math.pi

    earn_coins = 0
    
    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=8, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)
Exemple #9
0
class SimpleTower3(SimpleTower):
    """Another variation of the upgraded simple tower with greater damage capacity that rotates towards enemies and shoots projectiles at them"""
    name = 'Simple Tower v3'
    colour = 'red' 

    range = CircularRange(1.5)
    cool_down_steps = 0

    base_cost = 40
    level_cost = 15

    rotation_threshold = (1 / 6) * math.pi

    earn_coins = 0
    
    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=8, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)
Exemple #10
0
class SimpleTower2(SimpleTower):
    """A variation of upgraded simple tower with improved range that rotates towards enemies and shoots projectiles at them"""
    name = 'Simple Tower v2'
    colour = 'dark red' 

    range = CircularRange(5)    # Increased range
    cool_down_steps = 0

    base_cost = 40
    level_cost = 15

    rotation_threshold = (1 / 6) * math.pi

    earn_coins = 0
    
    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=1, level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)
Exemple #11
0
class CustomTower(SimpleTower):
    """A custom tower with short range that rotates towards energy enemies."""
    name = 'Energy Tower'
    colour = '#FEC40a'

    range = CircularRange(1.5)
    cool_down_steps = 0

    base_cost = 40
    level_cost = 30

    rotation_threshold = (1 / 6) * math.pi

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=5,
                 level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        target = self.get_unit_in_range(data.enemies)

        if target is None:
            return

        # check if the enemy is custom enemy.
        for enemy in self.get_units_in_range(data.enemies):
            if not enemy.name == "Energy Enemy":
                return
            else:
                target = enemy
                break

        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle,
                                      self.rotation_threshold)
        self.rotation = partial_angle

        # use energy to attack enemies.
        if partial_angle == angle:
            target.damage(self.get_damage(), 'energy')
Exemple #12
0
class IceTower(SimpleTower):
    """
        A spcecial tower which can slightly slow the speed of the enemy,
        the most expensive tower in this game, which can take significant affect
    """
    name = 'Ice Tower'

    cool_down_steps = 5
    base_cost = 100
    level_cost = 150
    range = CircularRange(2.5)

    colour = "#A7B6F2"

    rotation_threshold = (1 / 3) * math.pi

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=0,
                 level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)

        self._target: AbstractEnemy = None

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        target = self.get_unit_in_range(data.enemies)

        if target is None:
            return

        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle,
                                      self.rotation_threshold)
        self.rotation = partial_angle

        if partial_angle == angle:
            target.damage(self.get_damage(), 'ice')
Exemple #13
0
class SlowTower(SimpleTower):
    """A simple tower with short range that rotates towards enemies and slows them down"""

    name = 'Slow Tower'
    colour = '#8B8D7A'  #Stone Gray

    range = CircularRange(2)
    cool_down_steps = 5
    base_cost = 200
    level_cost = 50

    rotation_threshold = (1 / 6) * math.pi

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=0,
                 level: int = 1):
        super().__init__(cell_size, grid_size, rotation, base_damage, level)
        """Initialises the tower"""

        self._target: EnergyEnemy = None

    def step(self, data):
        """Rotates toward 'target' and attacks if possible"""
        self.cool_down.step()

        if self.get_unit_in_range(data.enemies) is None:
            return

        target = self.get_unit_in_range(data.enemies)
        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle,
                                      self.rotation_threshold)
        self.rotation = partial_angle
        if partial_angle == angle:
            target.damage(self.get_damage(), 'slow')
        else:
            return
Exemple #14
0
class LaserTower(SimpleTower):
    """A tower that fires lasers at a target"""
    name = 'Laser Tower'
    colour = '#6600FF' # purple-ish

    cool_down_steps = 1 #insanely fast

    base_cost = 500
    level_cost = 400 

    range = CircularRange(4)

    rotation_threshold = (1 / 3) * math.pi

    def __init__(self, cell_size: int, grid_size=(.9, .9), rotation=math.pi * .25, base_damage=2, level: int = 1):
        super().__init__(cell_size, grid_size=grid_size, rotation=rotation, base_damage=base_damage, level=level)

        self._target: AbstractEnemy = None

    def _get_target(self, units) -> Union[AbstractEnemy, None]:
        """Returns previous target, else selects new one if previous is invalid
        
        Invalid target is one of:
            - dead
            - out-of-range
            - off the map
        
        Return:
            AbstractEnemy: Returns previous target, unless it is non-existent or invalid (see above),
                           Otherwise, selects & returns new target if a valid one can be found,
                           Otherwise, returns None
        """
        if self._target is None \
                or self._target.is_dead() \
                or not self.is_position_in_range(self._target.position) \
                or self._target.position[0] > 400:
            self._target = self.get_unit_in_range(units)

        return self._target

    def step(self, units):
        """Rotates toward 'target' and fires laser if possible"""
        self.cool_down.step()

        target = self._get_target(units.enemies)

        # if there's no target or if the target is out of the map
        if target is None:
            return None

        
        # Rotate toward target
        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle, self.rotation_threshold)

        self.rotation = partial_angle

        if angle != partial_angle or not self.cool_down.is_done():
            return None

        self.cool_down.start()

        # Spawn laser on tower
        laser = Laser(self.position, self.cell_size, target, rotation=self.rotation,
                          damage=self.get_damage(), grid_speed=.5)

        # Move laser to outer edge of tower
        radius = self.grid_size[0] / 2
        delta = polar_to_rectangular(self.cell_size * radius, partial_angle)
        laser.move_by(delta)


        return [laser]
Exemple #15
0
 def tower_aging(self):
     """Make tower lose effectiveness when the tower is aging"""
     self.colour = "GREY"
     self.base_cost = 0
     self.range = CircularRange(0)
Exemple #16
0
class FireTower(AbstractTower):
    """An advanced tower that pretty much kills everything"""
    name = 'Fire Tower'
    colour = 'Red'  # Coquelicot

    range = CircularRange(1.5)
    cool_down_steps = 5

    base_cost = 100
    level_cost = 80

    rotation_threshold = (1 / 6) * math.pi

    def __init__(self,
                 cell_size: int,
                 grid_size=(.9, .9),
                 rotation=math.pi * .25,
                 base_damage=150,
                 level: int = 1):
        super().__init__(cell_size,
                         grid_size=grid_size,
                         rotation=rotation,
                         base_damage=base_damage,
                         level=level)

        self._target: AdvancedEnemy = None

    def _get_target(self, units) -> Union[AdvancedEnemy, None]:

        if self._target is None \
                or self._target.is_dead() \
                or not self.is_position_in_range(self._target.position):
            self._target = self.get_unit_in_range(units)

        return self._target

    def step(self, units):
        """Rotates toward 'target' and fires missile if possible"""
        self.cool_down.step()

        target = self._get_target(units.enemies)

        if target is None:
            return None

        # Rotate toward target
        angle = angle_between(self.position, target.position)
        partial_angle = rotate_toward(self.rotation, angle,
                                      self.rotation_threshold)

        self.rotation = partial_angle

        if angle != partial_angle or not self.cool_down.is_done():
            return None

        self.cool_down.start()

        # Spawn missile on tower
        fire = Fire(self.position,
                    self.cell_size,
                    target,
                    rotation=self.rotation,
                    damage=self.get_damage(),
                    grid_speed=.1)

        # Move missile to outer edge of tower
        radius = self.grid_size[0] / 2
        delta = polar_to_rectangular(self.cell_size * radius, partial_angle)
        fire.move_by(delta)

        return [fire]