class ArcherTowerShort(ArcherTower): def __init__(self, x, y, range, damage): super().__init__(x, y,range, damage) self.archer_imgs = archer_imgs[:] self.archer_count = 0 self.range = range self.damage = damage self.original_damage = self.damage self.inRange = False self.left = -1 # self.timer = time.time() self.name = 'archer_2' self.cost = [2500, 5500, 7000, 'MAX'] self.menu = Menu(self, self.x, self.y, menu_bg, [2500, 5500, 7000, 'MAX']) self.menu.add_button(upgrade_button, 'upgrade_button', self.cost)
class ArcherTowerShort(ArcherTowerLong): def __init__(self, x, y): super().__init__(x, y) self.tower_imgs = tower_imgs2[:] self.archer_imgs = archer_imgs[:] self.name = "archer_Tower2" self.archer_total_imgs = len(archer_imgs) self.archer_count = 0 self.range = 100 self.inRange = False self.original_range = self.range self.original_damage = self.damage self.left = True self.moving = False self.damage = 2 self.animation_speed_multiplier = 2 self.menu = Menu(self.x, self.y, self, menu_bg, [2000, 5000, "MAX"]) self.menu.add_button(upgrade_button, "Upgrade")
class Tower: """ Abstract class for towers """ def __init__(self, x, y): self.x = x self.y = y self.width = self.height = 96 self.sell_price = [0,0,0] self.price = [0,0,0] self.level = 1 self.range = 200 self.original_range = self.range # Not sure if this should be in here self.selected = False # define menu and buttons self.menu = Menu(self.x, self.y, self, menu_bg, [2000, 5000, 9000, 12000, "MAX"]) self.menu.add_button(upgrade_button, "Upgrade") self.moving = False self.tower_imgs = [] self.damage = 1 self.place_color = (0,0,255, 100) def draw(self, win): """ draws the tower :param win: surface :return: None """ img = self.tower_imgs[self.level - 1] win.blit(img, (self.x-img.get_width()//2, self.y-img.get_height()//2)) # draw menu if self.selected: self.menu.draw(win) def draw_radius(self, win): # draw range circle if selected if self.selected: surface = pygame.Surface((self.range*2, self.range*2), pygame.SRCALPHA, 32) pygame.draw.circle(surface, (128, 128, 128, 100), (self.range, self.range), self.range, 0) win.blit(surface, (self.x - self.range, self.y - self.range)) def draw_placement(self, win): # draw range circle surface = pygame.Surface((self.range*2, self.range*2), pygame.SRCALPHA, 32) pygame.draw.circle(surface, self.place_color, (58, 58), 58, 0) # 58 = 96/2 + 10 win.blit(surface, (self.x - 58, self.y - 58)) def click(self, X, Y): """ returns if tower has been clicked on and select tower if it was clicked :param X: int :param Y: int :return: bool """ img = self.tower_imgs[self.level - 1] if X <= self.x - img.get_width()//2 + self.width and X >= self.x - img.get_width()//2: if Y <= self.y + self.height - img.get_height()//2 and Y >= self.y - img.get_height()//2: return True return False def sell(self): """ call to sell the tower, returns sel price :retutn: int """ return self.sell_price[self.level - 1] def upgrade(self): """ upgrades the tower for a given cost :return: None """ if self.level < len(self.tower_imgs): self.level += 1 self.damage += 1 def get_upgrade_cost(self): """ gets the upgrade cost :return: int """ return self.menu.get_item_cost() def move(self, x, y): """ moves tower to given x and y :param x: int :param y: int :return: None """ self.x = x self.y = y self.menu.x = x self.menu.y = y self.menu.update() def collide(self, otherTower): x2 = otherTower.x y2 = otherTower.y dis = math.sqrt((x2 - self.x)**2 + (y2 - self.y)**2) return True if dis <= 116 else False # width and height of the towers are equals to 96 and adding 10 from both sides = 116
class Tower(GameObjects): """ Base structural class for our towers @param (STR) name: name of object @param (TUPLE) coord: (x, y) coordinates of object """ def __init__(self, name, coord): super().__init__(name, coord) # Tower coordinates self.x = self.coord[0] self.y = self.coord[1] # Allows for upgrade references self.level = 1 # Upgrade Cost self.cost = TowerConstants.UPGRADE_COST[name] # Selling logistics self.original_price = TowerConstants.ORIGINAL_PRICE[name] self.sell_price = [self.original_price, self.cost[0], self.cost[1]] # Tower dimensions self.width = self.dimensions[0] self.height = self.dimensions[1] # Clicking on a tower self.selected = False # Tower images self.tower_images = [] # Specific range for each tower self.range = TowerConstants.TOWER_RADIUS_RANGE[name] # Leveling up animation self.level_up_animation = False self.level_animation = 0 self.level_up = level_up # Menu logistics self.menu = Menu(self, menu_background) self.menu.add_button("upgrade", upgrade_button) self.menu.add_button("sell", sell_button) # Damage for towers that deal damage self.base_damage = 0 self.damage = self.base_damage # For moving the archer tower when purchasing from the shops self.dragged = False # Padding for clicking purposes self.extra_padding = 10 # Keeps track of kill count self.kill_count = 0 self.font = game_font def draw(self, window): """ Draws the tower, menu, and level-up animation upon condition @param (SURFACE) window: surface for rendering the drawing --> return: None """ if self.selected: self.menu.draw(window) #Drawing menu window.blit(kill_count_table, (self.x + self.width // 2 - 15, self.y - self.height // 2 + 35)) kills = self.font.render( str(self.kill_count) + " Kills", 1, (255, 255, 255)) window.blit( kills, (self.x + self.width // 2 + 5, self.y - self.height // 2 + 43)) tower_image = self.tower_images[self.level - 1] if not self.level_up_animation: #Always draw the tower except when leveling up window.blit(tower_image, (self.x - tower_image.get_width() // 2, self.y - tower_image.get_height() // 2)) else: #Leveling up animation procedure window.blit(self.level_up[self.level_animation // 2], (self.x - tower_image.get_width() - 75, self.y - 225)) self.level_animation += 1 if self.level_animation == len(level_up) * 2: self.level_up_animation = False self.level_animation = 0 def click(self, X, Y): """ returns True if tower has been selected else False @param (INT) X: mouse position's x-coordinate @param (INT) Y: mouse position's y-coordinate --> return: Bool """ tower_image = self.tower_images[self.level - 1] if X <= self.x + tower_image.get_width( ) // 2 - 2 * self.extra_padding and X >= self.x - tower_image.get_width( ) // 2 + self.extra_padding // 2: if self.name in TowerConstants.MAGIC_TOWER_NAMES or self.name in TowerConstants.SUP_TOWER_NAMES: if Y <= self.y + self.height // 2 - ( 2 * self.extra_padding ) and Y >= self.y - self.height // 2 + (2 * self.extra_padding): return True else: if Y <= self.y + self.height // 2 - ( 4 * self.extra_padding ) and Y >= self.y - self.height // 2 + (2 * self.extra_padding): return True return False def get_sell_cost(self): """ Sells the tower at a specific level at a reduced cost using our self.sell_price list of prices --> return: Int """ return round(0.75 * self.sell_price[self.level - 1]) def upgrade(self): """ Upgrades our tower and increment certain tower characteristics --> return: None """ if self.level < len(self.tower_images): self.level_up_animation = True self.level += 1 self.base_damage += 3 self.damage = self.base_damage #Since level does not upgrade in menu we have to manually do it here self.menu.tower_level += 1 def get_upgrade_cost(self): """ Returns our upgrade cost for the next tower level --> return: Int """ return self.cost[self.level - 1] def move(self, X, Y): """ Moves our tower to the coordinate (x, y) and updates menu coordinates @param (INT) X: mouse position's x-coordinate @param (INT) Y: mouse position's y-coordinate --> return: None """ self.menu.x, self.menu.y = X, Y self.x, self.y = X, Y self.menu.update_buttons() def get_closest_distance_to_path(self, path): """ Gets the closest distance from a tower at any point to the path @param (LIST) path: list of tuples containing path coordinates --> return: Float """ min_distance_to_line = float("inf") for p in path: game_path = p[:] game_path.sort(key=lambda coord: calculate_distance(self, coord)) point_A = game_path[ 0] # Closest point out of all the points on the path to to the tower try: point_after_A = p[p.index(point_A) + 1] point_before_A = p[p.index(point_A) - 1] closest_to_A = min( point_after_A, point_before_A, key=lambda point: calculate_distance(point_A, point)) except: if p.index(point_A) == 0: closest_to_A = p[p.index(point_A) + 1] elif p.index(point_A) == len(p) - 1: closest_to_A = p[p.index(point_A) - 1] finally: if closest_to_A[0] != point_A[0]: m = (closest_to_A[1] - point_A[1]) / (closest_to_A[0] - point_A[0]) else: m = 2 b = point_A[1] - m * point_A[0] closest_distance = abs(-m * self.x + self.y - b) / math.sqrt((-m)**2 + 1) min_distance_to_line = min(closest_distance, min_distance_to_line) return min_distance_to_line
class ArcherTowerLong(Tower): def __init__(self, x, y): super().__init__(x, y) self.tower_imgs = tower_imgs1[:] self.archer_imgs = archer_imgs[:] self.name = "archer_Tower" self.archer_total_imgs = len(archer_imgs) self.archer_count = 0 self.range = 200 self.original_range = self.range self.original_damage = self.damage self.inRange = False self.left = True self.damage = 1 # self.animation_speed_multiplier = 1 self.width = self.height = 96 self.moving = False self.menu = Menu(self.x, self.y, self, menu_bg, [2000, 5000, 9000, "MAX"]) self.menu.add_button(upgrade_button, "Upgrade") def draw(self, win): """ draw the archer tower and the animated archer :param win: surface :return: int """ super().draw_radius(win) super().draw(win) if self.inRange and not self.moving: self.archer_count += 1 if self.archer_count >= len(self.archer_imgs) * 2: self.archer_count = 0 else: self.archer_count = 0 archer = self.archer_imgs[self.archer_count // 2] win.blit(archer, (self.x - (archer.get_width() / 2), (self.y - archer.get_height() - 20))) def change_range(self, r): """ change range of archer tower :param r: int :return: None """ self.range = r def attack(self, enemies): """ attacks an enemy in the enemy list, modifies list :param enemies: list of enemies :return: None """ money = 0 self.inRange = False enemy_closest = [] for enemy in enemies: x, y = enemy.x, enemy.y dis = math.sqrt((self.x - x)**2 + (self.y - y)**2) if dis < self.range: self.inRange = True enemy_closest.append(enemy) enemy_closest.sort(key=lambda x: x.path_pos, reverse=True) if len(enemy_closest) > 0: first_enemy = enemy_closest[0] if self.archer_count / 2 == 8: if first_enemy.hit(self.damage) == True: money = first_enemy.money enemies.remove(first_enemy) if first_enemy.x < self.x and not self.left: self.left = True for x, img in enumerate(self.archer_imgs): self.archer_imgs[x] = pygame.transform.flip( img, True, False) elif first_enemy.x > self.x and self.left: self.left = False for x, img in enumerate(self.archer_imgs): self.archer_imgs[x] = pygame.transform.flip( img, True, False) return money def flip_archer_imgs(self): # no need atm. pass
class ArcherTower(Tower): def __init__(self, x, y, range, damage): super().__init__(x, y) self.archer_imgs = archer_imgs1[:] self.archer_count = 0 self.range = range self.original_range = self.range self.inRange = False self.damage = damage self.left = -1 self.timer = time.time() self.tower_enemy_closest = [] self.original_damage = self.damage self.moving = False self.name = 'archer' self.cost = [2000, 3500, 6000, 'MAX'] self.menu = Menu(self, self.x, self.y, menu_bg, [2000, 3500, 6000, "MAX"]) self.menu.add_button(upgrade_button, "upgrade_button", self.cost) def draw(self, win): ''' draw the archer tower and its orientation specially :param win: surface :return: ''' super().draw_radius(win) super().draw_menu(win) super().draw(win) if self.inRange and not self.moving: self.archer_count += 1 if self.archer_count >= len(self.archer_imgs)*2: self.archer_count = 0 else: self.archer_count = 0 archer = self.archer_imgs[self.archer_count // 2] # archer01 = pygame.transform.scale(archer, (100, 100)) win.blit(archer, ((self.x + self.width - archer.get_width()/2 + 2), (self.y + self.height - archer.get_height()/2 - 30))) def change_range(self,range): self.range = range + (self.level-1)*50 def attack(self, enemies): ''' attacks an enemy in the enemy list :param enemies: list of enemies :return: int ''' money = 0 self.inRange = False self.left = 1 for enemy in enemies: enemy_x, enemy_y = enemy.x, enemy.y dis = math.sqrt((self.x-enemy_x)**2 + (self.y-enemy_y)**2) # print(dis) if dis < self.range and enemy.health > 0.5 and enemy.x <= 1080: self.inRange = True self.tower_enemy_closest.append(enemy) if len(self.tower_enemy_closest)>0: first_enemy = self.tower_enemy_closest[0] if first_enemy.health < 0: self.tower_enemy_closest.clear() if first_enemy: if time.time() - self.timer >= 0.5: self.timer = time.time() if first_enemy.hit(): # self.tower_enemy_closest.remove(first_enemy) if first_enemy in enemies: money = first_enemy.money enemies.remove(first_enemy) # print(money) return money
class Tower: ''' abstract class for towers ''' def __init__(self, x, y): self.x = x self.y = y self.width = 0 self.height = 0 self.level = 1 self.sell_cost = [0, 0, 0] self.price = [5000, 7000, 12000, 'MAX'] self.selected = False self.item_cost = [5000, 7000, 12000, 'MAX'] self.menu = Menu(self, self.x, self.y, menu_bg, self.item_cost) self.tower_imgs = tower_imgs1[:] self.damage = 1 self.range = 0 self.inRange = 0 self.left = 1 self.menu.add_button(upgrade_button, 'upgrade_button', 0) self.place_color = (36, 120, 132, 100) self.effected = True def draw(self, win): imgs = self.tower_imgs[self.level - 1] win.blit(imgs, (self.x - imgs.get_width()//2, self.y - imgs.get_height()//2)) def draw_menu(self, win): # draw menu if self.selected: self.menu.draw(win) pass def draw_radius(self, win): # draw range circle if self.selected: # print(self.range*4, self.range*4) circle_surface = pygame.Surface((self.range * 4, self.range * 4), pygame.SRCALPHA, 32) # circle_surface.set_alpha(128) # circle_surface.fill(0,255,0)) pygame.draw.circle(circle_surface, (44, 195, 206, 20), (self.range, self.range), self.range, 0) win.blit(circle_surface, (self.x - self.range, self.y - self.range)) # pygame.draw.circle(win, (255, 0, 0), (self.x, self.y), 200, 3) def draw_placement(self, win): # draw range circle circle_surface = pygame.Surface((self.range * 4, self.range * 4), pygame.SRCALPHA, 32) pygame.draw.circle(circle_surface, self.place_color, (50, 50), 45, 0) win.blit(circle_surface, (self.x - 50, self.y - 50)) # print('draw placement range') def draw_shadow(self, win): circle_surface = pygame.Surface((self.range * 4, self.range * 4), pygame.SRCALPHA, 32) pygame.draw.circle(circle_surface, (0, 0, 0, 50), (50, 50), 50) win.blit(circle_surface, (self.x - 50, self.y - 45)) def click(self, X, Y): ''' return true if tower be clicked, else false :param x: int :param y: int :return: bool ''' img = self.tower_imgs[self.level-1] if X <= self.x - img.get_width() // 2 + 120 and X >= self.x - img.get_width() // 2: if Y <= self.y + 120 - img.get_height() // 2 and Y >= self.y - img.get_height() // 2: return True return False pass def sell(self): ''' call to sell the tower, returns sell price :return: int ''' pass def upgrade(self): ''' upgrade the tower by cost :return: ''' if self.level <= 2: self.level += 1 self.damage += self.damage * 0.5 self.range += self.range * 0.25 self.effected = True pass def get_upgrade_cost(self): return self.price[self.level - 1] def move(self, x, y): ''' moves tower to given x and y :param x: int :param y: int :return: ''' self.x = x self.y = y self.menu.x = x self.menu.y = y self.menu.update() def collide(self, otherTower): x2 = otherTower.x y2 = otherTower.y dis = math.sqrt((x2 - self.x)**2 + (y2 - self.y)**2) if dis >= 100: return False else: return True def occupyTheRoad(self): ''' for each two node points in path judge whether the position where tower to be constructed is in a range of t that might occupy the road for monsters :param self: tower object :param path: path node list :param t: range that a tower cant be constructed this amount away from the path of monsters :return: true or false ''' t = 50 for nodeIndex in range(len(path) - 1): x1 = path[nodeIndex][0] y1 = path[nodeIndex][1] x2 = path[nodeIndex + 1][0] y2 = path[nodeIndex + 1][1] # the line where the segment from is Ax+By+C=0 # B=1 A = -((y2 - y1) / (x2 - x1)) C = x1 * ((y2 - y1) / (x2 - x1)) - y1 disToSegment = abs(A * self.x + self.y + C) / math.sqrt(A ** 2 + 1) disToNodePoint1 = math.sqrt((self.x - x1) ** 2 + (self.y - y1) ** 2) disToNodePoint2 = math.sqrt((self.x-x2) ** 2 + (self.y - y2) ** 2) maxDis_NotToIgnore = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + t ** 2) if disToSegment < t and disToNodePoint1 <= maxDis_NotToIgnore and disToNodePoint2 <= maxDis_NotToIgnore: return True return False