class Foe(Character): @ctx_decorator def __init__(self, db, ctx: Ctx, foe_name=None, foe_candidate="Random", challenge_level=".25", damage_generator="Random", hit_point_generator="Max"): gender_candidate = 'U' self.character_id = -1 self.ctx = ctx self.name = foe_name ability_array_str = 'Common' if foe_candidate == "Random": foe_candidate = self.find_random(db=db, challenge_level=challenge_level) level = 1 # Player level will always be 1 for Foes Character.__init__(self, db=db, ctx=ctx, gender_candidate=gender_candidate, ability_array_str=ability_array_str, damage_generator=damage_generator, hit_point_generator=hit_point_generator, level=level) self.get_foe(db=db, foe_candidate=foe_candidate) self.stats.character_id = self.character_id self.stats.character_class = self.race # this looks funky, but characterStats presentation for the foe race self.stats.character_race = self.foe_type # is easier to compare using the class as "skeleton" and race as # Undead. Leaving that like that for it. In all programmatic work the foe race and foe_type will be used. if self.melee_weapon is not None: self.melee_weapon_obj = Weapon(db=db, ctx=ctx, name=self.get_melee_weapon()) self.melee_weapon_obj.set_weapon_proficient() if self.ranged_weapon is not None: self.ranged_weapon_obj = Weapon(db=db, ctx=ctx, name=self.get_ranged_weapon()) self.melee_weapon_obj.set_weapon_proficient() self.set_damage_adjs(db=db) self.class_eval.append({ "pythonClass": "Foe", "foe_candidate": foe_candidate, "challenge_level": challenge_level, "damage_generator": damage_generator, "hit_point_generator": hit_point_generator, "level": level }) self.logger.info(msg="user_audit", json_dict=self.__dict__, ctx=ctx) def get_melee_weapon(self): return self.melee_weapon def get_ranged_weapon(self): if self.ranged_weapon: return_val = self.ranged_weapon else: return_val = "Not_Defined" return return_val @ctx_decorator def default_melee_attack(self, ctx, target_name, target, attacker_id='unknown', vantage='Normal', luck_retry=False): return self.melee_attack(ctx=ctx, weapon_obj=self.melee_weapon_obj, attacker_id=attacker_id, target_name=target_name, target=target, vantage=vantage, luck_retry=luck_retry) @ctx_decorator def default_ranged_attack(self, ctx, target, attacker_id='unknown', vantage='Normal', luck_retry=False): self.stats.ranged_attack_attempts += 1 return self.ranged_attack(ctx=ctx, weapon_obj=self.ranged_weapon_obj, attacker_id=attacker_id, target=target, vantage=vantage, luck_retry=luck_retry) @ctx_decorator def find_random(self, db, challenge_level): sql = (f"SELECT name FROM dnd_5e.foe " f"where challenge_level='{challenge_level}' " f"ORDER BY RANDOM() LIMIT 1") results = db.query(sql) try: retstr = results[0][0] except IndexError: raise ValueError( f'Could not find foe for challenge level: {challenge_level}') return retstr @ctx_decorator def get_foe(self, db, foe_candidate): sql = (f"SELECT id, name, foe_type, size, base_walking_speed, " f"challenge_level, ability_string, ability_modifier_string, " f"hit_point_die, hit_point_modifier, hit_point_adjustment, " f"standard_hit_points, alignment, ranged_weapon, melee_weapon, " f"ranged_ammunition_type, ranged_ammunition_amt, armor, " f"shield, source_material, source_credit_url, " f"source_credit_comment FROM dnd_5e.foe " f"where name='{foe_candidate}'") results = db.query(sql) try: self.character_id = results[0][0] # id, if self.name is None: self.name = results[0][1] # name, # if name is used for something specific (skeleton_1, or jimmyjam, # this will tell us what creature it is. # Otherwise, these two will match. self.race = results[0][1] self.foe_type = results[0][2] # foe_type, self.size = results[0][3] # size, self.base_walking_speed = results[0][4] # base_walking_speed, self.cur_movement = results[0][4] self.challenge_level = results[0][5] # challenge_level, self.ability_array_str = results[0][6] # ability_string, # ability_modifier_string, self.ability_modifier_array = string_to_array(results[0][7]) self.hit_point_die = results[0][8] # hit_point_die, self.hit_point_modifier = results[0][9] # hit_point_modifier, self.hit_point_adjustment = results[0][10] self.standard_hit_points = results[0][11] self.alignment = results[0][12] # alignment, self.ranged_weapon = results[0][13] # ranged_weapon, self.melee_weapon = results[0][14] # melee_weapon, self.ranged_ammunition_type = results[0][15] self.ranged_ammunition_amt = results[0][16] self.armor = results[0][17] # armor, self.shield = results[0][18] # shield, self.source_material = results[0][19] # source_material, self.source_credit_url = results[0][20] # source_credit_url, self.source_credit_comment = results[0][21] self.assign_ability_array() self.set_armor_class() self.hit_points = self.assign_hit_points() self.cur_hit_points = self.hit_points self.temp_hit_points = 0 except IndexError: raise ValueError(f'Could not find foe: {foe_candidate}') @ctx_decorator def set_damage_adjs(self, db): sql = (f"select rt.affected_name, rt.affect " f"from lu_foe_trait as rt " f"join foe as r on rt.foe = r.name " f"where rt.category = 'Damage Received' " f"and rt.affect is not null " f"and rt.affected_name in ('Acid', 'Bludgeoning', " f"'Cold', 'Fire', 'Force', 'Ligtning', 'Necrotic'," f"'Piercing', 'Poison', 'Psychic', 'Radiant'," f"'Slashing', 'Thunder')" f"and r.name = '{self.get_race()}' ") rows = db.query(sql) for row in rows: self.damage_adj[row[0]] = row[1] @ctx_decorator def assign_hit_points(self): if not self.hit_point_adjustment: self.hit_point_adjustment = 0 if self.hit_point_generator == 'Max': ret_val = ((self.hit_point_modifier * self.hit_point_die) + self.hit_point_adjustment) elif self.hit_point_generator == 'Standard': ret_val = self.standard_hit_points else: d = Die(self.hit_point_die) ret_val = (d.roll(self.hit_point_modifier) + self.hit_point_adjustment) jdict = { "hit_point_generator": self.hit_point_generator, "hit_point_modifier": self.hit_point_modifier, "hit_point_die": self.hit_point_die, "hit_point_admustment": self.hit_point_adjustment } self.ctx.crumbs[-1].add_audit(json_dict=jdict) return ret_val def get_combat_preference(self): # TODO: Add a Ranged option if self.ranged_weapon: return_val = "Mixed" else: return_val = "Melee" return return_val def get_racial_traits(self): return None def get_name(self): return self.name def get_race(self): return self.race def get_class(self): return self.foe_type def get_alignment_str(self): return self.alignment @ctx_decorator def get_alignment_abbrev(self): sql = (f"select abbreviation from lu_alignment where value = " f"'{self.alignment}';") results = self.db.query(sql) return results[0][0] @ctx_decorator def is_not_using_shield(self): if self.shield == 'None': ret_val = True else: ret_val = False return ret_val def __str__(self): outstr = (f'{self.__class__.__name__}\n' f'gender: {self.get_gender()}\n' f'name: {self.get_name()}\n' f'race: {self.get_race()}\n' f'foe_type: {self.foe_type}\n' f'size: {self.size}\n' f'alignment: {self.get_alignment_str() }\n' f'alignment abbrev: {self.get_alignment_abbrev() }\n' f'base_walking_speed: {self.base_walking_speed }\n' f'challenge_level: {self.challenge_level }\n' f'ability_array_str: {self.ability_array_str }\n' f'abilityArrayStr: {self.get_ability_array()}\n' f'ability_modifier_array: {self.ability_modifier_array }\n' f'hit_point_die: {self.hit_point_die }\n' f'hit_point_modifier: {self.hit_point_modifier }\n' f'hit_point_adjustment: {self.hit_point_adjustment}\n' f'standard_hit_points: {self.standard_hit_points}\n' f'armor_class: {self.armor_class}\n' f'hit_points: {self.hit_points}\n' f'cur_hit_points: {self.cur_hit_points}\n' f'temp_hit_points: {self.temp_hit_points}\n' f'ranged_weapon: {self.ranged_weapon }\n' f'melee_weapon: {self.melee_weapon }\n' f'ranged_ammunition_type: {self.ranged_ammunition_type }\n' f'ranged_ammunition_amt: {self.ranged_ammunition_amt }\n' f'armor: {self.armor }\n' f'shield: {self.shield }\n' f'source_material: {self.source_material }\n' f'source_credit_url: {self.source_credit_url}\n' f'source_credit_comment: {self.source_credit_comment}\n') outstr = f'\n{outstr}\n' return outstr def __repr__(self): outstr = ( f'{{ "class": "{self.__class__.__name__}", ' f'"gender": "{self.get_gender()}", ' f'"name": "{self.get_name()}", ' f'"race": "{self.get_race()}", ' f'"foe_type": "{self.foe_type}", ' f'"size": "{self.size}", ' f'"alignment": "{self.get_alignment_str() }", ' f'"alignment abbrev": "{self.get_alignment_abbrev() }", ' f'"base_walking_speed": "{self.base_walking_speed}", ' f'"challenge_level": "{self.challenge_level}", ' f'"ability_array_str": "{self.ability_array_str}", ' f'"returned ability_array": {self.get_ability_array()}, ' f'"ability_modifier_array": "{self.ability_modifier_array }", ' f'"hit_point_die": {self.hit_point_die }, ' f'"hit_point_modifier": {self.hit_point_modifier }, ' f'"hit_point_adjustment": {self.hit_point_adjustment}, ' f'"standard_hit_points": {self.standard_hit_points}, ' f'"armor_class": {self.armor_class}, ' f'"hit_points": {self.hit_points}, ' f'"cur_hit_points": {self.cur_hit_points}, ' f'"temp_hit_points": {self.temp_hit_points}, ' f'"ranged_weapon": "{self.ranged_weapon }", ' f'"melee_weapon": "{self.melee_weapon }", ' f'"ranged_ammunition_type": "{self.ranged_ammunition_type }", ' f'"ranged_ammunition_amt": {self.ranged_ammunition_amt }, ' f'"armor": "{self.armor }", ' f'"shield": "{self.shield }", ' f'"source_material": "{self.source_material }", ' f'"source_credit_url": "{self.source_credit_url}", ' f'"source_credit_comment": "{self.source_credit_comment}" }}') return outstr