def mutate_magic_bits(self): self.reseed(salt="magicbit") for attr in self.magic_bits_attributes: value = getattr(self, attr) sort_func = lambda x: (bin(value ^ x).count('1'), random.random(), value) all_values = set([o.old_data[attr] for o in self.every] + [value]) all_values = sorted(all_values, key=sort_func) assert all_values[0] == value max_index = len(all_values) - 1 a = random.randint(0, random.randint(0, random.randint(0, max_index))) b = random.randint(0, max_index) if a > b: a, b = b, a index = int( round((b * self.random_degree) + (a * (1 - self.random_degree)))) new_value = all_values[index] assert (value | new_value) == 0xFFFF & (value | new_value) new_value = new_value ^ value for i in xrange(16): mask = (1 << i) if random.random() > max(self.random_degree, 0.5): if (new_value & mask): new_value = new_value ^ mask value = value ^ new_value setattr(self, attr, value)
def randomize_all(cls): super(MonsterMeatObject, cls).randomize_all() random_degree = MonsterMeatObject.random_degree families = list(range(12)) random.shuffle(families) meat_map = {} for i, f in enumerate(families): meat_classes = [0, 1, 2] if random.random() < (random_degree ** 0.5): random.shuffle(meat_classes) checks = ['a', 'b'] random.shuffle(checks) for check in checks: if check == 'a' and (meat_classes[0] == max(meat_classes) and random.random() > random_degree): meat_classes[:2] == list(reversed(meat_classes[:2])) if check == 'b' and (meat_classes[-1] == min(meat_classes) and random.random() > random_degree): meat_classes[-2:] == list(reversed(meat_classes[-2:])) assert len(set(meat_classes)) == 3 for j, c in enumerate(meat_classes): old_value = (f << 4) | c new_value = (i << 4) | j meat_map[old_value] = new_value assert sorted(meat_map.keys()) == sorted(meat_map.values()) for mmo in MonsterMeatObject.every: if (mmo.meat in meat_map and MonsterObject.get(mmo.index).is_monster): mmo.meat = meat_map[mmo.meat]
def shuffle_func(m): index = monsters.index(m) rand_index = random.random() * max_index ratio = (random.random() + random.random() + random.random()) / 3 new_index = (index * ratio) + (rand_index * (1 - ratio)) return (new_index, m.index)
def rank(self): hard_mode = "chaos" in get_activated_codes() if hard_mode: if self.xp == 0: return 20000 + random.random() else: return self.xp + random.random() else: return 0
def randomize_counts(self): if self.index <= 0xf: # boss formation enumerated_fcounts = list(enumerate(self.fcounts)) random.shuffle(enumerated_fcounts) aas = [self.enemy_indexes[2] == 0 and self.index > 0, False] for ((i, fcount), aa) in zip(enumerated_fcounts, aas): counts = fcount.old_data['counts'] candidates = [f for f in FormationCountObject.every if f.validate_boss(counts, allow_add=aa)] if not candidates: chosen = fcount else: chosen = random.choice(candidates) self.counts[i] = chosen.index if aa: old_monsters = [self.monsters[j] for (j, c) in enumerate(counts) if c > 0] avg_rank = (sum([m.rank for m in old_monsters]) / len(old_monsters)) avg_hp = (sum([m.hp for m in old_monsters]) / len(old_monsters)) candidates = [m for m in MonsterObject.ranked if m.intershuffle_valid and m.rank < avg_rank and m.hp < avg_hp] max_index = len(candidates) - 1 randval = random.random() randomness = (1-self.random_degree) if randomness > 0: randval = randval ** (1 / randomness) else: randval = 0 index = int(round((1-randval) * max_index)) chosen = candidates[index] self.enemy_indexes[2] = chosen.index return for (i, fcount) in enumerate(self.fcounts): counts = fcount.old_data['counts'] candidates = sorted( FormationCountObject.every, key=lambda fc: (fc.get_distance(counts), fc.signature)) max_index = len(candidates) - 1 randval = random.random() if self.random_degree > 0: randval = randval ** (1 / self.random_degree) else: randval = 0 assert 0 <= randval <= 1 index = int(round(randval * max_index)) chosen = candidates[index] self.counts[i] = chosen.index assert self.fcounts[i] is chosen
def new_family_attributes(self): self.family_attributes, self.extended_family_attributes if not hasattr(MonsterObject, '_newfamattr'): MonsterObject._newfamattr = {} if self.family_key in MonsterObject._newfamattr: return MonsterObject._newfamattr[self.family_key] if self.family_key >= 0xc0: MonsterObject._newfamattr[self.family_key] = \ MonsterObject._famattr[self.family_key] return self.new_family_attributes num_attributes = len(self.family_attributes) min_attributes = 8 max_attributes = max( len(v) for (k, v) in MonsterObject._famattr.items() if (k >> 4) <= 0xb) num_attributes = mutate_normal( num_attributes, minimum=min_attributes, maximum=max_attributes, random_degree=self.random_degree, wide=True) new_attributes = [] while len(new_attributes) < num_attributes: old_attribute = random.choice(sorted(self.family_attributes)) if random.random() > self.random_degree: # family attributes candidates = sorted(self.family_attributes) elif random.random() > self.random_degree: # extended family attributes candidates = sorted(self.extended_family_attributes) else: # any attributes candidates = {a for i in MonsterObject._exfamattr for a in MonsterObject._exfamattr[i] if i < 0xc} candidates = sorted(candidates) if len(new_attributes) == 0: candidates = [c for c in candidates if c.get_bit('use_battle')] new_attribute = old_attribute.get_similar( candidates=candidates, random_degree=self.random_degree, override_outsider=True) if new_attribute not in new_attributes: new_attributes.append(new_attribute) assert any([a.get_bit('use_battle') for a in new_attributes]) MonsterObject._newfamattr[self.family_key] = sorted(new_attributes) return self.new_family_attributes
def randomize(self): num_items = len(self.items) num_items = mutate_normal(num_items, minimum=1, maximum=8, random_degree=self.random_degree, wide=True) old_items = list(self.items) new_items = [] for _ in range(500): if len(new_items) == num_items: break buyable_check = not (random.random() < self.random_degree) chosen_class = random.choice(old_items).shop_item_class chosen_price = random.choice(old_items) candidates = [a for a in AttributeObject.every if a.index <= 0x7f and a.rank >= 0 and a.shop_item_class == chosen_class and a.is_buyable >= buyable_check] candidates = [c for c in candidates if c not in new_items] if not candidates: continue chosen = chosen_price.get_similar( candidates=candidates, random_degree=self.random_degree, override_outsider=True) new_items.append(chosen) else: raise Exception('Unable to populate shop.') for i in new_items: if not i.is_buyable: ItemPriceObject.get(i.index).price = 60000 self.item_indexes = [i.index for i in new_items]
def intershuffle(cls): for m in MonsterObject.every: m.name monsters = [m for m in MonsterObject.ranked if m.intershuffle_valid] max_index = len(monsters) - 1 hard_mode = "chaos" in get_activated_codes() if hard_mode: def shuffle_func(m): index = monsters.index(m) rand_index = random.random() * max_index ratio = (random.random() + random.random() + random.random()) / 3 new_index = (index * ratio) + (rand_index * (1 - ratio)) return (new_index, m.index) else: shuffle_func = lambda m: (random.random(), m.index) for attrs in ["common_drop", "rare_drop", ("soul_type", "soul")]: if "bal" in get_activated_codes() and attrs in [ "common_drop", "rare_drop" ]: continue if isinstance(attrs, basestring): attrs = [attrs] shuffled = sorted(monsters, key=shuffle_func) for attr in attrs: values = [getattr(m, attr) for m in shuffled] assert len(values) == len(monsters) for m, value in zip(monsters, values): setattr(m, attr, value)
def ff2_get_ranked_items(candidates=None): if candidates is None: candidates = range(0x10, 196) banned = range(0x10) + [47, 48] candidates = [c for c in candidates if c not in banned] candidates = sorted(candidates, key=lambda c: (ff2_get_price(c), random.random(), c)) return candidates
def bit_random_add(self, attr, rate=0.5): size = self.get_bit_width(attr) while random.random() <= rate: mask = 1 << random.randint(0, size-1) value = getattr(self, attr) if value & mask: break setattr(self, attr, value | mask)
def bit_random_remove(self, attr, rate=0.5): size = self.get_bit_width(attr) while random.random() <= rate: mask = 1 << random.randint(0, size-1) mask = mask ^ ((2**size)-1) value = getattr(self, attr) if value == value & mask: break setattr(self, attr, value & mask)
def mutate(self): if self.equipment == 0xFFFFFFFF: return class_frequency = bin(self.equipment).count('1') / 32.0 for i in xrange(32): mask = 1 << i frequency = min(self.frequency(i), class_frequency, 1/3.0) frequency = max(frequency, 1/32.0) if random.random() < frequency: self.equipment ^= mask
def randomize(self): if self.index == 6: # berserker passive candidates = [ jao.ability for jao in JobAbilityObject.groups[self.index] if jao.ability > 0x4D ] if candidates: self.commands[1] = random.choice(candidates) return if self.index > 20: return old_commands = list(self.commands) candidates = [ jao.ability for jao in JobAbilityObject.groups[self.index] if jao.ability <= 0x4D ] if self.index == 20: candidates += [5, 2] redundant_groups = [ range(0x2C, 0x32), range(0x32, 0x38), range(0x38, 0x3E), range(0x3E, 0x44), ] for i, ability in enumerate(self.commands): if not candidates: break if "fight" in get_activated_codes() and ability == 5: pass elif ability > 0 and random.random() <= (self.random_degree**0.75): new_command = random.choice(candidates) if new_command in self.commands: continue self.commands[i] = new_command for rg in redundant_groups: if len(set(self.commands) & set(rg)) >= 2: self.commands[i] = ability break else: candidates.remove(new_command) if ability in candidates: candidates.remove(ability) while not set(self.commands) & set([5, 0x2b, 2]): i, c = random.choice(sorted(enumerate(old_commands))) if c in [5, 0x2b, 2]: self.commands[i] = c for rg in redundant_groups: if len(set(self.commands) & set(rg)) >= 2: assert False
def mutate(self): for attr in ["steal_common", "steal_rare", "drop_common", "drop_rare"]: value = getattr(self, attr) if value > 0 and PriceObject.get(value).rank >= 0: candidates = [ p for p in PriceObject.every if p.is_valid_treasure ] chosen = PriceObject.get(value).get_similar(candidates) setattr(self, attr, chosen.index) if random.random() < self.random_degree: self.steal_rare, self.drop_rare = (self.drop_rare, self.steal_rare)
def ranked_requirements(self): unreachables = self.unreachable_locations counts = {} counts = [ (len([u for u in unreachables if req in self.get_requirements(u)]), random.random(), req) for req in self.requirements ] return [ req for (count, _, req) in sorted(counts) if count > 0 and req not in self.assigned_items ]
def randomize(self): if not self.intershuffle_valid: return other = self.get_similar() power = mutate_normal(other.power, minimum=0, maximum=0xf, wide=True) if random.random() < (self.random_degree / 2): power = int(round(power * 2 / 3)) count_statuses = bin(self.boost_statuses) while bin(self.boost_statuses).count('1') == count_statuses: new_status = random.choice([0x10, 0x40, 0x80]) self.misc_value |= new_status
def mutate_stat_curve(cls, class_index, attr): lus = [ lu for lu in LevelUpObject.every if lu.class_index == class_index ] assert len(lus) == 99 lus = lus[:98] assert len(lus) == 98 lus[0].reseed(salt="fullmut" + attr) bits = [lu.get_bit(attr) for lu in lus] value = len([b for b in bits if b]) base_ratio = value / float(len(lus)) max_ratio = max([ cls.get_class_stat_score(i, attr) / float(len(lus)) for i in xrange(6) ]) assert max_ratio >= base_ratio base_ratio = mutate_normal(base_ratio, 0, max_ratio, wide=False, random_degree=LevelUpObject.random_degree, return_float=True) remaining = list(lus) while remaining: ratio = mutate_normal(base_ratio, 0, max_ratio, wide=False, random_degree=LevelUpObject.random_degree, return_float=True) max_index = len(remaining) - 1 divider = random.randint(0, max_index) aa = remaining[:divider] bb = remaining[divider:] if len(aa) > len(bb): aa, bb = bb, aa elif len(aa) == len(bb) and random.choice([True, False]): aa, bb = bb, aa if random.choice([True, True, False]): to_set, remaining = aa, bb else: to_set, remaining = bb, aa assert len(to_set + remaining) == max_index + 1 for lu in to_set: value = (random.random() < ratio) lu.set_bit(attr, value)
def ranked_requirements(self): requirements = set([ r for label in self.assign_conditions for r in self.get_simplified_requirements(label) ]) unreachables = self.unreachable_locations counts = {} counts = [(len([ u for u in unreachables if req in self.get_simplified_requirements(u) ]), random.random(), req) for req in requirements] return [ req for (count, _, req) in sorted(counts) if count > 0 and req not in self.assigned_items ]
def mutate(self): super(MonsterObject, self).mutate() if 1 <= self.level <= 99: new_level = mutate_normal(self.level, minimum=1, maximum=99, random_degree=self.random_degree) old_divisibility = divisibility_rank(self.level) new_divisibility = divisibility_rank(new_level) if new_divisibility < old_divisibility: if not self.is_boss: self.level = new_level else: difference = float(new_level) / self.level if difference > 1: difference = 1 / difference difference = (difference * (1 - self.random_degree)) + ( self.random_degree**2) if random.random() < difference: self.level = new_level elif (not self.is_boss and random.random() < (self.random_degree**0.5)): self.level = new_level
def sort_by_item_usage(self, locations): fail_counter = defaultdict(int) for item in self.assigned_items: remember_location = self.get_assigned_location(item) del (self.assignments[remember_location]) assignable_locations = self.assignable_locations for l in locations: if l not in assignable_locations: fail_counter[l] += 1 self.assignments[remember_location] = item locations = sorted( sorted(locations), key=lambda l: (fail_counter[l], self.get_location_rank(l), random.random())) return locations
def assign_item(self, item, linearity=None): self._assignable_locations = None assignable_locations = self.get_valid_locations(item) if not assignable_locations: self.force_custom() raise ItemRouterException("No assignable locations: %s." % item) candidates = None if self.old_goal_requirements: candidates = ( self.requirements_locations[self.old_goal_requirements] & assignable_locations) if candidates: candidates = self.try_filter_no_custom_locations(candidates) chosen = random.choice(sorted(candidates)) self.old_goal_requirements = None else: if linearity is None: linearity = self.linearity new_locations = self.get_item_unlocked_locations(item) if not new_locations: linearity = linearity**2 ranker = lambda c: (self.get_location_rank(c), self.get_complexity_rank(c), self.rankrand(c)) candidates = sorted(assignable_locations, key=ranker) candidates = self.try_filter_no_custom_locations(candidates) if self.goal_requirements is not None: sorted_goals = self.get_bottleneck_goals() goal = sorted_goals[0] if goal != item: temp = [ c for c in candidates if c not in self.get_valid_locations(item) ] if temp: candidates = temp max_index = len(candidates) - 1 weighted_value = (random.random()**(1 - linearity)) index = int(round(max_index * weighted_value)) chosen = candidates[index] rank = [ i for i in self.location_ranks if chosen in self.location_ranks[i] ] assert len(rank) == 1 self.assign_item_location(item, chosen)
def intershuffle(cls): candidates = sorted( [jco for jco in JobCrystalObject.every if jco.intershuffle_valid], key=lambda jco: (jco.ability_ap_rank, jco.signature)) shuffled = [] while candidates: max_index = len(candidates) - 1 index = random.randint(0, max_index) degree = JobCrystalObject.random_degree**0.25 if degree <= 0.5: degree = degree * 2 a, b = 0, index else: degree = (degree - 0.5) * 2 a, b = index, max_index index = int(round((a * (1 - degree)) + (b * degree))) index = random.randint(0, index) chosen = candidates[index] shuffled.append(chosen) candidates.remove(chosen) candidates = sorted(shuffled, key=lambda jco: (jco.rank, jco.signature)) if 'GBA' not in get_global_label(): assert len(candidates) == len(shuffled) == 21 else: assert len(candidates) == len(shuffled) == 25 for c, s in zip(candidates, shuffled): c.crystal_index = s.old_data["crystal_index"] freelancer = [jco for jco in candidates if jco.is_freelancer][0] fight_crystals = [jco for jco in shuffled if jco.has_fight_command] if freelancer not in fight_crystals: assert not freelancer.has_fight_command chosen = fight_crystals[0] freelancer.crystal_index, chosen.crystal_index = ( chosen.crystal_index, freelancer.crystal_index) assert freelancer.has_fight_command if 'r' in get_flags(): chance = int(RemoveJobs.random_degree * 100) print('Removing jobs ({}% chance)...'.format(chance)) for c in candidates: if random.random() < RemoveJobs.random_degree: c.crystal_index = freelancer.crystal_index
def mutate(self): if not self.mutate_valid: return chance = random.random() price = self.rank if chance <= 0.85: if chance <= 0.70: # item self.treasure_type = 0x40 else: # magic self.treasure_type = 0x20 self.value = get_item_similar_price(price, magic=self.is_magic) else: # gold price = mutate_normal(price, minimum=1, maximum=65000) exponent = 0 while price >= 100: price /= 10 exponent += 1 self.treasure_type = exponent self.value = price
def mutate(self): oldstats = {} for key in self.mutate_attributes: oldstats[key] = getattr(self, key) super(MonsterObject, self).mutate() if self.is_boss: for (attr, oldval) in oldstats.items(): if getattr(self, attr) < oldval: setattr(self, attr, oldval) if 1 <= self.level <= 99: new_level = mutate_normal(self.level, minimum=1, maximum=99) old_divisibility = divisibility_rank(self.level) new_divisibility = divisibility_rank(new_level) if new_divisibility < old_divisibility: if not self.is_boss: self.level = new_level else: difference = float(new_level) / self.level if random.random() < difference: self.level = new_level elif not self.is_boss and random.choice([True, False]): self.level = new_level oldimmunities = self.status_immunities for attr in ["elemental_immunities", "absorptions", "weaknesses"]: if not self.is_boss: self.bit_shuffle(attr) else: self.bit_random_add(attr) if not self.is_boss: self.bit_shuffle("cant_evade") self.bit_random_add("cant_evade") self.bit_random_remove("cant_evade") oldstatus = self.status self.bit_random_add("status_immunities") self.bit_random_add("status", rate=0.1) newstatus = self.status newstatus = newstatus & (newstatus ^ oldimmunities) self.status = (oldstatus & oldimmunities) | newstatus self.bit_random_add("command_immunity")
def try_unlock_locations(self, reqs=None): if reqs is None: reqs = self.unassigned_items candidates = [ r for r in reqs if self.get_valid_locations(r) and self.get_item_unlocked_locations(r) ] if not candidates: return False chosen = None if self.goal_requirements: if set(candidates) & set(self.goal_requirements): candidates = [ c for c in candidates if c in self.goal_requirements ] else: sorted_goals = self.get_bottleneck_goals() max_index = len(sorted_goals) - 1 index = 0 goal = sorted_goals[index] candidates = self.try_filter_no_custom_items(candidates) temp = [ c for c in candidates if self.get_item_unlocked_locations(c) & self.get_valid_locations(goal, include_unreachable=True) ] if temp: max_index = len(candidates) - 1 index = int(round(max_index * (random.random()**2))) chosen = candidates[index] if chosen is None: candidates = self.try_filter_no_custom_items(candidates) chosen = random.choice(candidates) self.assign_item(chosen) return True
def randomize_all(cls): f = open(get_outfile(), "r+b") f.seek(addresses.hammer3) num_items = ord(f.read(1)) indexes = map(ord, f.read(num_items)) f.close() sios = [ShopIndexObject.get(i) for i in indexes] hard_mode = "chaos" in get_activated_codes() total_new_items = [] for item_type in [2, 3, 4]: subsios = [sio for sio in sios if sio.item_type == item_type] new_items = [] candidates = [ i for i in ItemObject.every if i.item_type == item_type and i.price > 0 ] candidates = sorted(candidates, key=lambda c: (c.price, random.random())) max_index = len(candidates) - 1 while len(new_items) < len(subsios): if hard_mode: index = random.randint(0, random.randint(0, max_index)) else: index = random.randint(0, max_index) chosen = candidates[index] if ("fam" in get_activated_codes() and item_type == 2 and chosen.index in HP_HEALING_ITEMS): continue restricted_items = None if "gun" in get_activated_codes() and item_type == 3: restricted_items = [WeaponObject.get(i) for i in GUNS] if "fist" in get_activated_codes() and item_type == 3: restricted_items = [WeaponObject.get(i) for i in FISTS] if "ass" in get_activated_codes() and item_type == 3: restricted_items = [WeaponObject.get(i) for i in KNIVES] if restricted_items is not None: temp = [i for i in restricted_items if i not in new_items] if temp: restricted_items = temp chosen = random.choice(restricted_items) if chosen in new_items and restricted_items is None: continue new_items.append(chosen) new_items = sorted(new_items, key=lambda ni: ni.index) total_new_items.extend(new_items) sios = [ShopIndexObject.get(i) for i in xrange(len(total_new_items))] for sio, ni in zip(sios, total_new_items): sio.item_type = ni.item_type sio.item_index = ni.index f = open(get_outfile(), "r+b") previous = list(sios) for address in ["hammer3", "hammer2", "hammer1"]: f.seek(getattr(addresses, address)) num_items = ord(f.read(1)) f.seek(getattr(addresses, address) + 1) chosen_sios = random.sample(previous, num_items) chosen_sios = sorted(chosen_sios, key=lambda sio: sio.index) for sio in chosen_sios: f.write(chr(sio.index)) sio.shop_rank = int(address[-1]) sio.inserted_item = False previous = chosen_sios f.close()
def ranked(self): if self is ItemObject: return sorted(ItemObject.every, key=lambda i: (i.rank, random.random())) return super(ItemObject, self).ranked
def rank(self): if hasattr(self, '_rank'): return self._rank by_price = sorted(self.every, key=lambda p: (p.price, p.signature)) by_price = [p for p in by_price if p.price > 0 and p.is_buyable] by_drop_rank = sorted( self.every, key=lambda p: (p.drop_rank, p.signature)) by_drop_rank = [p for p in by_drop_rank if p.drop_rank > 0] by_shop_day = sorted( self.every, key=lambda p: (p.old_shop_availability, p.signature)) by_shop_day = [p for p in by_shop_day if p.is_quest_buyable] for p in self.every: ranks = [] if p in by_price and p.is_buyable: ranks.append(by_price.index(p) / float(len(by_price))-1) if p in by_drop_rank: ranks.append( by_drop_rank.index(p) / float(len(by_drop_rank))-1) if ranks: p._rank = min(ranks) for p in self.every: if hasattr(p, '_rank'): for ev in p.evolves: if ev: p2 = PinObject.get(ev) if not hasattr(p2, '_rank') or p2._rank < p._rank: value = gen_random_normal() ** 4 new_rank = (value * 1.0) + ((1-value) * p._rank) p2._rank = new_rank by_price = sorted(self.every, key=lambda p: (p.price, p.signature)) by_price = [p for p in by_price if p.price > 0] for p in self.every: if hasattr(p, '_rank'): continue if p in by_shop_day: p._rank = by_shop_day.index(p) / float(len(by_shop_day)-1) elif p in by_price: p._rank = by_price.index(p) / float(len(by_price)-1) else: p._rank = random.random() sorted_class = sorted( self.every, key=lambda p: (-p.pin_class, p._rank, p.signature)) sorted_noclass = sorted( self.every, key=lambda p: (p._rank, p.signature)) for p in self.every: if p.pin_class >= 5: rank = sorted_noclass.index(p) else: value = gen_random_normal() rank = ((sorted_class.index(p) * value) + ((1-value) * sorted_noclass.index(p))) p._rank = mutate_normal( rank, minimum=0, maximum=len(self.every), wide=True, random_degree=self.random_degree**2, return_float=True) sorted_noclass = sorted( self.every, key=lambda p: (p._rank, p.signature)) for p in self.every: p._rank = sorted_noclass.index(p) / float(len(sorted_noclass)-1) return self.rank
def sorted_items_and_equipment(self): return sorted(self.all_items_and_equipment, key=lambda i: (i.rank, random.random(), i.pointer))
random.shuffle(order) for o in order: if o == 'a': temp = [ s for s in souls if ir.get_item_rank(soulstrs[s]) is not None ] if temp: souls = temp if o == 'b': temp = [s for s in souls if s not in banned] if temp: souls = temp souls = sorted(sorted(souls), key=lambda s: (ir.get_item_rank(soulstrs[s]), random.random())) soul_type, soul = souls.pop(0) soul_type -= 5 if replaceable: replacement = replaceable.pop(0) replacement.soul_type = boss.soul_type replacement.soul = boss.soul else: erased_souls.add((boss.soul_type + 5, boss.soul)) boss.soul_type = soul_type boss.soul = soul assert 0 <= boss.soul_type <= 3 remaining_treasures = [ t for t in TreasureObject.every if t not in done_treasures ]