def get_special_ap(self): levels = [e.stats['level'] for e in self.present_enemies if e] ap = int(sum(levels) // len(levels)) low = ap // 2 ap = low + random.randint(0, low) + random.randint(0, low) ap = random.randint(0, ap) self.ap = min(100, max(ap, 0))
def mutate(self, always_break=False): global changed_commands self.mutate_stats() self.mutate_price() broken, learned = False, False if always_break: self.mutate_break_effect(always_break=True) broken = True for command, itemids in break_unused_dict.items(): if command in changed_commands and self.itemid in itemids: self.mutate_break_effect() broken = True if self.itemid == 0xE6: self.mutate_learning() learned = True while random.randint(1, 5) == 5: x = random.randint(0, 99) if x < 10: self.mutate_special_action() if 10 <= x < 20 and not learned: self.mutate_learning() if 20 <= x < 50 and not broken: self.mutate_break_effect() broken = True if 50 <= x < 80: self.mutate_elements() if x >= 80: self.mutate_feature() if not self.heavy and random.randint(1, 20) == 20: self.heavy = True
def mutate_feature(self, feature=None): if self.is_consumable or self.is_tool: return if feature is None: feature = random.choice(list(STATPROTECT.keys())) nochange = STATPROTECT[feature] if feature == 'special2': # Allow rare merit award bit on relics if self.is_relic: if random.randint(1, 10) == 10: nochange &= ~0x20 # Reduce chance of Genji Glove bit on non-relics elif random.randint(1, 4) != 4: nochange |= 0x10 self.features[feature] = bit_mutate(self.features[feature], op="on", nochange=nochange) new_features = self.get_feature(feature, self.features[feature], nochange) if new_features != "": if "Special Feature" in self.mutation_log.keys(): for new_feature in new_features.split(", "): if new_feature not in self.mutation_log["Special Feature"]: self.mutation_log.update({"Special Feature": self.mutation_log["Special Feature"] + ", " + new_feature}) else: self.mutation_log["Special Feature"] = new_features self.mutate_name()
def get_special_mp(self): levels = [e.stats['level'] for e in self.present_enemies if e] mp = int(sum(levels) // len(levels)) low = mp // 2 mp = low + random.randint(0, low) + random.randint(0, low) mp = random.randint(0, mp) self.mp = min(100, max(mp, 0))
def elemshuffle(elements): elemcount = bin(elements).count('1') while random.randint(1, 5) == 5: elemcount += random.choice([-1, 1]) elemcount = max(0, min(elemcount, 8)) elements = 0 while elemcount > 0: elements = elements | (1 << random.randint(0, 7)) elemcount += -1 return elements
def mutate_break_effect(self, always_break=False, wild_breaks=False, no_breaks=False, unbreakable=False): global effects_used if self.is_consumable: return if no_breaks: self.itemtype &= ~0x20 return if unbreakable: self.features['otherproperties'] &= ~0x08 return if always_break: effects_used = [] success = False max_spellid = 0xFE if wild_breaks else 0x50 for _ in range(100): spell, _ = self.pick_a_spell(custom=lambda x: x.spellid <= max_spellid) if spell.spellid not in effects_used: effects_used.append(spell.spellid) success = True break if not success: return # swdtechs, blitzes, superball, and slots don't seem to work # correctly with procs, but they work with breaks. # (Mostly they just play the wrong animation, but a couple # softlock.) no_proc_ids = list(range(0x55, 0x66)) + list(range(0x7D, 0x82)) self.features['breakeffect'] = spell.spellid if not self.is_weapon or random.randint(1, 2) == 2 or always_break or spell.spellid in no_proc_ids: # always make armors usable in battle; weapons, only sometimes self.itemtype = self.itemtype | 0x20 # flag to break when used as an item if random.randint(1, 20) == 20: self.features['otherproperties'] &= 0xF7 else: self.features['otherproperties'] |= 0x08 # flag to set chance to proc a spell if self.is_weapon and spell.spellid not in no_proc_ids and ( not self.itemtype & 0x20 or random.randint(1, 2) == 2): self.features['otherproperties'] |= 0x04 self.mutation_log["Proc"] = get_spell(self.features['breakeffect']).name else: self.features['otherproperties'] &= 0xFB self.features['targeting'] = spell.targeting & 0xef self.mutate_name()
def mutate_power_hitmdef(): diff = min(self.features['power'], 0xFF-self.features['power']) diff = diff / 3 self.features['power'] = self.features['power'] - diff self.features['power'] = self.features['power'] + random.randint(0, diff) + random.randint(0, diff) self.features['power'] = int(min(0xFF, max(0, self.features['power']))) diff = min(self.features['hitmdef'], 0xFF-self.features['hitmdef']) diff = diff / 3 self.features['hitmdef'] = self.features['hitmdef'] - diff self.features['hitmdef'] = self.features['hitmdef'] + random.randint(0, diff) + random.randint(0, diff) self.features['hitmdef'] = int(min(0xFF, max(0, self.features['hitmdef'])))
def generate_name(size=None, maxsize=10): if not size: size = random.randint(1, 5) + random.randint(1, 5) if size < 4: size += random.randint(0, 5) def has_vowel(text): for c in text: if c.lower() in "aeiouy": return True return False while True: starts = sorted([s for s in generator if s[0].isupper()]) name = random.choice(starts) name = name[:size] while len(name) < size: key = name[-lookback:] if key not in generator and size - len(name) < len(key): name = random.choice(starts) continue if key not in generator or (random.randint(1, 15) == 15 and has_vowel(name[-2:])): if len(name) <= size - lookback: if len(name) + len(key) < maxsize: name += " " name += random.choice(starts) continue else: name = random.choice(starts) continue c = random.choice(generator[key]) name = name + c for ename in enemynames: if name == ename: name = "" break if name: for ename in enemynames: if len(name) > (lookback + 1): length = min(len(name), len(ename)) if name[:length] == ename[:length]: name = "" break if len(name) >= size: enemynames.append(name) return name
def mutate_special_action(self): if self.features['specialaction'] & 0xf0 != 0 or not self.is_weapon: return new_action = random.randint(1, 0xf) if new_action == 0xA: # make random valiant knife effect rare new_action = random.randint(1, 0xf) if new_action == 9: # no random dice effect return self.features['specialaction'] = (new_action << 4) | ( self.features['specialaction'] & 0x0f)
def enrank(r): mr = min(maxrank, 0xFF) r = max(0, min(r, mr)) if options_.is_code_active('racecave'): half = r // 2 quarter = half // 2 r = (half + random.randint(0, quarter) + random.randint(0, quarter)) if r <= 0: return 0 elif r >= mr: return 1.0 ratio = float(r) / mr return ratio
def unlock_chests(self, low, high, monster=False, guarantee_miab_treasure=False, enemy_limit=None): if len(self.chests) == 1: low = (low + high) / 2 dist = (high - low) / 2 for c in self.chests: c.set_content_type(0x80) c.contents = None value = low + random.randint(0, dist) + random.randint(0, dist) c.value = value c.mutate_contents(monster=monster, enemy_limit=enemy_limit, guarantee_miab_treasure=guarantee_miab_treasure, uniqueness=len(self.chests) != 1) if random.randint(1, 5) >= 4: c.set_new_id()
def mutate_power_hitmdef(): diff = min(self.features['power'], 0xFF-self.features['power']) diff = diff / 3 self.features['power'] = self.features['power'] - diff self.features['power'] = self.features['power'] + random.randint(0, diff) + random.randint(0, diff) self.features['power'] = int(min(0xFF, max(0, self.features['power']))) if "Dice" in self.name: return diff = min(self.features['hitmdef'], 0xFF-self.features['hitmdef']) diff = diff / 3 self.features['hitmdef'] = self.features['hitmdef'] - diff self.features['hitmdef'] = self.features['hitmdef'] + random.randint(0, diff) + random.randint(0, diff) self.features['hitmdef'] = int(min(0xFF, max(0, self.features['hitmdef'])))
def unlock_chests(self, low, high, monster=False, guarantee_miab_treasure=False, enemy_limit=None, crazy_prices=False, uncapped_monsters=False): if len(self.chests) == 1: low = (low + high) // 2 dist = (high - low) // 2 for c in self.chests: c.set_content_type(0x80) c.contents = None value = low + random.randint(0, dist) + random.randint(0, dist) c.value = value c.mutate_contents(monster=monster, enemy_limit=enemy_limit, guarantee_miab_treasure=guarantee_miab_treasure, uniqueness=len(self.chests) != 1, crazy_prices=crazy_prices, uncapped_monsters=uncapped_monsters) if random.randint(1, 5) >= 4: c.set_new_id()
def random_unused_code(self): candidates = [ c for c in NORMAL_CODES if c not in self.active_codes and c.name != "repairpalette" ] i = random.randint(1, 7) if i <= 2: secret_codes = [ c for c in NORMAL_CODES if c.is_cyphered and not self.is_code_active(c) ] if secret_codes: candidates = secret_codes elif i <= 4: new_codes = MAKEOVER_MODIFIER_CODES + [ c for c in NORMAL_CODES if c.name in ["alasdraco"] ] new_codes = [c for c in new_codes if not self.is_code_active(c)] if new_codes: candidates = new_codes if not candidates: candidates = NORMAL_CODES + MAKEOVER_MODIFIER_CODES selected = random.choice(candidates) if selected.is_cyphered: f = FourSquare(selected.key1, selected.key2) return f.decypher(selected.name) return selected.name
def mutate_chests(self, guideline=None): for c in self.chests: if self.fset.setid == 0: c.set_rank(None) else: c.set_rank(self.rank()) if guideline is None: if len(self.chests) > 0: values = [ c.get_current_value(guideline=100) for c in self.chests ] average_value = (sum(values) * 100) / len(values) guideline = average_value else: guideline = 100 if self.locid == 0xb4: return elif self.locid == 0x147: guideline = random.randint(1820, 4500) random.shuffle(self.chests) for c in self.chests: if self.locid in range(0x139, 0x13d) and c.empty: c.mutate_contents(monster=True, guideline=guideline) continue elif self.locid == 0x147: pass c.mutate_contents(guideline=guideline) if guideline is None and hasattr(c, "value") and c.value: guideline = value
def allocate_espers(ancient_cave, espers, characters, fout): char_ids = list(range(12)) + [13] # everyone but Gogo characters = [c for c in characters if c.id in char_ids] chars_for_esper = [] max_rank = max(espers, key=lambda e: e.rank).rank for e in espers: num_users = 1 if e.id not in [15, 16] and random.randint(1,25) >= 25 - max_rank + e.rank: num_users += 1 while num_users < 15 and random.choice([True] + [False] * (e.rank + 2)): num_users += 1 users = random.sample(characters, num_users) chars_for_esper.append([c.id for c in users]) if not ancient_cave: chars_for_esper[12] = chars_for_esper[11] # make Odin and Raiden equippable by the same person/people char_mask_for_esper = [ reduce( lambda x, y: x | y, [1 << char_id for char_id in chars_for_esper[e.id]] ) for e in espers ] for e in espers: e.chars = ", ".join([c.newname for c in characters if c.id in chars_for_esper[e.id]]) # do substitution esper_allocator_sub = Substitution() esper_allocator_sub.set_location(0x31B61) esper_allocator_sub.bytestring = [0x20,0x00,0xF8] esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x35524) esper_allocator_sub.bytestring = [0x20,0x07,0xF8] esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x358E1) esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x359B1) esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x35593) esper_allocator_sub.bytestring = [0xA9,0x2C] esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x355B2) esper_allocator_sub.bytestring = [0x20,0x2E,0xF8] esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x358E8) esper_allocator_sub.bytestring = [0xC9,0x20,0xF0,0x16] esper_allocator_sub.write(fout) esper_allocator_sub.set_location(0x3F800) esper_allocator_sub.bytestring = [0xAA, 0xB5, 0x69, 0x8D, 0xF8, 0x1C, 0x60, 0xDA, 0x08, 0x85, 0xE0, 0x0A, 0xAA, 0xE2, 0x10, 0xDA, 0xAD, 0xF8, 0x1C, 0x0A, 0xAA, 0xC2, 0x20, 0xBF, 0x67, 0x9C, 0xC3, 0xFA, 0x3F, 0x58, 0xF8, 0xC3, 0xF0, 0x05, 0x28, 0xFA, 0x4C, 0x76, 0x55, 0x28, 0xFA, 0xA9, 0x28, 0x4C, 0x95, 0x55, 0xBD, 0x02, 0x16, 0xC9, 0x80, 0xB0, 0x0F, 0xFA, 0xA6, 0x00, 0xBF, 0x4B, 0xF8, 0xC3, 0xF0, 0x07, 0x8D, 0x80, 0x21, 0xE8, 0x80, 0xF4, 0x60, 0x9C, 0x80, 0x21, 0x4C, 0xD9, 0x7F, 0x82, 0x9A, 0xA7, 0xC3, 0xAD, 0xFF, 0x9E, 0xAA, 0xAE, 0xA2, 0xA9, 0xBE, 0x00] + [i for sublist in map(int2bytes, char_mask_for_esper) for i in sublist] esper_allocator_sub.write(fout)
def mutate_chests(self, guideline=None): for c in self.chests: if self.fset.setid == 0: c.set_rank(None) else: c.set_rank(self.rank()) if guideline is None: if len(self.chests) > 0: values = [c.get_current_value(guideline=100) for c in self.chests] average_value = (sum(values)*100) / len(values) guideline = average_value else: guideline = 100 if self.locid == 0xb4: return elif self.locid == 0x147: guideline = random.randint(1820, 4500) random.shuffle(self.chests) for c in self.chests: if self.locid in range(0x139, 0x13d) and c.empty: c.mutate_contents(monster=True, guideline=guideline) continue elif self.locid == 0x147: pass c.mutate_contents(guideline=guideline) if guideline is None and hasattr(c, "value") and c.value: guideline = value
def randomize_fanatics(unused_locids): stairs = [get_location(i) for i in [363, 359, 360, 361]] pitstops = [get_location(i) for i in [365, 367, 368, 369]] num_new_levels = random.randint(0, 1) + random.randint(1, 2) unused_locations = [get_location(l) for l in unused_locids] fsets = get_new_fsets("fanatics", 10, supplement=False) for _ in xrange(num_new_levels): stair = unused_locations.pop() stop = unused_locations.pop() stair.copy(random.choice(stairs[1:-1])) stop.copy(random.choice(pitstops[1:])) add_location_map("Fanatics Tower", stair.locid) add_location_map("Fanatics Tower", stop.locid) index = random.randint(1, len(stairs)-1) stairs.insert(index, stair) pitstops.insert(index, stop) chest = stop.chests[0] chest.set_new_id() entrance = stop.entrances[0] entrance.dest = (entrance.dest & 0xFE00) | (stair.locid & 0x1FF) entrance = sorted(stair.entrances, key=lambda e: e.y)[1] entrance.dest = (entrance.dest & 0xFE00) | (stop.locid & 0x1FF) stair.setid = random.choice(fsets).setid stop.setid = random.choice(fsets).setid for a, b in zip(stairs, stairs[1:]): lower = sorted(a.entrances, key=lambda e: e.y)[0] upper = sorted(b.entrances, key=lambda e: e.y)[-1] lower.dest = (lower.dest & 0xFE00) | (b.locid & 0x1FF) upper.dest = (upper.dest & 0xFE00) | (a.locid & 0x1FF) for stop in pitstops: if random.choice([True, False]): continue index = pitstops.index(stop) if index == 0: continue index2 = index + random.choice([-1, -1, -2]) if index2 < 0: index2 = 0 stair = stairs[index2] entrance = stop.entrances[0] entrance.dest = (entrance.dest & 0xFE00) | (stair.locid & 0x1FF)
def randomize_fanatics(unused_locids): stairs = [get_location(i) for i in [363, 359, 360, 361]] pitstops = [get_location(i) for i in [365, 367, 368, 369]] num_new_levels = random.randint(0, 1) + random.randint(1, 2) unused_locations = [get_location(l) for l in unused_locids] fsets = get_new_fsets("fanatics", 10, supplement=False) for _ in range(num_new_levels): stair = unused_locations.pop() stop = unused_locations.pop() stair.copy(random.choice(stairs[1:-1])) stop.copy(random.choice(pitstops[1:])) add_location_map("Fanatics Tower", stair.locid) add_location_map("Fanatics Tower", stop.locid) index = random.randint(1, len(stairs) - 1) stairs.insert(index, stair) pitstops.insert(index, stop) chest = stop.chests[0] chest.set_new_id() entrance = stop.entrances[0] entrance.dest = (entrance.dest & 0xFE00) | (stair.locid & 0x1FF) entrance = sorted(stair.entrances, key=lambda e: e.y)[1] entrance.dest = (entrance.dest & 0xFE00) | (stop.locid & 0x1FF) stair.setid = random.choice(fsets).setid stop.setid = random.choice(fsets).setid for a, b in zip(stairs, stairs[1:]): lower = sorted(a.entrances, key=lambda e: e.y)[0] upper = sorted(b.entrances, key=lambda e: e.y)[-1] lower.dest = (lower.dest & 0xFE00) | (b.locid & 0x1FF) upper.dest = (upper.dest & 0xFE00) | (a.locid & 0x1FF) for stop in pitstops: if random.choice([True, False]): continue index = pitstops.index(stop) if index == 0: continue index2 = index + random.choice([-1, -1, -2]) if index2 < 0: index2 = 0 stair = stairs[index2] entrance = stop.entrances[0] entrance.dest = (entrance.dest & 0xFE00) | (stair.locid & 0x1FF)
def mutate_price(self, undo_priceless=False, crazy_prices=False): if crazy_prices: if self.itemid == 250: self.price = random.randint(250, 500) else: self.price = random.randint(20, 500) return if self.price <= 2: if undo_priceless: self.price = self.rank() else: return normal = self.price // 2 self.price += random.randint(0, normal) + random.randint(0, normal) while random.randint(1, 10) == 10: self.price += random.randint(0, normal) + random.randint(0, normal) zerocount = 0 while self.price > 100: self.price = self.price // 10 zerocount += 1 while zerocount > 0: self.price = self.price * 10 zerocount += -1 self.price = min(self.price, 65000)
def bit_mutate(byte, op="on", nochange=0x00): if op == "on": bit = 1 << random.randint(0, 7) if bit & nochange: return byte byte = byte | bit elif op == "off": bit = 1 << random.randint(0, 7) if bit & nochange: return byte bit = 0xff ^ bit byte = byte & bit elif op == "invert": bit = 1 << random.randint(0, 7) if bit & nochange: return byte byte = byte ^ bit return byte
def mutate_special_action(self): if self.features['specialaction'] != 0 or not self.is_weapon: return new_action = random.randint(1, 0xf) if new_action == 8: return self.features['specialaction'] = new_action
def mutation(base): while True: value = max(base // 2, 1) if self.beserk: value += 1 value += random.randint(0, value) + random.randint(0, value) while random.randint(1, 10) == 10: value = max(value // 2, 1) value += random.randint(0, value) + random.randint(0, value) value = max(1, min(value, 0xFE)) if not self.beserk: break elif value >= base: break return value
def mutate(self, ap=False): if ap and self.ap is not None and 0 < self.ap < 100: factor = self.levelrank() / 100 self.ap += int(round(self.ap * factor)) while random.choice([True, False]): self.ap += random.randint(-1, 1) self.ap = min(100, max(self.ap, 0)) if self.ambusher: if not (self.pincer_prohibited and self.back_prohibited): self.misc1 |= 0x90
def mutate_feature(self, feature=None): if self.is_consumable or self.is_tool: return if feature is None: feature = random.choice(list(STATPROTECT.keys())) nochange = STATPROTECT[feature] if feature == 'special2': # Allow rare merit award bit on relics if self.is_relic: if random.randint(1, 10) == 10: nochange &= ~0x20 # Reduce chance of Genji Glove bit on non-relics elif random.randint(1, 4) != 4: nochange |= 0x10 self.features[feature] = bit_mutate(self.features[feature], op="on", nochange=nochange)
def make_challenge_event(loc, ptr): bg = ch_bgs.pop() formids = random.sample(challenges[loc.restrank], 2) formations = [get_formation(formid) for formid in formids] for formid in formids: challenges[loc.restrank].remove(formid) setcands = [f for f in get_fsets() if f.setid >= 0x100 and f.unused] fset = setcands.pop() fset.formids = formids fset.write_data(fout) timer = max( [e.stats['hp'] for f in formations for e in f.present_enemies]) reverse = False if timer >= 32768: reverse = True timer = 65535 - timer timer = max(timer, 3600) half = None while half is None or random.randint(1, 5) == 5: half = timer // 2 timer = half + random.randint(0, half) + random.randint(0, half) if reverse: timer = 65535 - timer timer = int(round(timer / 1800.0)) timer = max(2, min(timer, 36)) timer = timer * 1800 timer = [timer & 0xFF, timer >> 8] addr1 = ptr + 10 - 0xa0000 addr2 = ptr + (len(enemy_template) - 1) - 0xa0000 addr1 = [addr1 & 0xFF, (addr1 >> 8) & 0xFF, addr1 >> 16] addr2 = [addr2 & 0xFF, (addr2 >> 8) & 0xFF, addr2 >> 16] bytestring = list(enemy_template) bytestring[4:7] = addr1 bytestring[7:10] = addr2 bytestring[11:13] = timer bytestring[17] = fset.setid & 0xFF bytestring[18] = bg assert None not in bytestring sub = Substitution() sub.set_location(ptr) sub.bytestring = bytes(bytestring) sub.write(fout) return ptr + len(enemy_template)
def generate_attack(): if random.randint(1, 7) != 7: while True: modifier = random.choice(modifiers) move = random.choice(moves) if len(modifier) + len(move) <= 10: break else: modifier = "" if random.randint(1, 4) != 4: candidates = list(moves) else: candidates = list(modifiers) candidates = [c for c in candidates if len(c) >= 3] move = random.choice(candidates) if len(modifier) + len(move) < 10: return ("%s %s" % (modifier, move)).strip() return modifier + move
def evade_is_screwed_up(value): while random.randint(1, 8) == 8: if value == 0: choices = [1, 6] elif value in [5, 0xA]: choices = [-1] elif value == 6: choices = [1, -6] else: choices = [1, -1] value += random.choice(choices) return value
def generate_bonus(self): rank = self.rank candidates = set(bonus_ranks[rank]) candidates = candidates - used_bonuses if candidates: candidates = sorted(candidates) self.bonus = random.choice(candidates) used_bonuses.add(self.bonus) return if random.randint(1, 2) == 2: rank += 1 while random.randint(1, 10) == 10: rank += 1 rank = min(rank, 4) candidates = [] for i in range(rank + 1): candidates.extend(bonus_ranks[i]) if candidates: self.bonus = random.choice(candidates) used_bonuses.add(self.bonus)
def generate_bonus(self): rank = self.rank candidates = set(bonus_ranks[rank]) candidates = candidates - used_bonuses if candidates: candidates = sorted(candidates) self.bonus = random.choice(candidates) used_bonuses.add(self.bonus) return if random.randint(1, 2) == 2: rank += 1 while random.randint(1, 10) == 10: rank += 1 rank = min(rank, 4) candidates = [] for i in range(rank+1): candidates.extend(bonus_ranks[i]) if candidates: self.bonus = random.choice(candidates) used_bonuses.add(self.bonus)
def mutate(self, mp=False, mp_boost_value=None): if mp and self.mp is not None and 0 < self.mp < 100: factor = self.levelrank() / 100 self.mp += int(round(self.mp * factor)) while random.choice([True, False]): self.mp += random.randint(-1, 1) self.mp = min(100, max(self.mp, 0)) if mp_boost_value: self.mp = min(255, floor(self.mp * float(mp_boost_value))) if self.ambusher: if not (self.pincer_prohibited and self.back_prohibited): self.misc1 |= 0x90
def mutate_price(self, undo_priceless=False): if self.price <= 2: if undo_priceless: self.price = self.rank() else: return normal = self.price / 2 self.price += random.randint(0, normal) + random.randint(0, normal) while random.randint(1, 10) == 10: self.price += random.randint(0, normal) + random.randint(0, normal) zerocount = 0 while self.price > 100: self.price = self.price / 10 zerocount += 1 while zerocount > 0: self.price = self.price * 10 zerocount += -1 self.price = min(self.price, 65000)
def mutate_break_effect(self, always_break=False): global effects_used if self.is_consumable: return if always_break: effects_used = [] success = False for _ in xrange(100): spell, _ = self.pick_a_spell(custom=lambda x: x.spellid <= 0x3F) if spell.spellid not in effects_used: effects_used.append(spell.spellid) success = True break if not success: return self.features['breakeffect'] = spell.spellid if not self.is_weapon or random.randint(1, 2) == 2 or always_break: # always make armors usable in battle; weapons, only sometimes self.itemtype = self.itemtype | 0x20 # flag to break when used as an item if random.randint(1, 20) == 20: self.features['breakeffect'] &= 0x7F else: self.features['breakeffect'] |= 0x80 # flag to set chance to proc a spell if self.is_weapon and (not self.itemtype & 0x20 or random.randint(1, 2) == 2): self.features['breakeffect'] |= 0x40 else: self.features['breakeffect'] &= 0xBF self.features['targeting'] = spell.targeting & 0xef
def mutate_chests(self, guideline=None, crazy_prices=False, no_monsters=False, uncapped_monsters=False): if not self.chests: return rank = None override = maplocations_override.get(maplocations[self.locid], None) if (self.attacks & 0x80 == 0 or override) and self.locid in maplocations: location = maplocations[self.locid] if override: location = override fsets = {get_location(l).fset for l in maplocations_reverse[location] if get_location(l).attacks & 0x80 != 0} if fsets: rank = min(fsets, key=lambda f: f.rank()).rank() else: rank = self.rank() for c in self.chests: c.set_rank(rank) if guideline is None: if not self.chests: values = [c.get_current_value(guideline=100) for c in self.chests] average_value = (sum(values)*100) // len(values) guideline = average_value else: guideline = 100 if self.locid == 0xb4: return if self.locid == 0x147: guideline = random.randint(1820, 4500) random.shuffle(self.chests) for c in self.chests: if self.locid in range(0x139, 0x13d) and c.empty: c.mutate_contents(monster=True, guideline=guideline, crazy_prices=crazy_prices, uncapped_monsters=uncapped_monsters) continue elif self.locid == 0x147: pass # No monster-in-a-box in the ZoneEater falling ceiling room. # It causes problems with the ceiling event. in_falling_ceiling_room = self.locid == 280 and c.memid in range(232, 235) monster = False if in_falling_ceiling_room or no_monsters else None c.mutate_contents(guideline=guideline, crazy_prices=crazy_prices, monster=monster, uncapped_monsters=uncapped_monsters) if guideline is None and hasattr(c, "value") and c.value: guideline = c.value
def mutate_misc(self): self.misc &= 0x0F value = random.randint(1, 20) if value <= 3: self.misc |= 0x20 elif value <= 9: self.misc |= 0x10 for i in self.items: i = get_item(i) if i is None: continue if i.price * price_multipliers[self.discount] > 65535: self.misc &= 0x0F break
def generate_spells(self): global used self.spells, self.learnrates = [], [] rank = self.rank if random.randint(1, 5) == 5: rank += 1 rank = min(rank, max(rankbounds.keys())) if random.randint(1, 10) != 10: candidates = self.get_candidates(rank) if candidates: s = random.choice(candidates) self.spells.append(s) used.add(s) rank = self.rank for _ in xrange(random.randint(0, 2) + random.randint(0, 2)): candidates = self.get_candidates(rank, set_lower=False) if candidates: s = random.choice(candidates) if s in self.spells: continue self.spells.append(s) used.add(s) self.spells = sorted(self.spells, key=lambda s: s.spellid) self.learnrates = [] esperrank = rankbounds[self.rank] if esperrank is None: esperrank = rankbounds[self.rank-1] esperrank = esperrank * 3 for s in self.spells: spellrank = s.rank() learnrate = int(esperrank / float(spellrank)) learnrate = random.randint(0, learnrate) + random.randint(0, learnrate) while random.randint(1, 3) == 3: learnrate += 1 learnrate = max(1, min(learnrate, 20)) self.learnrates.append(learnrate)
def mutate_nibble(byte, left=False, limit=7): if left: nibble = (byte & 0xf0) >> 4 byte = byte & 0x0f else: nibble = byte & 0x0f byte = byte & 0xf0 value = nibble & 0x7 if nibble & 0x8: value = value * -1 while random.randint(1, 6) == 6: value += random.choice([1, -1]) value = max(-limit, min(value, limit)) nibble = abs(value) if value < 0: nibble = nibble | 0x8 if left: return byte | (nibble << 4) else: return byte | nibble
def mutate_contents(self, guideline=None, monster=None, guarantee_miab_treasure=False, enemy_limit=None, uniqueness=False): global used_formations, done_items if self.do_not_mutate and self.contents is not None: return if self.value is not None: value = self.value else: value = self.get_current_value(guideline=guideline) items = get_ranked_items() itemids = [i.itemid for i in items] if self.treasure: try: index = itemids.index(self.contents) except ValueError: index = 0 indexed_item = items[index] else: lowpriced = [i for i in items if i.rank() <= value*100] if not lowpriced: lowpriced = items[:random.randint(1, 16)] index = max(0, len(lowpriced)-1) indexed_item = lowpriced[index] chance = random.randint(1, 50) orphaned_formations = get_orphaned_formations() orphaned_formations = [f for f in orphaned_formations if f not in used_formations] extra_miabs = get_extra_miabs(0) if orphaned_formations or extra_miabs: chance -= 2 chance = max(chance, 1) if monster is True: chance = 1 elif monster is False: chance += 3 chance = min(chance, 50) formations = get_appropriate_formations() formations = [f for f in formations if f.get_guaranteed_drop_value() >= value * 100] if 1 <= chance <= 3 and (self.rank or formations): # monster self.set_content_type(0x20) rank = self.rank or min(formations, key=lambda f: f.rank()).rank() if guarantee_miab_treasure: extra_miabs = [] orphaned_formations = [] candidates = [] else: if len(extra_miabs) > 1: extra_miabs = get_extra_miabs(rank) if orphaned_formations or extra_miabs: formations = [f for f in formations if f.rank() >= rank] formations = formations[:random.randint(1, 3)] candidates = (orphaned_formations + extra_miabs) candidates = sorted(set(candidates)) if len(candidates) != 1: candidates += formations candidates = [c for c in candidates if c not in used_formations] candidates = [c for c in candidates if c.formid not in banned_formids] if enemy_limit is not None: candidates = [f for f in candidates if f.rank() <= enemy_limit] if not candidates: candidates = (formations + get_orphaned_formations() + get_extra_miabs(0)) if enemy_limit is not None: candidates = [f for f in candidates if f.rank() <= enemy_limit] candidates = sorted(candidates, key=lambda f: f.rank()) half = len(candidates) / 2 candidates = candidates[half:] index = random.randint(0, half) + random.randint(0, half) index = min(index, len(candidates)-1) candidates = candidates[index:] candidates = sorted(candidates, key=lambda f: f.rank()) if orphaned_formations: index = max( 0, len([c for c in candidates if c.rank() <= rank])-1) index = mutate_index(index, len(candidates), [False, True], (-3, 2), (-1, 1)) else: index = 0 index = mutate_index(index, len(candidates), [False, True], (-1, 4), (-1, 1)) chosen = candidates[index] for m in chosen.present_enemies: m.auxloc = "Monster-in-a-Box" banned_formids.append(chosen.formid) used_formations.append(chosen) chosen = get_2pack(chosen) # only 2-packs are allowed self.contents = chosen.setid & 0xFF elif 4 <= chance <= 5: # gold self.set_content_type(0x80) value = value / 2 value += (random.randint(0, value) + random.randint(0, value)) self.contents = min(0xFF, max(1, value)) if self.contents == 0xFF: self.contents -= random.randint(0, 20) + random.randint(0, 20) else: # treasure self.set_content_type(0x40) if uniqueness and random.randint(1, 7) != 7: if len(done_items) >= len(items): done_items = [] temp = [i for i in items if i == indexed_item or i not in done_items] if len(temp) > 1: items = temp index = items.index(indexed_item) if indexed_item in done_items: items.remove(indexed_item) index = mutate_index(index, len(items), [False, True], (-4, 2), (-2, 2)) self.contents = items[index].itemid done_items.append(items[index]) assert self.contents <= 0xFF self.value = value
def mutate_items(self, fout): items = get_ranked_items() if self.shoptype == 1: valid_items = [c for c in items if c.is_weapon or c.is_tool] elif self.shoptype == 2: valid_items = [c for c in items if c.is_armor] elif self.shoptype == 3: valid_items = [c for c in items if not (c.is_weapon or c.is_armor or c.is_relic)] elif self.shoptype == 4: valid_items = [c for c in items if c.is_relic] elif self.shoptype == 5: valid_items = list(items) old_items = [i for i in self.items if i != 0xFF] if not old_items: return old_items = [get_item(i) for i in old_items] old_items = [i for i in old_items if i] if len(old_items) == 0: average_value = 0 else: average_value = sum([i.rank() for i in old_items]) / len(old_items) average_item = len([i for i in valid_items if i.rank() <= average_value]) average_item += -1 average_item = valid_items[average_item] while random.randint(1, 3) == 3 and len(old_items) < 8: old_items.append(average_item) new_items = [] for item in old_items: if random.randint(1, 10) == 10: candidates = items else: candidates = valid_items try: index = candidates.index(item) except ValueError: continue while random.randint(1, 3) < 3: index += random.randint(-2, 2) index = max(0, min(index, len(candidates)-1)) new_items.append(candidates[index]) if not new_items: return for i in new_items: if i.price < 3: price = i.rank() modifier = price / 2 price += random.randint(0, modifier) while random.randint(1, 4) < 4: price += random.randint(0, modifier) price = min(price, 0xFEFE) i.price = price zerocount = 0 while i.price > 100: i.price = i.price / 10 zerocount += 1 while zerocount > 0: i.price = i.price * 10 zerocount += -1 i.write_stats(fout) self.items = [i.itemid for i in new_items] self.items = sorted(set(self.items)) while len(self.items) < 8: self.items.append(0xFF) assert len(self.items) == 8
def reset_special_relics(items, characters, fout): global changed_commands characters = [c for c in characters if c.id < 14] changedict = {} loglist = [] hidden_commands = set(range(0, 0x1E)) - set(invalid_commands) for c in characters: hidden_commands = hidden_commands - set(c.battle_commands) if 0x1D in hidden_commands and random.randint(1, 3) != 3: hidden_commands.remove(0x1D) flags = [0x04, 0x08, 0x10, 0x20, 0x40] random.shuffle(flags) for flag in flags: if changedict: donebefore, doneafter = tuple(zip(*changedict.values())) donebefore, doneafter = set(donebefore), set(doneafter) else: donebefore, doneafter = set([]), set([]) while True: if flag == 0x08: candidates = set([0x0, 0x1, 0x2, 0x12]) else: candidates = range(0, 0x1E) candidates = set(candidates) - set([0x04, 0x14, 0x15, 0x19]) if random.randint(1, 5) != 5: candidates = candidates - donebefore candidates = sorted(candidates) before = random.choice(candidates) if before == 0: tempchars = [c for c in characters] else: tempchars = [c for c in characters if before in c.battle_commands] if not tempchars: continue unused = set(range(0, 0x1E)) - set(invalid_commands) if len(tempchars) <= 4: for t in tempchars: unused = unused - set(t.battle_commands) if flag == 0x08: unused = unused - changed_commands if set(hidden_commands) & set(unused): unused = set(hidden_commands) & set(unused) if before in unused: unused.remove(before) if random.randint(1, 5) != 5: unused = unused - doneafter if not unused: continue after = random.choice(sorted(unused)) if after in hidden_commands: hidden_commands.remove(after) for ptrdict in [sperelic, sperelic2]: beforeptr, afterptr = ptrdict[flag] fout.seek(beforeptr) fout.write(chr(before)) fout.seek(afterptr) fout.write(chr(after)) break changedict[flag] = (before, after) for item in items: if (item.is_consumable or item.is_tool or not item.features['special1'] & 0x7C): continue if item.itemid == 0x67: continue item.equippable &= IMP_MASK item.equippable |= 1 << 12 # gogo for flag in [0x04, 0x08, 0x10, 0x20, 0x40]: if flag & item.features['special1']: before, after = changedict[flag] tempchars = [c for c in characters if before in c.battle_commands] for t in tempchars: item.equippable |= (1 << t.id) item.write_stats(fout) loglist.append((item.name, before, after)) return loglist
def mutate_stats(self): if self.is_consumable: return def mutate_power_hitmdef(): diff = min(self.features['power'], 0xFF-self.features['power']) diff = diff / 3 self.features['power'] = self.features['power'] - diff self.features['power'] = self.features['power'] + random.randint(0, diff) + random.randint(0, diff) self.features['power'] = int(min(0xFF, max(0, self.features['power']))) if "Dice" in self.name: return diff = min(self.features['hitmdef'], 0xFF-self.features['hitmdef']) diff = diff / 3 self.features['hitmdef'] = self.features['hitmdef'] - diff self.features['hitmdef'] = self.features['hitmdef'] + random.randint(0, diff) + random.randint(0, diff) self.features['hitmdef'] = int(min(0xFF, max(0, self.features['hitmdef']))) mutate_power_hitmdef() while random.randint(0, 10) == 10: mutate_power_hitmdef() def mutate_nibble(byte, left=False, limit=7): if left: nibble = (byte & 0xf0) >> 4 byte = byte & 0x0f else: nibble = byte & 0x0f byte = byte & 0xf0 value = nibble & 0x7 if nibble & 0x8: value = value * -1 while random.randint(1, 6) == 6: value += random.choice([1, -1]) value = max(-limit, min(value, limit)) nibble = abs(value) if value < 0: nibble = nibble | 0x8 if left: return byte | (nibble << 4) else: return byte | nibble self.features['speedvigor'] = mutate_nibble(self.features['speedvigor']) self.features['speedvigor'] = mutate_nibble(self.features['speedvigor'], left=True) self.features['magstam'] = mutate_nibble(self.features['magstam']) self.features['magstam'] = mutate_nibble(self.features['magstam'], left=True) evade, mblock = self.evade, self.mblock def evade_is_screwed_up(value): while random.randint(1, 8) == 8: if value == 0: choices = [1, 6] elif value in [5, 0xA]: choices = [-1] elif value == 6: choices = [1, -6] else: choices = [1, -1] value += random.choice(choices) return value evade = evade_is_screwed_up(evade) mblock = evade_is_screwed_up(mblock) self.features['mblockevade'] = evade | (mblock << 4)
def reset_equippable(items, characters, numchars=NUM_CHARS): global changed_commands prevents = filter(lambda i: i.prevent_encounters, items) for item in prevents: while True: test = 1 << random.randint(0, numchars-1) if item.itemid == 0xDE or not (CHAR_MASK & item.equippable): item.equippable = test break if test & item.equippable: test |= IMP_MASK item.equippable &= test break items = filter(lambda i: not (i.is_consumable or i.is_tool or i.prevent_encounters), items) new_weaps = range(numchars) random.shuffle(new_weaps) new_weaps = dict(zip(range(numchars), new_weaps)) for item in items: if numchars == 14 and random.randint(1, 10) == 10: # for umaro's benefit item.equippable |= 0x2000 if item.is_weapon: equippable = item.equippable item.equippable &= IMP_MASK for i in range(numchars): if equippable & (1 << i): item.equippable |= (1 << new_weaps[i]) elif item.is_relic: if random.randint(1, 15) == 15: item.equippable = 1 << (random.randint(0, numchars-1)) while random.randint(1, 3) == 3: item.equippable |= (1 << (random.randint(0, numchars-1))) else: item.equippable = CHAR_MASK charequips = [] valid_items = filter(lambda i: (not i.is_weapon and not i.is_relic and not i.equippable & 0x4000), items) for c in range(numchars): myequips = [] for i in valid_items: if i.equippable & (1 << c): myequips.append(True) else: myequips.append(False) random.shuffle(myequips) charequips.append(myequips) for item in valid_items: item.equippable &= 0xc000 random.shuffle(charequips) for c in range(numchars): assert len(valid_items) == len(charequips[c]) for equippable, item in zip(charequips[c], valid_items): if equippable: item.equippable |= (1 << c) if random.randint(1, 3) == 3: weaponstoo = True else: weaponstoo = False for item in items: if item.equippable == 0: if not weaponstoo: continue item.equippable |= (1 << random.randint(0, numchars-1)) paladin_equippable = None for item in items: if item.itemid in [0x66, 0x67]: if paladin_equippable is not None: item.equippable = paladin_equippable else: paladin_equippable = item.equippable if 0x10 not in changed_commands: for item in items: if item.itemid == 0x1C: rage_chars = [c for c in characters if 0x10 in c.battle_commands] rage_mask = 0 for c in rage_chars: rage_mask |= (1 << c.id) rage_mask |= (1 << 12) # gogo if item.equippable & rage_mask: invert_rage_mask = 0xFFFF ^ rage_mask item.equippable &= invert_rage_mask assert not item.equippable & rage_mask return items
def parse_checkpoints(): if ANCIENT: checkpoints = ANCIENT_CHECKPOINTS_TABLE else: checkpoints = TOWER_CHECKPOINTS_TABLE def ent_text_to_ints(room, single=False): locid, entids = room.split(':') locid = int(locid) if '|' in entids: entids = entids.split('|') elif ',' in entids: entids = entids.split(',') elif '>' in entids: entids = entids.split('>')[:1] else: entids = [entids] entids = map(int, entids) if single: assert len(entids) == 1 entids = entids[0] return locid, entids done, fixed, remove, oneway = [], [], [], [] routes = [list([]) for _ in xrange(3)] for line in open(checkpoints): line = line.strip() if not line or line[0] == '#': continue if line[0] == 'R': rank = int(line[1:]) for route in routes: route[-1].append(("R", rank)) elif line[0] == '&': locid, entids = ent_text_to_ints(line[1:]) for e in entids: fixed.append((locid, e)) elif line[0] == '-': locid, entids = ent_text_to_ints(line[1:]) for e in entids: remove.append((locid, e)) elif '>>' in line: line = line.split('>>') line = [ent_text_to_ints(s, single=True) for s in line] first, second = tuple(line) oneway.append((first, second)) else: if line.startswith("!"): line = line.strip("!") for route in routes: route.append([]) elif line.startswith("$"): line = line.strip("$") for route in routes: subroute = route[-1] head, tail = subroute[0], subroute[1:] random.shuffle(tail) route[-1] = [head] + tail else: random.shuffle(routes) rooms = line.split(',') chosenrooms = [] for room in rooms: locid, entids = ent_text_to_ints(room) candidates = [(locid, entid) for entid in entids] candidates = [c for c in candidates if c not in done] chosen = random.choice(candidates) chosenrooms.append(chosen) done.append(chosen) for room, route in zip(chosenrooms, routes): route[-1].append(room) for first, second in oneway: done = False for route in routes: for subroute in route: if first in subroute: index = subroute.index(first) index = random.randint(1, index+1) subroute.insert(index, second) done = True if not done: raise Exception("Unknown oneway rule") for route in routes: for i in range(len(route)): route[i] = Segment(route[i]) for index in range(len(routes)): routes[index] = Route(routes[index]) FIXED_ENTRANCES.extend(fixed) REMOVE_ENTRANCES.extend(remove) return routes
def assign_maps(routes, nummaps=None): clusters = get_clusters() new_clusters = clusters for route in routes: for segment in route.segments: for cluster in segment.clusters: if cluster in new_clusters: new_clusters.remove(cluster) for c in new_clusters: c.remove_adjacent_entrances() similars = [] def is_too_similar(c): if c.locid in towerlocids: return False if len(c.entrances) == 1: return False loc = get_location(c.locid) layer1 = loc.layer1ptr palette = loc.palette_index entxys = set([(e.x, e.y) for e in c.entrances]) for l, p, xys in similars: if layer1 == l and palette == p: if xys & entxys: return True similars.append((layer1, palette, entxys)) return False # first phase - bare minimum max_new_maps = nummaps best_clusters = [c for c in new_clusters if len(c.entrances) >= 3] while True: random.shuffle(best_clusters) done_maps, done_clusters = set([]), set([]) for cluster in best_clusters: location = get_location(cluster.locid) if (cluster.locid not in towerlocids and len(location.chests) == 0 and random.choice([True, False])): continue if cluster.locid in done_maps: continue chosen = None for route in routes: for segment in route.segments: for inter in segment.intersegments: if chosen is None or chosen.need < inter.need: chosen = inter if chosen.need > 0: if is_too_similar(cluster): continue chosen.add_cluster(cluster, need=True) done_maps.add(cluster.locid) done_clusters.add(cluster.clusterid) if len(done_maps) <= max_new_maps: break else: for route in routes: for segment in route.segments: segment.intersegments = [InterSegment() for _ in segment.intersegments] # second phase -supplementary random.shuffle(new_clusters) for cluster in new_clusters: CAPACITY_RATIO = len(done_maps) / float(max_new_maps) if cluster.clusterid in done_clusters: continue if cluster.locid in done_maps and not ANCIENT: continue if cluster.locid not in towerlocids: if (cluster.locid not in done_maps and len(done_maps) >= max_new_maps): continue if (cluster.locid in done_maps and len(done_maps) >= max_new_maps and get_location(cluster.locid).longentrances): continue rank = None if cluster.locid in done_maps or cluster.locid in towerlocids: for route in routes: for segment in route.segments: for c1 in segment.clusters: if c1.locid == cluster.locid: temp = route.segments.index(segment) if rank is None: rank = temp else: rank = min(rank, temp) for inter in segment.intersegments: for c2 in inter.clusters: if c2.locid == cluster.locid: temp = route.segments.index(segment) if rank is None: rank = temp else: rank = min(rank, temp) location = get_location(cluster.locid) if (cluster.locid not in towerlocids and CAPACITY_RATIO > 0.2 and len(cluster.entrances) <= 2 and len(location.chests) == 0 and random.choice([True, False])): continue if len(cluster.entrances) == 1: candidates = [] for route in routes: for (i, segment) in enumerate(route.segments): if rank is not None and i != rank: continue for inter in segment.intersegments: if inter.need < 0: candidates.append(inter) if candidates: if is_too_similar(cluster): continue chosen = random.choice(candidates) chosen.add_cluster(cluster, need=True) done_maps.add(cluster.locid) done_clusters.add(cluster.clusterid) elif len(cluster.entrances) >= 2: if cluster.locid not in towerlocids: if (CAPACITY_RATIO > 0.5 and len(location.chests) == 0 and random.randint(1, 3) == 3): continue if is_too_similar(cluster): continue route = random.choice(routes) if rank is not None: segment = route.segments[rank] else: segment = random.choice(route.segments) chosen = random.choice(segment.intersegments) chosen.add_cluster(cluster, need=True) done_maps.add(cluster.locid) done_clusters.add(cluster.clusterid) for route in routes: for segment in route.segments: segment.interconnect()