def __init__(self, cr, race=None, pc_class=None): super().__init__(cr, race) self.profession = "Mercenary" if pc_class is None: self.npc_class = self.pc_classes() else: self.npc_class = pc_class self.level = smart_clamp(cr - 1, 1, 20) self.player = Player(self.npc_class, self.level) self.stats = self.player.stats self.stat_mod = self.player.stat_mod self.fav_stat_mod = self.stat_mod[self.npc_class.fav_stat] self.magic_bonus = self.player.magic_bonus x = self.player.level y = self.class_price_multi(self.player.cls.name) z = self.fav_stat_mod + self.magic_bonus self.price = smart_clamp(x * y * z, x, 99999) self.name = f"NPC {self.player.cls.name}" self.damage_bonus = self.stat_mod[ self.npc_class.fav_stat] + self.player.magic_bonus self.damage_type = self.player.damage_type self.special_dam_type = self.player.cls.damage_type self.damage_formula = self.player.damage_formula self.special_dam = f"{Monster(self.level).damage}" self.skills = SkillSet(self.level, self.player.prof_bonus, self.stats, self.background) self.inventory = Loot() while len(self.inventory.magic_items) < CR(self.cr).tier: self.inventory.magic_items = career_loot(1, self.level - 1, self.level).magic_items if len(self.inventory.magic_items) > self.level: self.inventory.magic_items = self.inventory.magic_items[:self. level] self.treasure = Loot()
def __init__(self, cr, room_name="Any", name=""): cr_rand = cr + plus_or_minus_linear(2) self.cr = smart_clamp(cr_rand + 3 if "legendary" in name.lower() else cr_rand, -3, 30) item_rank = smart_clamp(self.cr + 3 // 5, 0, 3) self.item_name = self.items_by_rank[item_rank]() if not name else name super().__init__(self.cr, room_name, self.item_name) self.treasure = Loot() self.treasure.magic_items.append(f"Sentient {self.item_name}")
def __init__(self, cls=None, level=0, race=None, point_buy=False): if not cls: self.cls = random_character_class()() if level > 0 else Apprentice( ) else: self.cls = cls() if level > 0 else Apprentice() self.level = smart_clamp(level, 0, 20) self.race = self.random_race() if not race else race self.health = 0 self.xp = 0 self.ac = 10 self.prof_bonus = 0 self.magic_bonus = 0 self.save_dc = 0 self.att_bonus = 0 self.stats = dict( self.cls.stats) if point_buy else self.set_random_stats() self.set_racial_bonuses() self.stat_mod = {} self.stat_mod_str = {} self.set_stat_mods() self.set_ac() self.set_xp() self.set_health() self.prof_bonus_str = "" self.set_prof() self.set_save_dc() self.set_att_bonus() self.saves = list(self.cls.save_prof) self.saving_throws = self.set_saves() self.background = random_background() self.loot = Loot() dam_mod = smart_clamp(self.level // 4, 1, 4) dam_bonus = self.stat_mod[self.cls.fav_stat] + self.magic_bonus self.average_damage = (dam_mod * (self.cls.damage_dice // 2)) + dam_bonus self.damage_formula = f"{dam_mod}d10 + {dam_bonus}" self.special_damage_formula = f"{smart_clamp(self.level // 2, 1, 10)}d8 + {10 + dam_bonus}" self.damage_type = self.find_dam_type() self.special_damage_type = self.cls.damage_type self.loot = get_loot(self.level) self.treasures = set() if self.level > 0: for _ in range(smart_clamp(self.level - 5, 1, 10)): if percent_true(1): self.treasures.add(magic_table_by_cr(self.level + 10)) elif percent_true(50): self.treasures.add(magic_table_by_cr(self.level)) elif len(self.treasures) < 1: self.treasures.add(magic_table_by_cr(self.level - 10))
def __init__(self, cr, dungeon_name=None, dungeon_size=4, wilderness_size=1, settlement_size=1): self.cr = smart_clamp(cr, -3, 30) self.npc = Npc(self.cr) self.race = self.npc.race self.appearance = self.npc.appearance self.mannerism = self.npc.mannerism self.background = self.npc.background self.objective = self.trinkets() if percent_true( 50) else self.actions() self.skill = self.skills() self.wilderness = Wilderness(self.cr, num_levels=wilderness_size, areas_per_level=10) self.dungeon_name = dungeon_name self.dungeon = Dungeon(self.cr + wilderness_size, dungeon_name, dungeon_levels=dungeon_size) self.reward = get_villain_loot(self.dungeon.threats[-1].threat.cr) self.reward.magic_items.append(magic_table_by_cr(self.cr)) self.dc = self.dungeon.threats[-1].threat.save_dc self.city = Settlement(cr=self.cr, num_people=settlement_size * 10) self.total_xp = self.dungeon.total_xp + self.wilderness.total_xp self.summary = '\n'.join(( f"NPC: Primary Quest Giver", f"Race: {self.race}", f"Appearance: {self.appearance}", f"Mannerism: {self.mannerism}", f"Background: {self.background}", f"", f"Quest Details:", f"Starting Location: {self.city.name}", f"Objective: {self.objective}.", f"Location: Beyond the {self.wilderness.name}, deep inside the {self.dungeon.name}.", f"Primary Villain: {self.dungeon.threats[-1].threat.name}", f"Quest Skill Check: {self.skill} {self.dc}", f"Reward: {self.reward}", f"", )) self.total_treasure = sum(( self.dungeon.total_treasure, self.wilderness.total_treasure, self.city.total_treasure, self.reward, ), Loot())
def __init__(self, cr, room_name="Any", name=None): minion_cr = cr - 3 if cr < 6 else cr // 2 minion_cr = smart_clamp(minion_cr, -3, 17) minion_name = name or random_monster("minion") super().__init__(minion_cr, room_name, minion_name) self.dam_type = self.find_dam_type() self.num_appearing = dice(2, 2) self.stats = { ability: ability_dice(7) for ability in self.abilities } self.set_stat_mods() self.individual_hp = max(1, int(self.total_hp // self.num_appearing)) self.treasure = Loot() self.damage_dice = ( "1d2", "1d2", "1d4", "1d4", "1d6", "1d6", "1d6", "1d6", "1d6", "1d6", "1d6", "1d6", "1d6", "1d6", "1d8", "1d8", "1d8", "1d8", "1d8", "1d10", "1d10", "1d10", "1d10", "1d10", "1d12", "1d12", "1d12", "1d12", "1d12", "1d20", "1d20", "1d20", "1d20", "1d20" )[minion_cr + 3] self.damage_bonus = ( 0, 2, 4, 6, 8, 14, 20, 26, 32, 40, 45, 50, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 140, 150, 160, 180, 200, 260, 270, 280, 290, 300 )[minion_cr + 3] actual_damage_bonus = self.damage_bonus // self.num_appearing if actual_damage_bonus > 0: self.individual_dam = f"{self.damage_dice} + {actual_damage_bonus}" else: self.individual_dam = f"{self.damage_dice}" self.at_will_dam = f"{smart_clamp(self.cr_val // 5, 1, 5)}d4 + {self.prof_bonus} {self.dam_type}"
class Npc: professions = TruffleShuffle(npc_dict['professions']) races = QuantumMonty(npc_dict['races']).front_linear appearances = TruffleShuffle(npc_dict['appearances']) talents = TruffleShuffle(npc_dict['talents']) mannerisms = TruffleShuffle(npc_dict['mannerisms']) traits = TruffleShuffle(npc_dict['traits']) bonds = TruffleShuffle(npc_dict['bonds']) flaws = TruffleShuffle(npc_dict['flaws']) trinkets = TruffleShuffle(npc_dict['trinkets']) treasure = Loot() save_dc_list = (13, 13, 13, 13, 13, 13, 13, 14, 15, 15, 15, 16, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 24) racial_bonuses = { "Human": { "STR": 1, 'DEX': 1, 'CON': 1, 'INT': 1, 'WIS': 1, 'CHA': 1 }, "Mountain Dwarf": { "STR": 2, 'CON': 2 }, "Hill Dwarf": { "WIS": 1, 'CON': 2 }, "Shout Halfling": { "DEX": 2, 'CON': 1 }, "Lightfoot Halfling": { "DEX": 2, 'CHA': 1 }, "Forest Gnome": { "INT": 2, 'DEX': 1 }, "Rock Gnome": { "INT": 2, 'CON': 2 }, "High Elf": { "DEX": 2, 'INT': 1 }, "Wood Elf": { "DEX": 2, 'WIS': 1 }, "Half-elf": { "CHA": 2, "DEX": 1, "CON": 1 }, "Drow": { "DEX": 2, 'CHA': 1 }, "Half-orc": { "STR": 2, 'CON': 1 }, "Dragonborn": { "STR": 2, 'CHA': 1 }, "Tiefling": { "CHA": 2, 'INT': 1 }, } def __init__(self, cr=1, race=None): self.name = "NPC" self.damage = 0 self.cr = smart_clamp(cr, 1, 20) self.profession = self.professions() self.race = race if race else random_human() if percent_true( 50) else self.races() self.appearance = self.appearances() self.talent = self.talents() self.mannerism = self.mannerisms() self.trait = self.traits() self.bond = self.bonds() self.flaw = self.flaws() self.trinket = self.trinkets() self.save_dc = self.save_dc_list[self.cr + 3] self.xp = 0 self.stats = {} self.stat_mod = {} self.stat_mod_str = {} self._set_stats() self._set_racial_bonuses() self._set_stat_mods() self.background = random_background() self.saving_throws = self.set_saves() self.health = self._set_health() self.ac = 10 + self.stat_mod["DEX"] def _set_racial_bonuses(self): if self.race in self.racial_bonuses.keys(): for stat, val in self.racial_bonuses[self.race].items(): self.stats[stat] += val def _set_health(self): return smart_clamp(4 + self.stat_mod['CON'], 1, 20) def _set_stats(self): stats = ('STR', 'INT', 'DEX', 'WIS', 'CON', 'CHA') for ability in stats: self.stats[ability] = ability_dice(3) def _set_stat_mods(self): mod_by_stat = (-5, -5, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10) for ability in self.stats: self.stat_mod[ability] = mod_by_stat[smart_clamp( self.stats[ability], 1, 30)] if self.stat_mod[ability] > 0: self.stat_mod_str[ability] = f"+{self.stat_mod[ability]}" else: self.stat_mod_str[ability] = f"{self.stat_mod[ability]}" def set_saves(self): result = [] for key, val in self.stat_mod.items(): if val > 0: result.append(f"{key} +{val}") else: result.append(f"{key} {val}") return result @property def ability_scores(self): return "\n".join(f" {key} {val} ({self.stat_mod_str[key]})" for key, val in self.stats.items()) def __str__(self): self.output = (f"NPC: {self.profession}", f"Race: {self.race}", f"Appearance: {self.appearance}", f"Mannerism: {self.mannerism}", f"Ideal: {self.background.ideal}", f"Flaw: {self.background.flaw}", f"Hit Points: {self.health}", f"Armor Class: {self.ac}", "") return "\n".join(self.output)
def get_treasures(self): total = Loot() for room in self.threats: if room.treasure: total += room.treasure return total
class Trap: name = "Basic" type = "Minor" spot_dc = 10 save_dc = 11 damage = "1d10" rank_0_damage = "1d2" rank_1_damage = "2d4" rank_2_damage = "3d6" rank_3_damage = "4d8" rank_4_damage = "5d10" random_ability = TruffleShuffle(("CON", "STR", "DEX", "INT", "WIS")) random_trap = FlexCat( { 'bludgeoning': ("Falling Rocks", "Swinging Hammer", "Falling Chandelier"), 'falling': ("Deep Pit", "Spring Loaded Floor Tile", "Greased Slide", "False Floor"), 'piercing': ("Spiked Pit", "Guided Arrow", "Flying Broken Glass", "Falling Chandelier of Spikes"), 'slashing': ("Swinging Blade", "Guillotine", "Hooks and Chains", "Cloud of Daggers", "Exploding Mirror"), 'poison': ("Poison Dart", "Noxious Gas", "Pit Vipers", "Exploding Rotten Corpse"), 'acid': ("Acid Spay", "Acid Pool", "Acidic Bile", "Dripping Acidic Goo"), 'fire': ("Flame Jet", "Explosive Gas", "Lava Pit", "Delayed Fireball Blast", "Inferno"), 'lightning': ("Lightning Rod", "Tesla Coil", "Electric Trip Wire", "Lightning Bolt"), 'cold': ("Frozen Ground", "Cone of Cold", "Hail Storm", "Freeze the Flesh", "Ice Lance"), 'necrotic': ("Putrid Spores", "Cloud of Decay", "Energy Drain", "Beam of Entropy"), }, key_bias="truffle_shuffle", val_bias="truffle_shuffle") treasure = Loot() def __init__(self, cr, dam_type=None): self.cr = smart_clamp(cr, -3, 30) self.save_vs = self.random_ability() self.xp = self.set_xp_by_cr(self.cr) if dam_type: self.damage_type = dam_type else: self.damage_type = self.random_trap.random_cat() self.name = self.random_trap(self.damage_type) self.damage = self.set_damage(self.cr) def set_damage(self, cr): if cr < 1: damage = self.rank_0_damage elif cr < 5: damage = self.rank_1_damage elif cr < 11: damage = self.rank_2_damage elif cr < 17: damage = self.rank_3_damage else: damage = self.rank_4_damage return damage @staticmethod def set_xp_by_cr(cr): xp_lookup = (10, 25, 50, 100, 200, 450, 700, 1100, 1800, 2300, 2900, 3900, 5000, 5900, 7200, 8400, 10000, 11500, 13000, 15000, 18000, 20000, 22000, 25000, 33000, 41000, 50000, 62000, 155000, 155000, 155000, 155000, 155000, 155000, 155000, 155000, 155000, 155000, 155000, 155000) return xp_lookup[cr + 3] def __lt__(self, other): return self.cr <= other.cr def __str__(self): self.output = ( f"{self.type} Trap, CR {cr_str(self.cr)}: {self.name}", f"Spot & Disarm DC {self.spot_dc}", f"Save vs. {self.save_vs} DC {self.save_dc} for half damage.", f"Damage: {self.damage} {self.damage_type}", f"Disarm XP: {self.xp}", "") return "\n".join(self.output)