def gen_input_hero(self, hero, rival_towers): if hero.state == 'out' or hero.hp <= 0: return list(np.zeros(13+3*19)) dis_rival = 10000 if len(rival_towers) > 0: dis_list = [StateUtil.cal_distance2(hero.pos, t.pos) for t in rival_towers] dis_rival = min(dis_list) hero_input = [self.normalize_value(int(hero.hero_name)), self.normalize_value(hero.pos.x), self.normalize_value(hero.pos.z), self.normalize_value(hero.speed), self.normalize_value(hero.att), # todo: 2 是普攻手长,现只适用于1,2号英雄,其他英雄可能手长不同 0.2, self.normalize_value(hero.mag), self.normalize_value(hero.hp), hero.hp/float(hero.maxhp), self.normalize_value(hero.mp), self.normalize_value(dis_rival), hero.team] is_enemy_visible = hero.is_enemy_visible() hero_input.append(int(is_enemy_visible)) skill_info1 = SkillUtil.get_skill_info(hero.cfg_id, 1) skill_info2 = SkillUtil.get_skill_info(hero.cfg_id, 2) skill_info3 = SkillUtil.get_skill_info(hero.cfg_id, 3) skill_input1 = self.gen_input_skill(skill_info1, hero.skills[1]) skill_input2 = self.gen_input_skill(skill_info2, hero.skills[2]) skill_input3 = self.gen_input_skill(skill_info3, hero.skills[3]) hero_input=hero_input+skill_input1+skill_input2+skill_input3 return hero_input
def find_next_tgt(state_info, unit, soldier_list): # 为了节省计算量,我们只从英雄附近的小兵中寻找被攻击者, 或者是英雄 heros = StateUtil.get_heros_in_team(state_info, 1 - unit.team_id) hero = heros[0] min_dis = StateUtil.cal_distance2(hero.pos, unit.pos) tgt = hero.hero_name for soldier in soldier_list: dis = StateUtil.cal_distance2(soldier.pos, unit.pos) if dis < min_dis: tgt = soldier.unit_name min_dis = dis if min_dis <= StateUtil.TOWER_ATTACK_RADIUS * 1000: return tgt return None
def gen_input_building(self, building, query_hero=None, state_info=None, hero_name=None, revert=False): if building is None: building_info = np.zeros(8) building_info = list(building_info) else: hero_info = state_info.get_hero(hero_name) building_info = [ self.normalize_value(building.pos.x - query_hero.pos.x if not revert else -( building.pos.x - query_hero.pos.x)), self.normalize_value(building.pos.z - query_hero.pos.z if not revert else -( building.pos.z - query_hero.pos.z)), self.normalize_value(building.att), self.normalize_value(7000), self.normalize_value(building.hp), self.normalize_value( StateUtil.cal_distance2(building.pos, hero_info.pos)), building.team if not revert else 1 - building.team ] # 添加是否在攻击当前英雄 attack_info = state_info.if_unit_attack_hero( building.unit_name, hero_name) if attack_info is None: building_info.append(0) else: building_info.append(1) return building_info
def assert_tower_in_input(cur_state, hero_name, rival_hero): # 如果敌方塔要攻击英雄的话,检查塔的信息是不是在input中 att_info = cur_state.if_tower_attack_hero(hero_name) if att_info is not None: tower = str(att_info.atker) tower_info = cur_state.get_obj(tower) hero_info = cur_state.get_hero(hero_name) model_input = LineModel_PPO1.gen_input(cur_state, hero_name, rival_hero) if model_input[44] == Line_Input_Lite.normalize_value_static( int(tower)): print('yes found attack tower in input', tower, 'distance', model_input[50], 'cal_distance', StateUtil.cal_distance2(tower_info.pos, hero_info.pos)) else: print('not found attack tower in input', tower, 'distance', model_input[50], 'cal_distance', StateUtil.cal_distance2(tower_info.pos, hero_info.pos))
def play_attack(state_info, attacker, defender, time_second=0.5): attacker_info = state_info.get_hero( attacker) if StateUtil.if_unit_hero( attacker) else state_info.get_unit(attacker) defender_info = state_info.get_hero( defender) if StateUtil.if_unit_hero( defender) else state_info.get_unit(defender) #TODO 解决塔和小兵,还有英雄不同的攻击频率问题,有些攻击其实不是每次更新都需要触发的 # 更新攻击信息,这个信息对下一帧的计算会起来指导作用 # 如果已经存在一个,需要替换掉 # 忽略技能id这个参数,因为这种情况下不会被使用到 att = AttackStateInfo(attacker, defender, defender_info.pos, -1) state_info.update_attack_info(att) # 如果攻击者是小兵,为了简化,默认都可以攻击到英雄 if StateUtil.if_unit_soldier( attacker_info.cfg_id) and StateUtil.if_unit_long_range_attack( attacker_info.cfg_id): # 更新位置信息 attacker_info.pos = defender.pos state_info.update_unit(attacker_info) # 英雄攻击,需要考虑距离问题,还有追击的位移情况 #TODO 这里的英雄位置,可能是移动之前的,也可能是移动之后的,其实需要分别处理。而且判断是否攻击到的条件太简单了 elif StateUtil.if_unit_hero(attacker): dis = StateUtil.cal_distance2(attacker_info.pos, defender_info.pos) move_dis = attacker_info.speed * time_second if dis > move_dis: # 只移动,不攻击 attacker_info.pos = PlayEngine.move_towards( attacker_info.pos, defender_info.pos, move_dis, dis) StateUtil.update_hero(attacker_info) return state_info else: attacker_info.pos = defender_info.pos StateUtil.update_hero(attacker_info) #TODO 计算最终伤害的公式很不清楚,需要后续核实。减伤比例为 防御/(防御+100)=防御减伤,但是防御值怎么计算得来 if StateUtil.if_unit_hero(defender): hero_cfg_id = defender_info.cfg_id defend_value, mag_defend_value = PlayEngine.get_defend_value( hero_cfg_id, defender_info.level) dmg = attacker_info.attack * defend_value / (defend_value + 100) defender_info.hp = max(defender_info.hp - dmg, 0) state_info.update_hero(defender_info) else: dmg = attacker_info.attack defender_info.hp = max(defender_info.hp - dmg, 0) state_info.update_unit(defender_info) return state_info
def find_skill_targets(state_info, attacker_info, tgt_pos, skill_length, skill_width, is_circle): if not is_circle: # 首先满足直线距离应该小于技能长度 tgt_unit_list = [ unit for unit in state_info.units if StateUtil.cal_distance2(attacker_info.pos, unit.pos) <= skill_length and not StateUtil.if_unit_tower(unit.unit_name) ] tgt_hero_list = [ hero for hero in state_info.heros if StateUtil.cal_distance2(attacker_info.pos, hero.pos) <= skill_length and hero.hero_name != attacker_info.hero_name ] # 需要旋转坐标系 x1 = xcosa + ysina, y1 = ycosa - xsina # 或者按比例计算最大最下z值(相当于坐标系上的x) tgt_units = [] for unit in tgt_unit_list: mid_x = attacker_info.pos.z + StateUtil.cal_distance2( attacker_info.pos, unit.pos) / StateUtil.cal_distance2( attacker_info.pos, tgt_pos) * (tgt_pos - attacker_info) if mid_x - skill_width / 2 <= unit.x <= mid_x + skill_width / 2: tgt_units.append(unit) tgt_heros = [] for hero in tgt_hero_list: mid_x = attacker_info.pos.z + StateUtil.cal_distance2( attacker_info.pos, hero.pos) / StateUtil.cal_distance2( attacker_info.pos, tgt_pos) * (tgt_pos - attacker_info) if mid_x - skill_width / 2 <= hero.x <= mid_x + skill_width / 2: tgt_heros.append(hero) return tgt_units, tgt_heros else: # 根据tgt_pos画圆,计算范围内的敌人 # 攻击对象不是塔 tgt_unit_list = [ unit for unit in state_info.units if StateUtil.cal_distance2(tgt_pos, unit.pos) <= skill_length and not StateUtil.if_unit_tower(unit.unit_name) ] tgt_hero_list = [ hero for hero in state_info.heros if StateUtil.cal_distance2(tgt_pos, hero.pos) <= skill_length and hero.hero_name != attacker_info.hero_name ] return tgt_unit_list, tgt_hero_list
def gen_input_hero(self, hero, query_hero, rival_towers, revert=False): if hero.state == 'out' or hero.hp <= 0: return list(np.zeros(16 + 3 * 17)) dis_rival = 10000 if len(rival_towers) > 0: dis_list = [ StateUtil.cal_distance2(hero.pos, t.pos) for t in rival_towers ] dis_rival = min(dis_list) hero_input = [ self.normalize_value(hero.pos.x - query_hero.pos.x if not revert else -( hero.pos.x - query_hero.pos.x)), self.normalize_value(hero.pos.z - query_hero.pos.z if not revert else -( hero.pos.z - query_hero.pos.z)), self.normalize_value(hero.speed), self.normalize_value(hero.att), self.normalize_value(hero.attspeed), self.normalize_value(hero.attpen), self.normalize_value(hero.attpenrate), # # todo: 2 是普攻手长,现只适用于1,2号英雄,其他英雄可能手长不同 # 0.2, self.normalize_value(hero.hp), hero.hp / float(hero.maxhp), self.normalize_value(hero.hprec), self.normalize_value(hero.mp), self.normalize_value(hero.mag), self.normalize_value(hero.magpen), self.normalize_value(hero.magpenrate), self.normalize_value(dis_rival), hero.team if not revert else 1 - hero.team ] # is_enemy_visible = hero.is_enemy_visible() # hero_input.append(int(is_enemy_visible)) skill_info1 = SkillUtil.get_skill_info(hero.cfg_id, 1) skill_info2 = SkillUtil.get_skill_info(hero.cfg_id, 2) skill_info3 = SkillUtil.get_skill_info(hero.cfg_id, 3) skill_input1 = self.gen_input_skill(skill_info1, hero.skills[1]) skill_input2 = self.gen_input_skill(skill_info2, hero.skills[2]) skill_input3 = self.gen_input_skill(skill_info3, hero.skills[3]) hero_input = hero_input + skill_input1 + skill_input2 + skill_input3 return hero_input
def gen_input_building(self,building, state_info=None, hero_name=None): if building is None: building_info=np.zeros(9) building_info=list(building_info) else: hero_info = state_info.get_hero(hero_name) building_info=[self.normalize_value(int(building.unit_name)), self.normalize_value(building.pos.x), self.normalize_value(building.pos.z), self.normalize_value(building.att), self.normalize_value(7000), self.normalize_value(building.hp), self.normalize_value(StateUtil.cal_distance2(building.pos, hero_info.pos)), building.team] # 添加是否在攻击当前英雄 attack_info = state_info.if_unit_attack_hero(building.unit_name, hero_name) if attack_info is None: building_info.append(0) else: building_info.append(1) return building_info
def gen_input_creep(self, creep, query_hero=None, state_info=None, hero_name=None, towers=None, revert=False): if creep is None: return list(np.zeros(7)) dis = 10000 if len(towers) > 0: dis_list = [ StateUtil.cal_distance2(creep.pos, t.pos) for t in towers ] dis = min(dis_list) creep_info = [ self.normalize_value(creep.pos.x - query_hero.pos.x if not revert else -( creep.pos.x - query_hero.pos.x)), self.normalize_value(creep.pos.z - query_hero.pos.z if not revert else -( creep.pos.z - query_hero.pos.z)), self.normalize_value(creep.att), self.normalize_value(creep.hp), self.normalize_value(dis), creep.team if not revert else 1 - creep.team ] # 添加是否在攻击当前英雄 attack_info = state_info.if_unit_attack_hero(creep.unit_name, hero_name) if attack_info is None: creep_info.append(0) else: creep_info.append(1) return creep_info
def build_response(self, state_cache, state_index, hero_name): action_strs=[] restart = False # 对于模型,分析当前帧的行为 if self.real_hero != hero_name: state_info = state_cache[state_index] prev_hero = state_cache[state_index-1].get_hero(hero_name) if len(state_cache) >= 2 is not None else None # 如果有真实玩家,我们需要一些历史数据,所以分析3帧前的行为 elif len(state_cache) > 3: state_info = state_cache[state_index-3] next1_state_info = state_cache[state_index-2] next2_state_info = state_cache[state_index-1] next3_state_info = state_cache[state_index] else: return action_strs, False # 决定是否购买道具 buy_action = EquipUtil.buy_equip(state_info, hero_name) if buy_action is not None: buy_str = StateUtil.build_command(buy_action) action_strs.append(buy_str) # 如果有可以升级的技能,优先升级技能3 hero = state_info.get_hero(hero_name) skills = StateUtil.get_skills_can_upgrade(hero) if len(skills) > 0: skillid = 3 if 3 in skills else skills[0] update_cmd = CmdAction(hero.hero_name, CmdActionEnum.UPDATE, skillid, None, None, None, None, None, None) update_str = StateUtil.build_command(update_cmd) action_strs.append(update_str) # 回城相关逻辑 # 如果在回城中且没有被打断则继续回城,什么也不用返回 if prev_hero is not None: if hero.hero_name in self.hero_strategy and self.hero_strategy[hero.hero_name] == ActionEnum.town_ing \ and prev_hero.hp <= hero.hp \ and not StateUtil.if_hero_at_basement(hero): if not hero.skills[6].canuse: print(self.battle_id, hero.hero_name, '回城中,继续回城') return action_strs, False else: print(self.battle_id, hero.hero_name, '回城失败') town_action = CmdAction(hero.hero_name, CmdActionEnum.CAST, 6, hero.hero_name, None, None, None, None, None) action_str = StateUtil.build_command(town_action) action_strs.append(action_str) return action_strs, False if hero.hp <= 0: self.hero_strategy[hero.hero_name] = None return action_strs, False # # 补血逻辑 # if prev_hero is not None and hero.hero_name in self.hero_strategy and self.hero_strategy[ # hero.hero_name] == ActionEnum.hp_restore: # if StateUtil.cal_distance2(prev_hero.pos, hero.pos) < 100: # print(self.battle_id, hero_name, '到达补血点', '血量增长', hero.hp - prev_hero.hp) # del self.hero_strategy[hero_name] # if hero == self.model1_hero: # self.model1_hp_restore = time.time() # else: # self.model2_hp_restore = time.time() # 撤退逻辑 # TODO 甚至可以使用移动技能移动 if prev_hero is not None and hero.hero_name in self.hero_strategy and self.hero_strategy[hero.hero_name] == ActionEnum.retreat_to_town: if StateUtil.cal_distance2(prev_hero.pos, hero.pos) < 100: print(self.battle_id, hero_name, '开始回城') self.hero_strategy[hero.hero_name] = ActionEnum.town_ing town_action = CmdAction(hero.hero_name, CmdActionEnum.CAST, 6, hero.hero_name, None, None, None, None, None) action_str = StateUtil.build_command(town_action) action_strs.append(action_str) else: print(self.battle_id, hero_name, '还在撤退中', StateUtil.cal_distance2(prev_hero.pos, hero.pos)) return action_strs, False # 如果击杀了对方英雄,扫清附近小兵之后则启动撤退回城逻辑 if prev_hero is not None: if hero.hero_name in self.hero_strategy and self.hero_strategy[hero.hero_name] == ActionEnum.town_ing and prev_hero.hp <= hero.hp \ and not StateUtil.if_hero_at_basement(hero): if not hero.skills[6].canuse: return action_strs, False else: town_action = CmdAction(hero.hero_name, CmdActionEnum.CAST, 6, hero.hero_name, None, None, None, None, None) action_str = StateUtil.build_command(town_action) action_strs.append(action_str) if hero.hp <= 0: self.hero_strategy[hero.hero_name] = None return action_strs, False # 检查周围状况 near_enemy_heroes = StateUtil.get_nearby_enemy_heros(state_info, hero.hero_name, StateUtil.LINE_MODEL_RADIUS) near_enemy_units = StateUtil.get_nearby_enemy_units(state_info, hero.hero_name, StateUtil.LINE_MODEL_RADIUS) nearest_enemy_tower = StateUtil.get_nearest_enemy_tower(state_info, hero.hero_name, StateUtil.LINE_MODEL_RADIUS + 3) nearest_friend_units = StateUtil.get_nearby_friend_units(state_info, hero.hero_name, StateUtil.LINE_MODEL_RADIUS) line_index = 1 near_enemy_units_in_line = StateUtil.get_units_in_line(near_enemy_units, line_index) nearest_enemy_tower_in_line = StateUtil.get_units_in_line([nearest_enemy_tower], line_index) # 如果击杀对面英雄就回城补血。整体逻辑为,周围没有兵的情况下启动撤退逻辑,到达撤退地点之后启动回城。补满血之后再跟兵出来 # 处在泉水之中的时候设置策略层为吃线 if len(near_enemy_units_in_line) == 0 and len(near_enemy_heroes) == 0: if (hero_name == self.model1_hero and self.model2_just_dead == 1 and not StateUtil.if_hero_at_basement(hero)) \ or (hero_name == self.model2_hero and self.model1_just_dead == 1 and not StateUtil.if_hero_at_basement(hero)): if hero.hp / float(hero.maxhp) > 0.8: if hero_name == self.model1_hero: self.model2_just_dead = 0 else: self.model1_just_dead = 0 else: print(self.battle_id, hero_name, '选择撤退') self.hero_strategy[hero_name] = ActionEnum.retreat_to_town retreat_pos = StateUtil.get_retreat_pos(state_info, hero, line_index=1) action = CmdAction(hero_name, CmdActionEnum.MOVE, None, None, retreat_pos, None, None, -1, None) action_str = StateUtil.build_command(action) action_strs.append(action_str) if hero_name == self.model1_hero: self.model2_just_dead = 0 else: self.model1_just_dead = 0 return action_strs, False if StateUtil.if_hero_at_basement(hero): if hero_name == self.model1_hero: self.model2_just_dead = 0 else: self.model1_just_dead = 0 if hero.hp < hero.maxhp: if hero_name in self.hero_strategy: del self.hero_strategy[hero_name] return action_strs, False # # 残血并且周围没有敌人的情况下,可以去塔后吃加血 # if hero.hp / float(hero.maxhp) < 0.9 and hero not in self.hero_strategy: # print('补血条件', self.battle_id, hero_name, time.time(), self.model1_hp_restore, self.model2_hp_restore) # if hero == self.model1_hero and time.time() - self.model1_hp_restore > LineTrainerPPO.HP_RESTORE_GAP: # print(self.battle_id, hero_name, '选择加血') # self.hero_strategy[hero_name] = ActionEnum.hp_restore # elif hero == self.model2_hero and time.time() - self.model2_hp_restore > LineTrainerPPO.HP_RESTORE_GAP: # print(self.battle_id, hero_name, '选择加血') # self.hero_strategy[hero_name] = ActionEnum.hp_restore # # if self.hero_strategy[hero_name] == ActionEnum.hp_restore: # restore_pos = StateUtil.get_hp_restore_place(state_info, hero) # action = CmdAction(hero_name, CmdActionEnum.MOVE, None, None, restore_pos, None, None, -1, None) # action_str = StateUtil.build_command(action) # action_strs.append(action_str) # return action_strs, False # 开始根据策略决定当前的行动 # 对线情况下,首先拿到兵线,朝最前方的兵线移动 # 如果周围有危险(敌方单位)则启动对线模型 # 如果周围有小兵或者塔,需要他们都是在指定线上的小兵或者塔 if (len(near_enemy_units_in_line) == 0 and len(nearest_enemy_tower_in_line) == 0 and ( len(near_enemy_heroes) == 0 or StateUtil.if_in_line(hero, line_index, 4000) == -1) ) or\ (len(nearest_friend_units) == 0 and len(near_enemy_units_in_line) == 0 and len(near_enemy_heroes) == 0 and len(nearest_enemy_tower_in_line) == 1): # 跟兵线或者跟塔,优先跟塔 self.hero_strategy[hero.hero_name] = ActionEnum.line_1 # print("策略层:因为附近没有指定兵线的敌人所以开始吃线 " + hero.hero_name) front_soldier = StateUtil.get_frontest_soldier_in_line(state_info, line_index, hero.team) first_tower = StateUtil.get_first_tower(state_info, hero) if front_soldier is None or (hero.team == 0 and first_tower.pos.x > front_soldier.pos.x) or (hero.team == 1 and first_tower.pos.x < front_soldier.pos.x): # 跟塔,如果塔在前面的话 follow_tower_pos = StateUtil.get_tower_behind(first_tower, hero, line_index=1) move_action = CmdAction(hero.hero_name, CmdActionEnum.MOVE, None, None, follow_tower_pos, None, None, None, None) action_str = StateUtil.build_command(move_action) action_strs.append(action_str) else: # 得到最前方的兵线位置 move_action = CmdAction(hero.hero_name, CmdActionEnum.MOVE, None, None, front_soldier.pos, None, None, None, None) action_str = StateUtil.build_command(move_action) action_strs.append(action_str) else: if self.real_hero != hero_name: # 使用模型进行决策 # print("使用对线模型决定英雄%s的行动" % hero.hero_name) self.hero_strategy[hero.hero_name] = ActionEnum.line_model # 目前对线只涉及到两名英雄 rival_hero = '28' if hero.hero_name == '27' else '27' action, explorer_ratio, action_ratios = self.get_action(state_info, hero_name, rival_hero) # 考虑使用固定策略 # 如果决定使用策略,会连续n条行为全都采用策略(比如确保对方残血时候连续攻击的情况) # 如果策略返回为空则表示策略中断 if self.policy_ratio > 0 and ( 0 < self.cur_policy_act_idx_map[hero_name] < self.policy_continue_acts or random.uniform(0, 1) <= self.policy_ratio ): policy_action = LineTrainerPolicy.choose_action(state_info, action_ratios, hero_name, rival_hero, near_enemy_units, nearest_friend_units) if policy_action is not None: policy_action.vpred = action.vpred action = policy_action self.cur_policy_act_idx_map[hero_name] += 1 print("英雄 " + hero_name + " 使用策略,策略行为计数 idx " + str(self.cur_policy_act_idx_map[hero_name])) if self.cur_policy_act_idx_map[hero_name] >= self.policy_continue_acts: self.cur_policy_act_idx_map[hero_name] = 0 else: # 策略中断,清零 if self.cur_policy_act_idx_map[hero_name] > 0: print("英雄 " + hero_name + " 策略中断,清零") self.cur_policy_act_idx_map[hero_name] = 0 action_str = StateUtil.build_command(action) action_strs.append(action_str) # 如果是要求英雄施法回城,更新英雄状态,这里涉及到后续多帧是否等待回城结束 if action.action == CmdActionEnum.CAST and int(action.skillid) == 6: print("英雄%s释放了回城" % hero_name) self.hero_strategy[hero.hero_name] = ActionEnum.town_ing # 如果是选择了撤退,进行特殊标记,会影响到后续的行为 if action.action == CmdActionEnum.RETREAT: print("英雄%s释放了撤退,撤退点为%s" % (hero_name, action.tgtpos.to_string())) self.hero_strategy[hero.hero_name] = ActionEnum.retreat self.retreat_pos = action.tgtpos # 如果批量训练结束了,这时候需要清空未使用的训练集,然后重启游戏 if action.action == CmdActionEnum.RESTART: restart = True else: # 保存action信息到状态帧中 state_info.add_action(action) else: # 还是需要模型来计算出一个vpred rival_hero = '28' if hero.hero_name == '27' else '27' action, explorer_ratio, action_ratios = self.get_action(state_info, hero_name, rival_hero) # 推测玩家的行为 guess_action = Replayer.guess_player_action(state_info, next1_state_info, next2_state_info, next3_state_info, hero_name, rival_hero) guess_action.vpred = action.vpred action_str = StateUtil.build_command(guess_action) action_str['tick'] = state_info.tick print('猜测玩家行为为:' + JSON.dumps(action_str)) # 保存action信息到状态帧中 state_info.add_action(guess_action) return action_strs, restart
def in_battle_range(pos, battle_range): dis = StateUtil.cal_distance2(pos, TeamBattleTrainer.BATTLE_CIRCLE) if dis < battle_range * 1000 + 500: return -1 return dis