class Villager(Image, threading.Thread): lock = threading.RLock() # for testing purpose only want to create one leader leader_taken = False def __init__(self, image, position, villager_id, font, listener, current_leader, skill_images): self.role = Role.FOLLOWER self.listener = listener self.current_leader = current_leader self.leadership_term = 0 # render shouting self.message_count = 1 # for testing to only create one leader self.skills = [] self.skill_adding_list = [] self.max_health = Constant.VILLAGER_MAX_HP self.current_health = self.max_health self.current_message = "" self.message_countdown = 0 self.learned_skill_names = [] self.turning_learned_skills_list = [] self.dead = False self.dead_message_sent = False width, height = image.get_rect().size center_x, center_y = position super().__init__(image, center_x, center_y, height, width) self.villager_id = villager_id self.font = font self.attacked = False self.item = [] self.attack = None self.land = Land(self, Constant.LAND_SIZE) self.house = None self.build_house_countdown = Constant.BUILD_HOUSE_COUNT_DOWN threading.Thread.__init__(self) self.attack_probability = 0.5 self.attack_display_count_down = Constant.ATTACK_DISPLAY_COUNT_DOWN self.attack_display_count_down_const = Constant.ATTACK_DISPLAY_COUNT_DOWN self.attacked = False self.attack_power = 1 self.skill_images = skill_images def pickTile(self, tile): """ Check which tile is clicked by mouse, and applied its benefits to Villager :param tile: Tile :return: """ if tile.mature: if tile.tile_type == Constant.TILE_TYPE_PLANT: self.current_health_up_with_amount( Constant.PLANT_HEALTH_INCREASE) elif tile.tile_type == Constant.TILE_TYPE_ANIMAL: self.current_health_up_with_amount( Constant.ANIMAL_HEALTH_INCREASE) tile.un_mature() def addHouse(self): """ Add a house Object to Villager """ self.house = House(self.x, self.y) # armour def addItemToLeftHand(self, image, item_name, image_scale): """ Adding a item to the left hand side of the villager :param image: Image :param item_name: str :param image_scale: int :return: """ width, height = image.get_rect().size temp_item_center_x = self.x + width * image_scale // 2 temp_item_center_y = self.y + width * image_scale temp_item = Item(image, temp_item_center_x, temp_item_center_y, item_name, image_scale) self.item.append(temp_item) # sword def addItemToRightHand(self, image, item_name, image_scale): """ Adding a item to the right hand side of the villager :param image: Image :param item_name: str :param image_scale: int :return: """ width, height = image.get_rect().size temp_item_center_x = self.x - width * image_scale temp_item_center_y = self.y temp_item = Item(image, temp_item_center_x, temp_item_center_y, item_name, image_scale) self.item.append(temp_item) def being_attacked(self, hp_decrement): """ if villager is attcked set the hp down :param hp_decrement: int """ self.current_health_down_with_amount(hp_decrement) def add_skill(self, skill_name): """ Add skill Object to player's skill list :param skill_name: str """ skill_num = len(self.skills) image = self.skill_images[skill_name] # each row render four skill, then go up one_skill = Skill( skill_name, image, self.x - self.width / 2 - ((image.get_rect().size)[0] * Constant.SKILL_IMAGE_SCALE_VILLAGER) / 2, (self.y + self.height / 2) - (int(skill_num) * int( (image.get_rect().size)[1] * Constant.SKILL_IMAGE_SCALE_VILLAGER)), Constant.SKILL_IMAGE_SCALE_VILLAGER, False) self.skills.append(one_skill) def run(self): while (not self.dead) and (not self.listener.stopped): # consuming the parsed JSON message from the queue request = self.listener.request_queue.get() request_type = request[Constant.MESSAGE_TYPE] # according to the type of the request applying corresponding methods to villager if request_type == Constant.VILLAGER_DEAD: self.dead = True continue if request_type == Constant.APPEND and self.role == Role.LEADER: if not request[Constant.NEW_ENTRIES]: self.reclaim_authority() elif request_type == Constant.LEADERSHIP: self.set_leadership(request) elif request_type == Constant.REQUEST_VOTE: self.set_candidate(request) elif request_type == Constant.REQUEST_VOTE_REPLY: self.vote(request) elif request_type == Constant.REQUEST_COMMAND_ACK and self.role == Role.LEADER: self.leader_receive_learn(request) elif request_type == Constant.APPEND_REPLY: self.learning_skill(request) elif request_type == Constant.COMMIT_INDEX: self.learned_skill(request) if self.current_health == 0: debug_print("Villager" + str(self.villager_id) + " is dead") self.dead = True if self.listener.stopped: print(str(self.villager_id) + "'s listener is dead") self.dead = True if self.dead: # if dead send the JSON to cooresponding remote Raft peer to ask it to terminate data = { Constant.MESSAGE_TYPE: "villager_killed", Constant.PEER_ID: self.listener.peer_id } try: self.listener.socket.sendall( str.encode(json.dumps(data) + "\n")) print("villager killed message sent") except ConnectionResetError: print("connection dead") print("villager killed message sent") self.listener.stop_listener() self.dead_message_sent = True def reclaim_authority(self): """ Display a dialgue box to show the string 'I'm the leader' """ self.set_message(Constant.AUTHORITY_MESSAGE) def set_leadership(self, request): """ trying to set the leader by this leadership request dictionary :param request: dict """ term = request[Constant.SENDER_TERM] # if there is still a leader and the term number of JSON messag is smaller than # current term ignore this outdated leader messge if self.current_leader and self.current_leader.leadership_term > term: return self.role = Role.LEADER self.leadership_term = term self.set_message(Constant.NEW_LEADER_MESSAGE) def set_candidate(self, request): """ set villager to candidate by this request_vote request :param request: dict """ term = request[Constant.SENDER_TERM] # abort the outdated request_vote message if self.current_leader and self.current_leader.leadership_term > term: return self.role = Role.CANDIDATE self.set_message(Constant.CANDIDATE_MESSAGE) def vote(self, request): term = request[Constant.SENDER_TERM] if self.current_leader and self.current_leader.leadership_term > term: return vote_for = request[Constant.VOTE_PEER_ID][4:] debug_print(type(request[Constant.VOTE_GRANTED])) if request[Constant.VOTE_GRANTED] == True: self.set_message(Constant.VOTE_MESSAGE.format(vote_for)) def leader_receive_learn(self, request): skill_name = request[Constant.REQUEST_COMMAND_LIST][0] index = int(request[Constant.INDEX]) if index == len(self.skills): self.add_skill(skill_name) while self.skill_adding_list: length = len(self.skills) if self.skill_adding_list[0][0] == length: skill = self.skill_adding_list.pop(0) self.add_skill(skill[1]) else: break elif index > len(self.skills): self.skill_adding_list.append((index, skill_name)) self.skill_adding_list.sort() def learning_skill(self, request): result = request[Constant.APPEND_RESULT] if result and self.current_leader: index = int(request[Constant.LAST_LOG_INDEX]) if (index >= len(self.skills)) and (index < len( self.current_leader.skills)): for i in range(len(self.skills), index + 1): self.add_skill(self.current_leader.skills[i].skill_name) def learned_skill(self, request): debug_print("in learned_skill") if not request: while self.turning_learned_skills_list and self.turning_learned_skills_list[ 0][0] == len(self.learned_skill_names): skill = self.turning_learned_skills_list.pop(0) self.learned_skill(skill[1]) return index = int(request[Constant.INDEX]) debug_print("index is" + str(index)) debug_print("skills: ") debug_print(self.skills) if (index < len(self.skills)) and (index == len( self.learned_skill_names)): skill_name = self.skills[index].skill_name debug_print("skill name: " + skill_name) else: while self.turning_learned_skills_list and self.turning_learned_skills_list[ 0][0] == len(self.learned_skill_names): skill = self.turning_learned_skills_list.pop(0) self.learned_skill(skill[1]) self.turning_learned_skills_list.append((index, request)) self.turning_learned_skills_list.sort() debug_print("returned in else") return if skill_name not in Constant.SKILLS: debug_print("not in skills") return if skill_name == Constant.ARMOUR: self.addItemToLeftHand(ConstantImage.ARMOUR_IMAGE_SPRITE, Constant.ITEM_NAME_ARMOUR, Constant.ARMOUR_IMAGE_SCLAE) elif skill_name == Constant.SWORD: self.addItemToRightHand(ConstantImage.SWORD_IMAGE_SPRITE, Constant.ITEM_NAME_SWORD, Constant.SWORD_IMAGE_SCALE) elif skill_name == Constant.ANIMAL: for tile in self.land.tiles: if tile.tile_type == Constant.TILE_TYPE_ANIMAL: tile.display_plant_or_animal = True elif skill_name == Constant.PLANT: for tile in self.land.tiles: if tile.tile_type == Constant.TILE_TYPE_PLANT: tile.display_plant_or_animal = True elif skill_name == Constant.HOUSE: self.addHouse() self.skills[index].greyed = False self.skills[index].applied = True debug_print("set skill greyed false") self.learned_skill_names.append(skill_name) def set_message(self, message): self.current_message = message self.message_countdown = Constant.MESSAGE_TIME def max_health_up(self): self.max_health += 1 def max_health_down(self): self.max_health -= 1 def current_health_up(self): self.current_health += 1 def current_health_down(self): self.current_health -= 1 def current_health_up_with_amount(self, hp_increment): self.current_health += hp_increment if self.current_health > self.max_health: self.current_health = self.max_health def attack_monster_or_not(self, monster): if (Constant.SWORD not in self.learned_skill_names) or self.attacked or \ (Constant.SWORD in self.learned_skill_names): return self.attacked = random.random() >= self.attack_probability if self.attacked and self.attack_power > 0: monster.set_attack(self.attack_power) self.attack = AttackAnimation( ConstantImage.VILLAGER_ATTACK_IMAGE_SPRITE, monster.x, monster.y, Constant.VILLAGER_ATTACK_IMAGE_SCALE) self.attack_display_count_down = self.attack_display_count_down_const else: self.attacked = False def current_health_down_with_amount(self, hp_decrement): if self.house is not None and self.house.display_house: self.house.house_durability_decrement_with_amount(hp_decrement) if self.house.current_durability <= 0: self.house.display_house = False return if Constant.ARMOUR in self.learned_skill_names: hp_decrement -= Constant.ITEM_ARMOUR_DEFEND_POWER_ADD if hp_decrement >= self.current_health: self.current_health = 0 self.dead = True return self.current_health -= hp_decrement def build_house(self): if self.house: if not self.house.display_house: if self.build_house_countdown == Constant.BUILD_HOUSE_COUNT_DOWN: self.set_message(Constant.BUILD_HOUSE_MESSAGE) self.build_house_countdown -= 1 elif self.build_house_countdown > 0: self.build_house_countdown -= 1 else: self.house.display_house = True self.build_house_countdown = Constant.BUILD_HOUSE_COUNT_DOWN def render_attack(self, screen): if self.attacked and self.attack_display_count_down != 0: self.attack.render(screen) self.attack_display_count_down -= 1 if self.attack_display_count_down <= 0: self.attacked = False def render(self, screen): if self.house and self.house.display_house: self.house.render(screen) super().render(screen) for one_skill in self.skills: one_skill.render(screen) self.land.render(screen) name = self.font.render("Villager " + str(self.villager_id), 1, Constant.BLACK) screen.blit( name, (self.x - name.get_width() // 2, self.y + self.height // 2)) if self.role != Role.FOLLOWER: if self.role == Role.LEADER: title = "Leader" role = self.font.render(title, 1, Constant.BLACK) screen.blit(role, (self.x - role.get_width() // 2, self.y + self.height // 2 + role.get_height() + 2)) if self.message_countdown > 0: message = self.font.render(self.current_message, 1, Constant.BLACK) screen.blit(message, (self.x - message.get_width() // 2, self.y - self.height // 2 - message.get_height() - 2)) self.message_countdown -= 1 pygame.draw.rect( screen, Constant.GRAY, pygame.Rect((self.x - self.width // 2, self.y - self.height // 2), (self.width, Constant.HEAL_BAR_HEIGHT))) pygame.draw.rect( screen, Constant.RED, pygame.Rect((self.x - self.width // 2, self.y - self.height // 2), (self.width * (self.current_health / self.max_health), Constant.HEAL_BAR_HEIGHT))) for one_item in self.item: one_item.render(screen)