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 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 interconnect(self): self.links = [] if len(self.clusters) < 2: return starter = max(self.clusters, key=lambda c: len(c.entrances)) while True: links = [] done_ents = set([]) done_clusts = [starter] clusters = self.clusters random.shuffle(clusters) for c in clusters: if c in done_clusts: continue prelim = max(done_clusts, key=lambda c2: len(set(c2.entrances)-done_ents)) numents = len(set(prelim.entrances) - done_ents) if numents == 0: break candidates = [c2 for c2 in done_clusts if len(set(c2.entrances)-done_ents) == numents] chosen = random.choice(candidates) acands = [e for e in c.entrances if e not in done_ents] bcands = [e for e in chosen.entrances if e not in done_ents] a, b = random.choice(acands), random.choice(bcands) if c not in done_clusts: done_clusts.append(c) done_ents.add(a) done_ents.add(b) links.append((a, b)) if set(done_clusts) == set(self.clusters): break self.links = links
def ranked_clusters(self): startclust = self.clusters[0] done = set([startclust]) ranked = [] if startclust not in ranked: ranked.append(startclust) while True: ents = [e for c in done for e in c.entrances] relevant_links = [(a, b) for (a, b) in self.consolidated_links if a in ents or b in ents] new_ents = set([]) for a, b in relevant_links: if a not in ents: new_ents.add(a) if b not in ents: new_ents.add(b) if not new_ents: break newclusts = [ c for c in self.consolidated_clusters if set(c.entrances) & new_ents ] done |= set(newclusts) newclusts = sorted(newclusts, key=lambda c: c.clusterid) random.shuffle(newclusts) for c in newclusts: if c not in ranked: ranked.append(c) if set(self.consolidated_clusters) != set(ranked): import pdb pdb.set_trace() return ranked
def ranked_clusters(self): startclust = self.clusters[0] done = set([startclust]) ranked = [] if startclust not in ranked: ranked.append(startclust) while True: ents = [e for c in done for e in c.entrances] relevant_links = [(a, b) for (a, b) in self.consolidated_links if a in ents or b in ents] new_ents = set([]) for a, b in relevant_links: if a not in ents: new_ents.add(a) if b not in ents: new_ents.add(b) if not new_ents: break newclusts = [c for c in self.consolidated_clusters if set(c.entrances) & new_ents] done |= set(newclusts) newclusts = sorted(newclusts, key=lambda c: c.clusterid) random.shuffle(newclusts) for c in newclusts: if c not in ranked: ranked.append(c) if set(self.consolidated_clusters) != set(ranked): import pdb; pdb.set_trace() return ranked
def fill_out(self, additional=None): linked = self.linked_entrances links = [] unlinked = [] for cluster in self.clusters: entrances = [e for e in cluster.entrances if e not in linked] random.shuffle(entrances) if ANCIENT: unlinked.extend(entrances) else: if len(cluster.entrances) <= 4: unlinked.extend(entrances) else: diff = len(cluster.entrances) - len(entrances) if diff < 3: remaining = 3 - diff unlinked.extend(entrances[:remaining]) if additional: unlinked.append(additional) if not unlinked: return random.shuffle(unlinked) locids = [e.location.locid for e in unlinked] maxlocid = max(locids, key=lambda l: locids.count(l)) mosts = [e for e in unlinked if e.location.locid == maxlocid] lesses = [e for e in unlinked if e not in mosts] for m in mosts: if not lesses: break l = random.choice(lesses) links.append((m, l)) lesses.remove(l) unlinked.remove(l) unlinked.remove(m) extra = None while unlinked: if len(unlinked) == 1: extra = unlinked[0] break u1 = unlinked.pop() u2 = unlinked.pop() links.append((u1, u2)) self.links += links return extra
def fill_out(self, additional=None): linked = self.linked_entrances links = [] unlinked = [] for cluster in self.clusters: entrances = [e for e in cluster.entrances if e not in linked] random.shuffle(entrances) if ANCIENT: unlinked.extend(entrances) else: if len(cluster.entrances) <= 4: unlinked.extend(entrances) else: diff = len(cluster.entrances) - len(entrances) if diff < 3: remaining = 3 - diff unlinked.extend(entrances[:remaining]) if additional: unlinked.append(additional) if not unlinked: return random.shuffle(unlinked) locids = [e.location.locid for e in unlinked] maxlocid = max(locids, key=locids.count) mosts = [e for e in unlinked if e.location.locid == maxlocid] lesses = [e for e in unlinked if e not in mosts] for m in mosts: if not lesses: break l = random.choice(lesses) links.append((m, l)) lesses.remove(l) unlinked.remove(l) unlinked.remove(m) extra = None while unlinked: if len(unlinked) == 1: extra = unlinked[0] break u1 = unlinked.pop() u2 = unlinked.pop() links.append((u1, u2)) self.links += links return extra
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 swap_formations(self, other): if not (self.swappable and other.swappable): return highself = max(self.formations, key=lambda f: f.rank()) highother = max(other.formations, key=lambda f: f.rank()) candidates = self.formations + other.formations if random.randint(1, 7) != 7: candidates.remove(highself) candidates.remove(highother) random.shuffle(candidates) formids = [f.formid for f in candidates] self.formids = formids[:len(formids) // 2] other.formids = formids[len(formids) // 2:] if len(formids) == 6: self.formids.append(highself.formid) other.formids.append(highother.formid) self.shuffle_formations() other.shuffle_formations()
def remove_redundant_formation(self, fsets, replacement=None, check_only=False): result = False if len(set(self.formations)) == 1: pass elif len(set(self.formations)) < 4: result = True if replacement: for i in range(4): if self.formids[i] in self.formids[i + 1:]: formid = self.formids[i] self.formids.remove(formid) random.shuffle(self.formids) self.formids.append(replacement.formid) else: formations = list(self.formations) random.shuffle(formations) for i in range(4): f = self.formations[i] for fs2 in fsets: if fs2 != self and f in fs2.formations: result = True if replacement: formid = self.formids[i] self.formids.remove(formid) random.shuffle(self.formids) self.formids.append(replacement.formid) break if result is True: break if replacement: try: assert len(self.formations) == 4 assert self.formations[3] == replacement except: return False return True if not check_only and result is False: raise Exception("Can't use this formation.") return result
def remap_maps(routes): conlinks = [] cononeways = [] conentrances = [] conclusters = [] for route in routes: conlinks.extend(route.consolidated_links) cononeways.extend(route.consolidated_oneways) conentrances.extend(route.consolidated_entrances) conclusters.extend(route.consolidated_clusters) conclusters = sorted(set(conclusters), key=lambda c: c.clusterid) if ANCIENT: unused_maps = [ l.locid for l in get_locations() if l.locid not in towerlocids and l.locid not in PROTECTED ] rest_maps = [l.locid for l in get_unused_locations() if l.locid != 414] else: unused_maps = [l.locid for l in get_unused_locations()] rest_maps = [] for cluster in conclusters: if not isinstance(cluster, RestStop): continue locid = cluster.locid newlocid = rest_maps.pop() locexchange[(locid, locid)] = newlocid try: unused_maps.remove(newlocid) except: import pdb pdb.set_trace() for cluster in conclusters: if isinstance(cluster, RestStop): continue locid = cluster.locid if (locid, cluster.clusterid) in locexchange: continue locclusters = [ c for c in conclusters if not isinstance(c, RestStop) and c.locid == locid ] if locid in towerlocids: for c in locclusters: locexchange[(locid, c.clusterid)] = locid else: location = get_location(locid) if locid in unused_maps: newlocid = locid unused_maps = [u for u in unused_maps if u != newlocid] else: newlocid = unused_maps.pop() if location.longentrances: locexchange[(locid, cluster.clusterid)] = newlocid else: for c in locclusters: locexchange[(locid, c.clusterid)] = newlocid newlocations = [] for newlocid in sorted(set(locexchange.values())): keys = [ key for (key, value) in locexchange.items() if value == newlocid ] assert len(set([a for (a, b) in keys])) == 1 copylocid = keys[0][0] if copylocid >= 1000: cluster = [c for c in conclusters if c.locid == copylocid][0] copylocid = 413 location = get_location(413) newlocation = Location(locid=newlocid, dummy=True) newlocation.copy(location) newlocation.events = [] newlocation.npcs = [] newlocation.entrance_set.entrances = [] newlocation.restrank = cluster.rank else: location = get_location(copylocid) entrances = location.entrances newlocation = Location(locid=newlocid, dummy=True) newlocation.copy(location) newlocation.events = [] newlocation.npcs = [] newlocation.entrance_set.entrances = [] fixed = [ e for e in entrances if (e.location.locid, e.entid) in FIXED_ENTRANCES ] newlocation.entrance_set.entrances.extend(fixed) locclusters = [ c for c in conclusters if locexchange[(c.locid, c.clusterid)] == newlocid ] clustents = [e for c in locclusters for e in c.entrances] clustents = [e for e in clustents if e in conentrances] for ent in clustents: destent = [(a, b) for (a, b) in conlinks if ent in (a, b)] destent += [(a, b) for (a, b) in cononeways if ent == a] assert len(destent) == 1 destent = destent[0] destent = [d for d in destent if d != ent][0] destclust = [c for c in conclusters if destent in c.entrances] assert len(destclust) == 1 destclust = destclust[0] newdestlocid = locexchange[(destclust.locid, destclust.clusterid)] if destent.location.locid >= 1000: destloc = get_location(413) destent = [d for d in destloc.entrances if d.entid == 3][0] else: destloc = get_location(destent.location.locid) destent = [ d for d in destloc.entrances if d.entid == destent.entid ][0] mirror = destent.mirror if mirror: dest = mirror.dest & 0xFE00 destx, desty = mirror.destx, mirror.desty if abs(destx - destent.x) + abs(desty - destent.y) > 3: mirror = None if not mirror: dest, destx, desty = 0x2000, destent.x, destent.y dest &= 0x3DFF dest |= newdestlocid entrance = Entrance() entrance.x, entrance.y = ent.x, ent.y entrance.dest, entrance.destx, entrance.desty = dest, destx, desty entrance.set_location(newlocation) newlocation.entrance_set.entrances.append(entrance) newlocation.setid = 0 newlocation.ancient_rank = 0 newlocation.copied = copylocid adjents = [] for ent in newlocation.entrances: for clust in locclusters: if isinstance(clust, RestStop): continue assert clust.locid == newlocation.copied if clust.has_adjacent_entrances: x, y = ent.x, ent.y for ent2 in clust.entgroups.keys(): if ent2.x == ent.x and ent2.y == ent.y: break else: continue entgroup = clust.entgroups[ent2] for ent3 in entgroup: x3, y3 = ent3.x, ent3.y if x == x3 and y == y3: continue entrance = Entrance() entrance.x, entrance.y = x3, y3 entrance.dest, entrance.destx, entrance.desty = ( ent.dest, ent.destx, ent.desty) entrance.set_location(newlocation) adjents.append(entrance) newlocation.entrance_set.entrances.extend(adjents) newlocations.append(newlocation) locations = get_locations() newlocids = [l.locid for l in newlocations] assert len(newlocids) == len(set(newlocids)) for location in newlocations: for e in location.entrances: if (location.locid, e.entid) not in FIXED_ENTRANCES: assert e.dest & 0x1FF in newlocids assert location not in locations if location.locid not in towerlocids: location.entrance_set.convert_longs() # XXX: Unnecessary??? for i, loc in enumerate(newlocations): if loc.locid in towerlocids: oldloc = get_location(loc.locid) oldloc.entrance_set.entrances = loc.entrances oldloc.ancient_rank = loc.ancient_rank oldloc.copied = oldloc.locid newlocations[i] = oldloc ranked_clusters = [] for n in range(len(routes[0].segments)): rankedcsets = [route.segments[n].ranked_clusters for route in routes] for tricluster in zip_longest(*rankedcsets, fillvalue=None): tricluster = list(tricluster) random.shuffle(tricluster) for cluster in tricluster: if cluster is None: continue if cluster.locid not in ranked_clusters: cluster.routerank = n ranked_clusters.append(cluster) ranked_locations = [] for cluster in ranked_clusters: locid, clusterid = cluster.locid, cluster.clusterid newlocid = locexchange[locid, clusterid] newloc = [l for l in newlocations if l.locid == newlocid][0] if newloc not in ranked_locations: newloc.routerank = cluster.routerank ranked_locations.append(newloc) assert len(set(ranked_locations)) == len(set(newlocations)) ranked_locations = [ l for l in ranked_locations if not hasattr(l, "restrank") ] for i, loc in enumerate(ranked_locations): loc.ancient_rank = i loc.make_tower_basic() if not ANCIENT: if loc.locid not in towerlocids: loc.make_tower_flair() from options import Options_ loc.unlock_chests( 200, 1000, uncapped_monsters=Options_.is_code_active('bsiab')) fsets = get_new_fsets("kefka's tower", 20) fset = random.choice(fsets) for formation in fset.formations: formation.set_music(6) formation.set_continuous_music() loc.setid = fset.setid switch292, gate292 = (292, 0), (292, 1) switch334, gate334 = (334, 5), (334, 3) swd = {switch292: None, switch334: None, gate292: None, gate334: None} segments = [s for route in routes for s in route.segments] for segment in segments: for cluster in segment.ranked_clusters: for key in list(swd.keys()): locid, entid = key if cluster.locid == locid and entid in cluster.entids: assert swd[key] is None swd[key] = (segment, cluster) assert None not in list(swd.values()) s292segment, s292cluster = swd[switch292] s334segment, s334cluster = swd[switch334] g292segment, g292cluster = swd[gate292] g334segment, g334cluster = swd[gate334] if s292segment == g334segment and s334segment == g292segment: assert s292segment != s334segment ranked292 = s292segment.ranked_clusters ranked334 = s334segment.ranked_clusters if (ranked292.index(s292cluster) > ranked292.index(g334cluster) and ranked334.index(s334cluster) > ranked334.index(g292cluster)): raise Exception("Dungeon cannot be completed with this layout.") return newlocations, unused_maps
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 = {(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 not location.chests 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 not location.chests 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 not location.chests 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()
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 = list(map(int, entids)) if single: assert len(entids) == 1 entids = entids[0] return locid, entids done, fixed, remove, oneway = [], [], [], [] routes = [list([]) for _ in range(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 enumerate(route): route[i] = Segment(route[i]) for index, _ in enumerate(routes): routes[index] = Route(routes[index]) FIXED_ENTRANCES.extend(fixed) REMOVE_ENTRANCES.extend(remove) return routes
def manage_palettes(fout, change_to, char_ids): sabin_mode = options_.is_code_active('suplexwrecks') tina_mode = options_.is_code_active('bravenudeworld') christmas_mode = options_.is_code_active('christmas') new_palette_mode = not options_.is_code_active('sometimeszombies') characters = get_characters() npcs = get_npcs() charpal_options = {} for line in open(CHARACTER_PALETTE_TABLE): if line[0] == '#': continue charid, palettes = tuple(line.strip().split(':')) palettes = list(map(hex2int, palettes.split(','))) charid = hex2int(charid) charpal_options[charid] = palettes if new_palette_mode: twinpal = random.randint(0, 5) char_palette_pool = list(range(0, 6)) + list(range(0, 6)) char_palette_pool.remove(twinpal) char_palette_pool.append( random.choice(list(range(0, twinpal)) + list(range(twinpal, 6)))) while True: random.shuffle(char_palette_pool) #make sure terra, locke, and edgar are all different if twinpal in char_palette_pool[0:2]: continue if char_palette_pool[0] == char_palette_pool[1]: continue break char_palette_pool = char_palette_pool[:4] + [twinpal, twinpal ] + char_palette_pool[4:] palette_change_to = {} additional_celeses = [] for npc in npcs: if npc.graphics == 0x41: additional_celeses.append(npc) if npc.graphics not in charpal_options: continue # Don't recolor shadowy Sabin on Mt. Kolts if npc.locid in [0x60, 0x61]: continue if npc.graphics in change_to: new_graphics = change_to[npc.graphics] if (npc.graphics, npc.palette) in palette_change_to: new_palette = palette_change_to[(npc.graphics, npc.palette)] elif new_palette_mode and npc.graphics < 14: new_palette = char_palette_pool[npc.graphics] palette_change_to[(npc.graphics, npc.palette)] = new_palette npc.palette = new_palette else: while True: new_palette = random.choice(charpal_options[new_graphics]) if sabin_mode or tina_mode: new_palette = random.randint(0, 5) if (new_palette == 5 and new_graphics not in [3, 0xA, 0xC, 0xD, 0xE, 0xF, 0x12, 0x14] and random.randint(1, 10) != 10): continue break palette_change_to[(npc.graphics, npc.palette)] = new_palette npc.palette = new_palette npc.palette = new_palette for npc in additional_celeses: if (6, 0) in palette_change_to: npc.palette = palette_change_to[(6, 0)] main_palette_changes = {} for character in characters: c = character.id if c not in change_to: continue fout.seek(0x2CE2B + c) before = ord(fout.read(1)) new_graphics = change_to[c] new_palette = palette_change_to[(c, before)] main_palette_changes[c] = (before, new_palette) fout.seek(0x2CE2B + c) fout.write(bytes([new_palette])) pointers = [0, 4, 9, 13] pointers = [ptr + 0x18EA60 + (18 * c) for ptr in pointers] if c < 14: for ptr in pointers: fout.seek(ptr) byte = ord(fout.read(1)) byte = byte & 0xF1 byte |= ((new_palette + 2) << 1) fout.seek(ptr) fout.write(bytes([byte])) character.palette = new_palette if options_.is_code_active('repairpalette'): make_palette_repair(fout, main_palette_changes) if new_palette_mode: char_hues = [ 0, 10, 20, 30, 45, 60, 75, 90, 120, 150, 180, 200, 220, 240, 270, 300, 330 ] char_hues.append(random.choice([0, 0, 345, random.randint(105, 135)])) char_hues = shuffle_char_hues(char_hues) skintones = [((31, 24, 17), (25, 13, 7)), ((31, 23, 15), (25, 15, 8)), ((31, 24, 17), (25, 13, 7)), ((31, 25, 15), (25, 19, 10)), ((31, 25, 16), (24, 15, 12)), ((27, 17, 10), (20, 12, 10)), ((25, 20, 14), (19, 12, 4)), ((27, 22, 18), (20, 15, 12)), ((28, 22, 16), (22, 13, 6)), ((28, 23, 15), (22, 16, 7)), ((27, 23, 15), (20, 14, 9))] snowmanvampire = ((29, 29, 30), (25, 25, 27)) if christmas_mode or random.randint(1, 100) > 66: skintones.append(snowmanvampire) random.shuffle(skintones) # no vampire townsfolk if snowmanvampire in skintones[:6] and not christmas_mode: skintones.remove(snowmanvampire) skintones = skintones[:5] + [snowmanvampire] for i in range(6): pointer = 0x268000 + (i * 0x20) if new_palette_mode: palette = recolor_character_palette(fout, pointer, palette=None, flesh=(i == 5), santa=(christmas_mode and i == 3), skintones=skintones, char_hues=char_hues) else: palette = recolor_character_palette(fout, pointer, palette=None, flesh=(i == 5), santa=(christmas_mode and i == 3)) pointer = 0x2D6300 + (i * 0x20) recolor_character_palette(fout, pointer, palette=palette) # esper terra pointer = 0x268000 + (8 * 0x20) if new_palette_mode: palette = recolor_character_palette(fout, pointer, palette=None, trance=True) else: palette = recolor_character_palette(fout, pointer, palette=None, flesh=True, middle=False) pointer = 0x2D6300 + (6 * 0x20) palette = recolor_character_palette(fout, pointer, palette=palette) # recolor magitek and chocobos transformer = get_palette_transformer(middle=True) def recolor_palette(pointer, size): fout.seek(pointer) palette = [read_multi(fout, length=2) for _ in range(size)] palette = transformer(palette) fout.seek(pointer) for c in palette: write_multi(fout, c, length=2) recolor_palette(0x2cfd4, 23) recolor_palette(0x268000 + (7 * 0x20), 16) recolor_palette(0x12ee20, 16) recolor_palette(0x12ef20, 16) for line in open(EVENT_PALETTE_TABLE): if line[0] == '#': continue line = line.split(' ') if len(line) > 1: if line[1] == 'c' and options_.is_code_active( 'thescenarionottaken'): return if line[1] == 'd' and not options_.is_code_active( 'thescenarionottaken'): return pointer = hex2int(line[0].strip()) fout.seek(pointer) data = bytearray(fout.read(5)) char_id, palette = data[1], data[4] if char_id not in char_ids: continue try: data[4] = palette_change_to[(char_id, palette)] except KeyError: continue fout.seek(pointer) fout.write(data)
def manage_character_appearance(fout, preserve_graphics=False): characters = get_characters() wild = options_.is_code_active('partyparty') sabin_mode = options_.is_code_active('suplexwrecks') tina_mode = options_.is_code_active('bravenudeworld') soldier_mode = options_.is_code_active('quikdraw') moogle_mode = options_.is_code_active('kupokupo') ghost_mode = options_.is_code_active('halloween') christmas_mode = options_.is_code_active('christmas') sprite_swap_mode = options_.is_code_active('makeover') and not ( sabin_mode or tina_mode or soldier_mode or moogle_mode or ghost_mode) new_palette_mode = not options_.is_code_active('sometimeszombies') if new_palette_mode: # import recolors for incompatible base sprites recolors = [("cyan", 0x152D40, 0x16A0), ("mog", 0x15E240, 0x16A0), ("umaro", 0x162620, 0x16A0), ("dancer", 0x1731C0, 0x5C0), ("lady", 0x1748C0, 0x5C0)] for rc in recolors: filename = os.path.join("data", "sprites", "RC" + rc[0] + ".bin") try: with open_mei_fallback(filename, "rb") as f: sprite = f.read() except OSError: continue if len(sprite) >= rc[2]: sprite = sprite[:rc[2]] fout.seek(rc[1]) fout.write(sprite) if (wild or tina_mode or sabin_mode or christmas_mode): if christmas_mode: char_ids = list(range(0, 0x15)) # don't replace kefka else: char_ids = list(range(0, 0x16)) else: char_ids = list(range(0, 0x0E)) male = None female = None if tina_mode: change_to = dict(list(zip(char_ids, [0x12] * 100))) elif sabin_mode: change_to = dict(list(zip(char_ids, [0x05] * 100))) elif soldier_mode: change_to = dict(list(zip(char_ids, [0x0e] * 100))) elif ghost_mode: change_to = dict(list(zip(char_ids, [0x14] * 100))) elif moogle_mode: # all characters are moogles except Mog, Imp, and Esper Terra if wild: # make mog human mog = random.choice( list(range(0, 0x0A)) + list(range(0x0B, 0x0F)) + [0x10, 0x11, 0x13, 0x15]) #esper terra and imp neither human nor moogle esper_terra, imp = random.sample([0x0F, 0x12, 0x14], 2) else: mog = random.choice(list(range(0, 0x0A)) + list(range(0x0B, 0x0E))) esper_terra = 0x12 imp = 0x0F change_to = dict(list(zip(char_ids, [0x0A] * 100))) change_to[0x0A] = mog change_to[0x12] = esper_terra change_to[0x0F] = imp else: female = [0, 0x06, 0x08] female += [ c for c in [0x03, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0x14] if random.choice([True, False]) ] female = [c for c in char_ids if c in female] male = [c for c in char_ids if c not in female] if preserve_graphics: change_to = dict(list(zip(char_ids, char_ids))) elif wild: change_to = list(char_ids) random.shuffle(change_to) change_to = dict(list(zip(char_ids, change_to))) else: random.shuffle(female) random.shuffle(male) change_to = dict( list(zip(sorted(male), male)) + list(zip(sorted(female), female))) manage_character_names(fout, change_to, male) swap_to = get_sprite_swaps(char_ids, male, female, change_to) for c in characters: if c.id < 14: if sprite_swap_mode and c.id in swap_to: c.new_appearance = swap_to[c.id].name elif not preserve_graphics: c.new_appearance = NAME_ID_DICT[change_to[c.id]] else: c.new_appearance = c.original_appearance sprite_ids = list(range(0x16)) ssizes = ([0x16A0] * 0x10) + ([0x1560] * 6) spointers = dict([(c, sum(ssizes[:c]) + 0x150000) for c in sprite_ids]) ssizes = dict(list(zip(sprite_ids, ssizes))) char_portraits = {} char_portrait_palettes = {} sprites = {} riding_sprites = {} try: f = open(RIDING_SPRITE_TABLE, "r") except IOError: pass else: for line in f.readlines(): char_id, filename = line.strip().split(',', 1) try: g = open_mei_fallback( os.path.join("custom", "sprites", filename), "rb") except IOError: continue riding_sprites[int(char_id)] = g.read(0x140) g.close() f.close() for c in sprite_ids: fout.seek(0x36F1B + (2 * c)) portrait = read_multi(fout, length=2) char_portraits[c] = portrait fout.seek(0x36F00 + c) portrait_palette = fout.read(1) char_portrait_palettes[c] = portrait_palette fout.seek(spointers[c]) sprite = fout.read(ssizes[c]) if c in riding_sprites: sprite = sprite[:0x1560] + riding_sprites[c] sprites[c] = sprite if tina_mode: char_portraits[0x12] = char_portraits[0] char_portrait_palettes[0x12] = char_portrait_palettes[0] portrait_data = [] portrait_palette_data = [] fout.seek(0x2D1D00) for _ in range(19): portrait_data.append(fout.read(0x320)) fout.seek(0x2D5860) for _ in range(19): portrait_palette_data.append(fout.read(0x20)) free_portrait_ids, merchant = get_free_portrait_ids( swap_to, change_to, char_ids, char_portraits) for c in char_ids: new = change_to[c] portrait = char_portraits[new] portrait_palette = char_portrait_palettes[new] if c == 0x13 and sprite_swap_mode and not merchant: new_soldier = change_to[0xE] portrait = char_portraits[new_soldier] portrait_palette = char_portrait_palettes[new_soldier] elif (char_portraits[c] == 0 and c != 0): portrait = char_portraits[0xE] portrait_palette = char_portrait_palettes[0xE] elif sprite_swap_mode and c in swap_to: use_fallback = True fallback_portrait_id = swap_to[c].fallback_portrait_id if fallback_portrait_id < 0 or fallback_portrait_id > 18: fallback_portrait_id = 0xE portrait = fallback_portrait_id * 0x320 portrait_palette = bytes([fallback_portrait_id]) new_portrait_data = portrait_data[fallback_portrait_id] new_portrait_palette_data = portrait_palette_data[ fallback_portrait_id] if swap_to[c].has_custom_portrait(): use_fallback = False try: g = open_mei_fallback( os.path.join("custom", "sprites", swap_to[c].portrait_filename), "rb") h = open_mei_fallback( os.path.join("custom", "sprites", swap_to[c].portrait_palette_filename), "rb") except IOError: use_fallback = True print("failed to load portrait %s for %s, using fallback" % (swap_to[c].portrait_filename, swap_to[c].name)) else: new_portrait_data = g.read(0x320) new_portrait_palette_data = h.read(0x20) h.close() g.close() if not use_fallback or fallback_portrait_id in free_portrait_ids: portrait_id = free_portrait_ids[0] portrait = portrait_id * 0x320 portrait_palette = bytes([portrait_id]) free_portrait_ids.remove(free_portrait_ids[0]) fout.seek(0x2D1D00 + portrait) fout.write(new_portrait_data) fout.seek(0x2D5860 + portrait_id * 0x20) fout.write(new_portrait_palette_data) elif portrait == 0 and wild and change_to[c] != 0: portrait = char_portraits[0xE] portrait_palette = char_portrait_palettes[0xE] fout.seek(0x36F1B + (2 * c)) write_multi(fout, portrait, length=2) fout.seek(0x36F00 + c) fout.write(portrait_palette) if wild: fout.seek(spointers[c]) fout.write(sprites[0xE][:ssizes[c]]) fout.seek(spointers[c]) if sprite_swap_mode and c in swap_to: try: g = open_mei_fallback( os.path.join("custom", "sprites", swap_to[c].file), "rb") except IOError: newsprite = sprites[change_to[c]] for ch in characters: if ch.id == c: ch.new_appearance = NAME_ID_DICT[change_to[c]] else: newsprite = g.read(min(ssizes[c], swap_to[c].size)) # if it doesn't have riding sprites, it probably doesn't have a death sprite either if swap_to[c].size < 0x16A0: newsprite = newsprite[:0xAE0] + sprites[0xE][ 0xAE0:0xBA0] + newsprite[0xBA0:] g.close() else: newsprite = sprites[change_to[c]] newsprite = newsprite[:ssizes[c]] fout.write(newsprite) # celes in chains fout.seek(0x159500) chains = fout.read(192) fout.seek(0x17D660) fout.write(chains) manage_palettes(fout, change_to, char_ids)
def randomize_magicite(fout, sourcefile): magicite = [] # Some espers use 128x128 graphics, and those look like crap in the Ifrit/Shiva fight # So make sure Ifrit and Shiva have espers with small graphics. Tritoch also has # Issues with large sprites in the cutscene with the MagiTek armor espers = get_espers(sourcefile) shuffled_espers = {} espers_by_name = {e.name: e for e in espers} esper_graphics = [ MonsterGraphicBlock(pointer=0x127780 + (5 * i), name="") for i in range(len(espers)) ] for eg in esper_graphics: eg.read_data(sourcefile) # Ifrit's esper graphics are large. But he has separate enemy graphics that are fine. ifrit_graphics = copy.copy(get_monster(0x109).graphics) ifrit_id = espers_by_name["Ifrit"].id esper_graphics[ifrit_id] = ifrit_graphics # Pick the replacements for Ragnarok/Crusader out of high-rank espers high_rank_espers = [e for e in espers if e.rank >= 4] replace_ids = [ espers_by_name[name].id for name in ["Ragnarok", "Crusader"] ] special_espers = select_magicite(high_rank_espers, replace_ids) shuffled_espers.update(special_espers) # Pick replacements for Shiva, Ifrit, and Tritoch, which must not be large # Shiva and Ifrit must be picked from rank < 3, Tritoch can be any small_espers = [ e for e in espers if not esper_graphics[e.id].large and e not in shuffled_espers.values() ] low_ranked_small_espers = [e for e in small_espers if e.rank < 3] replace_ids = [espers_by_name[name].id for name in ["Shiva", "Ifrit"]] enemy_espers = select_magicite(low_ranked_small_espers, replace_ids) shuffled_espers.update(enemy_espers) remaining_small_espers = [ e for e in small_espers if e not in enemy_espers.values() ] replace_ids = [espers_by_name["Tritoch"].id] enemy_espers = select_magicite(remaining_small_espers, replace_ids) shuffled_espers.update(enemy_espers) # TODO: maybe allow tritoch to be big if we skip cutscenes #tritoch_id = [e.id for e in espers if e.name == "Tritoch"][0] #if esper_graphics[tritoch_id].large: # tritoch_formations = [0x1BF, 0x1C0, 0x1E7, 0x1E8] # for g in tritoch_formations: # f = get_formation(g) # f.mouldbyte = 6 << 4 # f.enemy_pos[0] = f.enemy_pos[0] & 0xF0 + 3 # f.write_data(fout) # Make sure Odin's replacement levels up odin_id = espers_by_name["Odin"].id raiden_id = espers_by_name["Raiden"].id while True: odin_candidates = [ e for e in espers if e not in shuffled_espers.values() and e.rank <= 3 ] odin_replacement = select_magicite(odin_candidates, [odin_id]) odin_replacement_rank = odin_replacement[odin_id].rank raiden_candidates = [ e for e in espers if e not in shuffled_espers.values() and e.rank > odin_replacement_rank ] if not raiden_candidates: continue raiden_replacement = select_magicite(raiden_candidates, [raiden_id]) shuffled_espers.update(odin_replacement) shuffled_espers.update(raiden_replacement) break # Shuffle all remaining espers for rank in range(0, 5): remaining_keys = [ e.id for e in espers if e.id not in shuffled_espers.keys() and e.rank <= rank ] remaining_values = [ e for e in espers if e not in shuffled_espers.values() and e.rank <= max(rank + 1, 2) ] random.shuffle(remaining_values) shuffled_espers.update(zip(remaining_keys, remaining_values)) assert (sorted([e.id for e in espers], key=id) == sorted(shuffled_espers.keys())) assert (sorted(espers, key=id) == sorted(shuffled_espers.values(), key=id)) locations = [e.location for e in espers] for i, e in shuffled_espers.items(): e.location = locations[i] with open(sourcefile, 'br') as s: for line in open(MAGICITE_TABLE, 'r'): line = line.split('#')[0].strip() l = line.split(',') address = int(l[0], 16) dialogue = [int(d, 16) for d in l[1:]] s.seek(address) instruction = ord(s.read(1)) esper_index = ord(s.read(1)) if instruction not in [ 0x86, 0x87 ] or esper_index < 0x36 or esper_index > 0x50: print("Error in magicite table") return magicite.append(Magicite(address, esper_index - 0x36, dialogue)) for m in magicite: original_name = espers[m.original_esper_index].name m.esper_index = shuffled_espers[m.original_esper_index].id new_name = shuffled_espers[m.original_esper_index].name for d in m.dialogue: patch_dialogue(d, original_name, "{" + original_name + "}") patch_dialogue(d, original_name + "'s", "{" + original_name + "Possessive}") dotted_name = "".join(chain(*zip(original_name, repeat('.'))))[:-1] patch_dialogue(d, dotted_name, "{" + original_name + "Dotted}") set_dialogue_var(original_name, new_name) set_dialogue_var(original_name + "Possessive", new_name + "'s") dotted_new_name = "".join(chain(*zip(new_name, repeat('.'))))[:-1] set_dialogue_var(original_name + "Dotted", dotted_new_name) fout.seek(m.address + 1) fout.write(bytes([m.esper_index + 0x36])) phoenix_replacement = shuffled_espers[espers_by_name["Phoenix"].id] set_location_name(71, f"{phoenix_replacement.name.upper()} CAVE") esper_monsters = [(0x108, "Shiva"), (0x109, "Ifrit"), (0x114, "Tritoch"), (0x115, "Tritoch"), (0x144, "Tritoch")] for monster_id, name in esper_monsters: monster = get_monster(monster_id) esper_id = [e.id for e in espers if e.name == name][0] replacement = shuffled_espers[esper_id] change_enemy_name(fout, monster_id, replacement.name) mg = esper_graphics[replacement.id] monster.graphics.copy_data(mg) monster.graphics.write_data(fout) ragnarok = get_item(27) ragnarok.dataname = bytes([0xd9]) + name_to_bytes( shuffled_espers[espers_by_name["Ragnarok"].id].name, 12) ragnarok.write_stats(fout) return shuffled_espers
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()
def remap_maps(routes): conlinks = [] cononeways = [] conentrances = [] conclusters = [] for route in routes: conlinks.extend(route.consolidated_links) cononeways.extend(route.consolidated_oneways) conentrances.extend(route.consolidated_entrances) conclusters.extend(route.consolidated_clusters) conclusters = sorted(set(conclusters), key=lambda c: c.clusterid) if ANCIENT: unused_maps = [l.locid for l in get_locations() if l.locid not in towerlocids and l.locid not in PROTECTED] rest_maps = [l.locid for l in get_unused_locations() if l.locid != 414] else: unused_maps = [l.locid for l in get_unused_locations()] rest_maps = [] for cluster in conclusters: if not isinstance(cluster, RestStop): continue locid = cluster.locid newlocid = rest_maps.pop() locexchange[(locid, locid)] = newlocid try: unused_maps.remove(newlocid) except: import pdb; pdb.set_trace() for cluster in conclusters: if isinstance(cluster, RestStop): continue locid = cluster.locid if (locid, cluster.clusterid) in locexchange: continue locclusters = [c for c in conclusters if not isinstance(c, RestStop) and c.locid == locid] if locid in towerlocids: for c in locclusters: locexchange[(locid, c.clusterid)] = locid else: location = get_location(locid) if locid in unused_maps: newlocid = locid unused_maps = [u for u in unused_maps if u != newlocid] else: newlocid = unused_maps.pop() if location.longentrances: locexchange[(locid, cluster.clusterid)] = newlocid else: for c in locclusters: locexchange[(locid, c.clusterid)] = newlocid newlocations = [] for newlocid in sorted(set(locexchange.values())): keys = [key for (key, value) in locexchange.items() if value == newlocid] assert len(set([a for (a, b) in keys])) == 1 copylocid = keys[0][0] if copylocid >= 1000: cluster = [c for c in conclusters if c.locid == copylocid][0] copylocid = 413 location = get_location(413) newlocation = Location(locid=newlocid, dummy=True) newlocation.copy(location) newlocation.events = [] newlocation.npcs = [] newlocation.entrance_set.entrances = [] newlocation.restrank = cluster.rank else: location = get_location(copylocid) entrances = location.entrances newlocation = Location(locid=newlocid, dummy=True) newlocation.copy(location) newlocation.events = [] newlocation.npcs = [] newlocation.entrance_set.entrances = [] fixed = [e for e in entrances if (e.location.locid, e.entid) in FIXED_ENTRANCES] newlocation.entrance_set.entrances.extend(fixed) locclusters = [c for c in conclusters if locexchange[(c.locid, c.clusterid)] == newlocid] clustents = [e for c in locclusters for e in c.entrances] clustents = [e for e in clustents if e in conentrances] for ent in clustents: destent = [(a, b) for (a, b) in conlinks if ent in (a, b)] destent += [(a, b) for (a, b) in cononeways if ent == a] assert len(destent) == 1 destent = destent[0] destent = [d for d in destent if d != ent][0] destclust = [c for c in conclusters if destent in c.entrances] assert len(destclust) == 1 destclust = destclust[0] newdestlocid = locexchange[(destclust.locid, destclust.clusterid)] if destent.location.locid >= 1000: destloc = get_location(413) destent = [d for d in destloc.entrances if d.entid == 3][0] else: destloc = get_location(destent.location.locid) destent = [d for d in destloc.entrances if d.entid == destent.entid][0] mirror = destent.mirror if mirror: dest = mirror.dest & 0xFE00 destx, desty = mirror.destx, mirror.desty if abs(destx - destent.x) + abs(desty - destent.y) > 3: mirror = None if not mirror: dest, destx, desty = 0x2000, destent.x, destent.y dest &= 0x3DFF dest |= newdestlocid entrance = Entrance() entrance.x, entrance.y = ent.x, ent.y entrance.dest, entrance.destx, entrance.desty = dest, destx, desty entrance.set_location(newlocation) newlocation.entrance_set.entrances.append(entrance) newlocation.setid = 0 newlocation.ancient_rank = 0 newlocation.copied = copylocid adjents = [] for ent in newlocation.entrances: for clust in locclusters: if isinstance(clust, RestStop): continue assert clust.locid == newlocation.copied if clust.has_adjacent_entrances: x, y = ent.x, ent.y for ent2 in clust.entgroups.keys(): if ent2.x == ent.x and ent2.y == ent.y: break else: continue entgroup = clust.entgroups[ent2] for ent3 in entgroup: x3, y3 = ent3.x, ent3.y if x == x3 and y == y3: continue entrance = Entrance() entrance.x, entrance.y = x3, y3 entrance.dest, entrance.destx, entrance.desty = ( ent.dest, ent.destx, ent.desty) entrance.set_location(newlocation) adjents.append(entrance) newlocation.entrance_set.entrances.extend(adjents) newlocations.append(newlocation) locations = get_locations() newlocids = [l.locid for l in newlocations] assert len(newlocids) == len(set(newlocids)) for location in newlocations: for e in location.entrances: if (location.locid, e.entid) not in FIXED_ENTRANCES: assert e.dest & 0x1FF in newlocids assert location not in locations if location.locid not in towerlocids: location.entrance_set.convert_longs() # XXX: Unnecessary??? for i, loc in enumerate(newlocations): if loc.locid in towerlocids: oldloc = get_location(loc.locid) oldloc.entrance_set.entrances = loc.entrances oldloc.ancient_rank = loc.ancient_rank oldloc.copied = oldloc.locid newlocations[i] = oldloc ranked_clusters = [] for n in range(len(routes[0].segments)): rankedcsets = [route.segments[n].ranked_clusters for route in routes] for tricluster in izip_longest(*rankedcsets, fillvalue=None): tricluster = list(tricluster) random.shuffle(tricluster) for cluster in tricluster: if cluster is None: continue if cluster.locid not in ranked_clusters: cluster.routerank = n ranked_clusters.append(cluster) ranked_locations = [] for cluster in ranked_clusters: locid, clusterid = cluster.locid, cluster.clusterid newlocid = locexchange[locid, clusterid] newloc = [l for l in newlocations if l.locid == newlocid][0] if newloc not in ranked_locations: newloc.routerank = cluster.routerank ranked_locations.append(newloc) assert len(set(ranked_locations)) == len(set(newlocations)) ranked_locations = [l for l in ranked_locations if not hasattr(l, "restrank")] for i, loc in enumerate(ranked_locations): loc.ancient_rank = i loc.make_tower_basic() if not ANCIENT: if loc.locid not in towerlocids: loc.make_tower_flair() loc.unlock_chests(200, 1000) fsets = get_new_fsets("kefka's tower", 20) fset = random.choice(fsets) for formation in fset.formations: formation.set_music(6) formation.set_continuous_music() loc.setid = fset.setid switch292, gate292 = (292, 0), (292, 1) switch334, gate334 = (334, 5), (334, 3) swd = {switch292: None, switch334: None, gate292: None, gate334: None} segments = [s for route in routes for s in route.segments] for segment in segments: for cluster in segment.ranked_clusters: for key in swd.keys(): locid, entid = key if cluster.locid == locid and entid in cluster.entids: assert swd[key] is None swd[key] = (segment, cluster) assert None not in swd.values() s292segment, s292cluster = swd[switch292] s334segment, s334cluster = swd[switch334] g292segment, g292cluster = swd[gate292] g334segment, g334cluster = swd[gate334] if s292segment == g334segment and s334segment == g292segment: assert s292segment != s334segment ranked292 = s292segment.ranked_clusters ranked334 = s334segment.ranked_clusters if (ranked292.index(s292cluster) > ranked292.index(g334cluster) and ranked334.index(s334cluster) > ranked334.index(g292cluster)): raise Exception("Dungeon cannot be completed with this layout.") return newlocations, unused_maps
def shuffle_formations(self): random.shuffle(self.formids)
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 manage_opera(fout, affect_music): fout.seek(0) data = fout.read() SAMPLE_MAX_SIZE = 3746 global opera_log #Determine opera cast class OperaSinger: def __init__(self, name, title, sprite, gender, file, sampletype, sample, octave, volume, init): self.name = name self.title = title self.sprite = sprite self.gender = gender self.file = file self.sampletype = sampletype self.sample = sample self.octave = octave self.volume = volume self.init = init self.sid = ("ext" if sampletype == "brr" else sampletype) + ":" + sample if self.sampletype not in ["brr", "ext"]: try: self.sample = int(self.sample, 16) except ValueError: print( f"WARNING: in opera config: {name}: invalid sample id for {sampletype}: {sample}" ) self.sample = 1 def get_sample_text(self, prg): if self.sampletype in ["brr", "ext"]: return f"\n#BRR 0x{prg:02X}; {self.sample}\n" elif self.sampletype == "legacy": return f"\n#BRR 0x{prg:02X}; {get_legacy_import(self.sample, subpath='music')}\n" else: return f"\n#WAVE 0x{prg:02X} 0x{self.sample:02X}\n" #voice range notes # Overture (Draco) ranges from B(o-1) to B(o) # Aria (Maria) ranges from D(o) to D(o+1) # Duel 1 (Draco) ranges from B(o-1) to E(o) # Duel 2 (Maria) ranges from A(o) to F(o+1) # Duel 3 (Ralse) ranges from E(o) to C(o+1) # Duel 4 (Draco) ranges from B(o-1) to F(o) # Duel 5 (Ralse) ranges from E(o) to B(o) singer_options = [] opera_config = configparser.ConfigParser(interpolation=None) opera_config.optionxform = lambda option: option try: with open(os.path.join('custom', 'opera.txt'), "r") as f: opera_config.read_file(f) except OSError: print("WARNING: failed to load opera config") return if 'Singers' not in opera_config or 'Factions' not in opera_config: print("WARNING: opera config is invalid, or failed to load") return for k, v in opera_config.items('Singers'): line = [k] + v.split('|') singer_options.append([l.strip() for l in line]) singer_options = [OperaSinger(*s) for s in singer_options] #categorize by voice sample (unless not changing music) if affect_music: voices = {} for s in singer_options: if s.sid in voices: voices[s.sid].append(s) else: voices[s.sid] = [s] #find a set of voices that doesn't overflow SPC RAM while True: vchoices = random.sample(list(voices.values()), 3) test_mml = read_opera_mml('duel') for i, c in enumerate(vchoices): test_mml += c[0].get_sample_text(i + 0x2A) memusage = get_spc_memory_usage(test_mml, subpath="music") if memusage <= SAMPLE_MAX_SIZE: break #select characters charpool = [] char = {} #by voice, for singers for v in vchoices: charpool.append(random.choice(v)) random.shuffle(charpool) for c in ["Maria", "Draco", "Ralse"]: char[c] = charpool.pop() opera_log += str(str(c) + ": ").ljust(17) + string.capwords( str(char[c].name)) + "\n" #by sprite/name, for impresario charpool = [c for c in singer_options if c not in char.values()] char["Impresario"] = random.choice(charpool) opera_log += str("Impresario: ").ljust(17) + string.capwords( str(char["Impresario"].name)) + "\n" else: print( "\nalasdraco -- note: opera voices will not be changed unless a" + " random music code (johnnydmad/johnnyachaotic) is also used." + " Sprites and text will still be affected.") cchoices = random.sample(singer_options, 4) char = {} for c in ["Maria", "Draco", "Ralse", "Impresario"]: char[c] = cchoices.pop() opera_log += str(str(c) + ": ").ljust(17) + string.capwords( str(char[c].name)) + "\n" #reassign sprites in npc data locations = get_locations() #first, free up space for a unique Ralse #choose which other NPCs get merged: # 0. id for new scholar, 1. offset for new scholar, 2. id for merged sprite, 3. offset for merged sprite, 4. spritesheet filename, 5. extra pose type merge_options = [ (32, 0x172C00, 33, 0x1731C0, "dancer.bin", "flirt"), #fancy gau -> dancer (32, 0x172C00, 35, 0x173D40, "gau-fancy.bin", "sideeye"), #clyde -> fancy gau (60, 0x17C4C0, 35, 0x173D40, "daryl.bin", "sideeye"), #clyde -> daryl (42, 0x176580, 35, 0x173D40, "katarin.bin", "sideeye"), #clyde -> katarin (60, 0x17C4C0, 42, 0x176580, "daryl.bin", "sideeye"), #katarin -> daryl (60, 0x17C4C0, 42, 0x176580, "katarin.bin", "sideeye"), #daryl -> katarin (60, 0x17C4C0, 41, 0x175FC0, "daryl.bin", "sleeping"), #rachel -> daryl (60, 0x17C4C0, 41, 0x175FC0, "rachel.bin", "sleeping"), #daryl -> rachel (60, 0x17C4C0, 30, 0x172080, "returner.bin", "prone"), #daryl -> returner (53, 0x17A1C0, 59, 0x17BFC0, "figaroguard.bin", None), #conductor -> figaro guard (45, 0x1776C0, 48, 0x178800, "maduin.bin", "prone"), #yura -> maduin ] merge = random.choice(merge_options) #merge sacrifice into new slot replace_npc(locations, (merge[0], None), merge[2]) #move scholar into sacrifice slot for i in [0, 1, 3, 4, 5]: replace_npc(locations, (27, i), (merge[0], i)) #randomize palettes of shuffled characters for i in [0x1A, 0x1B, 0x1C, 0x2B]: palette = random.randint(0, 5) for l in locations: for npc in l.npcs: if npc.graphics == i: #npc.palette = palette pass #debug info # for l in locations: # for npc in l.npcs: # if npc.graphics in [118,138]: # print() # print(f"graphics {npc.graphics} found at ${npc.pointer:X}, in location 0x{l.locid:X}") # print(f"palette {npc.palette}, facing byte {npc.facing:X}") # print(f"facing {npc.facing & 3:X}, change {npc.facing>>2 & 1:X}") # print(f"bglayer {npc.facing>>3 & 3:X}, unknown1 {npc.facing>>5 & 1:X}") # print(f"mirror {npc.facing>>6 & 2:X}, unknown2 {npc.facing>>7 & 1:X}") #randomize item thrown off balcony balcony = get_location(0xEC) for npc in balcony.npcs: if npc.graphics == 88: item = random.choice([ #(84, 0x24, "chest"), #treasure box (palette broken) (87, 0x44, "statue"), (88, 0x54, "flowers"), #bouquet (89, 0x54, "letter"), #letter (91, 0x54, "magicite"), #magicite (92, 0x44, "book"), #book ##DO NOT THROW THE BABY (96, 0x44, "crown"), #slave crown (97, 0x54, "weight"), #4ton weight (100, 0x54, "bandana"), #locke's bandana ##(124, 0x02, "helmet") #a shiny thing (didn't work) ]) npc.graphics = item[0] npc.palette = random.choice(range(6)) npc.facing = item[1] set_dialogue_var("OperaItem", item[2]) opera_log += str("Opera Flowers: ").ljust(17) + string.capwords( item[2]) + "\n" #print(f"opera item is {npc.graphics}, palette {npc.palette} ({item[2]})") #print(f"at address {npc.pointer:X}") #4 ton weight for npc in get_location(0xEB).npcs: if npc.graphics == 97: item = random.choice([ (89, 0x00, "letter"), # letter (92, 0x44, "book"), # book (108, 0x00, "rat"), # rat (87, 0x44, "mini statue"), # mini statue (93, 0x54, "baby" ), # ultros is allowed to try to throw the baby (STOP HIM) (97, 0x54, "4 ton weight"), # 4ton weight (112, 0x43, "fire"), # fire ###(118, 0x10), #rock (didn't work) ###(138, 0x12) #leo's sword (didn't work) ]) npc.graphics = item[0] npc.palette = random.choice(range(6)) npc.facing = item[1] opera_log += str("Opera Rafters: ").ljust(17) + string.capwords( item[2]) + "\n" #print(f"ultros item is {npc.graphics}, palette {npc.palette}") #print(f"at address {npc.pointer:X}") #set up some spritesheet locations pose = { 'singing': [0x66, 0x67, 0x68, 0x69, 0x64, 0x65, 0x60, 0x61, 0x62, 0x63], 'ready': list(range(0x3E, 0x44)), 'prone': list(range(0x51, 0x57)), 'angry': list(range(0x76, 0x7C)), 'flirt': [0xA3, 0xA4, 0x9C, 0x99, 0x9A, 0x9B], 'sideeye': [0x92, 0x93, 0x94, 0x95, 0x08, 0x09], 'sleeping': [0x86, 0x87, 0x88, 0x89, 0x08, 0x09] } opath = os.path.join("custom", "opera") #load scholar graphics try: with open(os.path.join(opath, "ralse.bin"), "rb") as f: sprite = f.read() except IOError: print(f"failed to open custom/opera/ralse.bin") sprite = None if sprite: new_sprite = create_sprite(sprite) data = byte_insert(data, merge[1], new_sprite) #load new graphics into merged slot try: with open(os.path.join(opath, f"{merge[4]}"), "rb") as f: sprite = f.read() except IOError: try: with open(os.path.join("custom", "sprites", f"{merge[4]}"), "rb") as f: sprite = f.read() except: print( f"failed to open custom/opera/{merge[4]} or custom/sprites/{merge[4]}" ) sprite = None if sprite: #print(f"merge {merge}, pose {pose}") new_sprite = create_sprite( sprite, pose[merge[5]] if merge[5] is not None else []) data = byte_insert(data, merge[3], new_sprite) #load new graphics into opera characters char_offsets = { "Maria": (0x1705C0, pose['ready'] + pose['singing']), "Draco": (0x1713C0, pose['prone'] + pose['singing']), "Ralse": (0x170CC0, pose['prone'] + pose['singing']), "Impresario": (0x176B40, pose['angry']) } for cname, c in char.items(): #print(f"{cname} -> {c.name}") try: with open(os.path.join(opath, f"{c.sprite}.bin"), "rb") as f: sprite = f.read() except IOError: try: with open(os.path.join("custom", "sprites", f"{c.sprite}.bin"), "rb") as f: sprite = f.read() except: print( f"failed to open custom/opera/{c.sprite}.bin or custom/sprites/{c.sprite}.bin" ) continue offset, extra_tiles = char_offsets[cname] #tiles = list(range(0x28)) + extra_tiles #new_sprite = bytearray() #for t in tiles: # loc = t*32 # new_sprite.extend(sprite[loc:loc+32]) #data = byte_insert(data, offset, new_sprite) new_sprite = create_sprite(sprite, extra_tiles) data = byte_insert(data, offset, new_sprite) ### adjust script load_patch_file("opera") factions = [] for f, _ in opera_config.items('Factions'): factions.append([s.strip() for s in f.split('|')]) factions = random.choice(factions) if random.choice([False, True]): factions = (factions[1], factions[0]) opera_log += str("Opera Factions: ").ljust(17) + string.capwords( factions[0]) + " VS " + string.capwords(factions[1]) + "\n" set_dialogue_var("OperaEast", factions[0]) set_dialogue_var("OperaWest", factions[1]) set_dialogue_var("maria", char['Maria'].name) set_dialogue_var("draco", char['Draco'].name) set_dialogue_var("ralse", char['Ralse'].name) set_dialogue_var("impresario", char['Impresario'].name) set_dialogue_var("mariatitle", char['Maria'].title) set_dialogue_var("dracotitle", char['Draco'].title) set_dialogue_var("ralsetitle", char['Ralse'].title) set_dialogue_var("impresariotitle", char['Impresario'].title) char['Maria'].gender = set_pronoun('Maria', char['Maria'].gender) char['Draco'].gender = set_pronoun('Draco', char['Draco'].gender) char['Ralse'].gender = set_pronoun('Ralse', char['Ralse'].gender) char['Impresario'].gender = set_pronoun('Impresario', char['Impresario'].gender) #due to the variance in power relations connoted by "make X my queen" and "make X my king", this line will be altered in all variations so that it means roughly the same thing no matter the Maria replacement's gender if char['Maria'].gender == "female": set_dialogue_var("MariaTheGirl", "the girl") set_dialogue_var("MariaQueenBad", "mine") set_dialogue_var("MariaQueen", "queen") set_dialogue_var("MariaWife", "wife") elif char['Maria'].gender == "male": set_dialogue_var("MariaTheGirl", "the guy") set_dialogue_var("MariaQueenBad", "mine") set_dialogue_var("MariaQueen", "king") set_dialogue_var("MariaWife", "husband") elif char['Maria'].gender == "object": set_dialogue_var("MariaTheGirl", char['Maria'].title + " " + char['Maria'].name) set_dialogue_var("MariaQueenBad", "mine") set_dialogue_var("MariaQueen", "prize") set_dialogue_var("MariaWife", "collection") else: set_dialogue_var("MariaTheGirl", "the girl") set_dialogue_var("MariaQueenBad", "mine") set_dialogue_var("MariaQueen", "consort") set_dialogue_var("MariaWife", "partner") if char['Impresario'].gender == "male": set_dialogue_var("ImpresarioMan", "man") # from "music man" elif char['Impresario'].gender == "female": set_dialogue_var("ImpresarioMan", "madam") elif char['Impresario'].gender == "object": set_dialogue_var("ImpresarioMan", "machine") else: set_dialogue_var("ImpresarioMan", "maker") ### adjust music opera = {} maria_sample = char['Maria'].get_sample_text(0x2A) draco_sample = char['Draco'].get_sample_text(0x2B) ralse_sample = char['Ralse'].get_sample_text(0x2C) try: overture = read_opera_mml('overture') overture += draco_sample overture += f"\n#def draco= |B o{char['Draco'].octave[0]} v{char['Draco'].volume} {char['Draco'].init}\n" seg = read_opera_mml(f"{char['Draco'].file}_overture") overture += seg aria = read_opera_mml('aria') aria += maria_sample aria += f"\n#def maria= |A o{char['Maria'].octave[1]} v{char['Maria'].volume} {char['Maria'].init}\n" seg = read_opera_mml(f"{char['Maria'].file}_aria") aria += seg duel = read_opera_mml('duel') duel += maria_sample duel += f"\n#def maria= |A o{char['Maria'].octave[3]} v{char['Maria'].volume} {char['Maria'].init}\n" duel += draco_sample duel += f"\n#def draco= |B o{char['Draco'].octave[2]} v{char['Draco'].volume} {char['Draco'].init}\n" duel += f"\n#def draco2= |B o{char['Draco'].octave[5]} v{char['Draco'].volume} {char['Draco'].init}\n" duel += ralse_sample duel += f"\n#def ralse= |C o{char['Ralse'].octave[4]} v{char['Ralse'].volume} {char['Ralse'].init}\n" duel += f"\n#def ralse2= |C o{char['Ralse'].octave[6]} v{char['Ralse'].volume} {char['Ralse'].init}\n" duelists = ["Draco", "Maria", "Ralse", "Draco", "Ralse"] for i in range(5): seg = read_opera_mml(f"{char[duelists[i]].file}_duel{i+1}") duel += seg #print(overture) #print("########") #print(duel) #print("########") #print(aria) opera['opera_draco'] = overture opera['wed_duel'] = duel opera['aria'] = aria except IOError: print("opera music generation failed, reverting to default") affect_music = False fout.seek(0) fout.write(data) return opera if affect_music else None
def reset_special_relics(items, characters, filename): global changed_commands f = open(filename, 'r+b') 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] f.seek(beforeptr) f.write(chr(before)) f.seek(afterptr) f.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(filename) loglist.append((item.name, before, after)) f.close() return loglist
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 interconnect(self): links = [] for segment in self.intersegments: segment.interconnect() for i, (a, b) in enumerate(zip(self.clusters, self.clusters[1:])): aid = self.entids[i] bid = self.entids[i+1] if a.singleton: acands = a.entrances elif i == 0: acands = [e for e in a.entrances if e.entid == aid] else: acands = [e for e in a.entrances if e.entid != aid] aent = random.choice(acands) bcands = [e for e in b.entrances if e.entid == bid] bent = bcands[0] inter = self.intersegments[i] if a.singleton: previnter = self.intersegments[i-1] if i > 0 else None thresh = 3 for j in xrange(thresh): k = thresh-j intercands = [] excands = inter.get_external_candidates(num=k, test=True) if excands: intercands.append(inter) k = max(1, k-1) if previnter is not None: excands = previnter.get_external_candidates(num=k, test=True) if excands: intercands.append(previnter) if intercands: break else: raise Exception("No available intersegments.") chosen = random.choice(intercands) excands = (chosen.get_external_candidates(num=1)) if excands is None: raise Exception("Routing error.") links.append((aent, excands[0])) a.entering, a.exiting = True, True if previnter and not previnter.empty: # TODO: Sometimes this fails for j in range(i, len(self.intersegments)): nextinter = self.intersegments[j] if nextinter.empty: continue c = previnter.get_external_candidates(num=1)[0] d = nextinter.get_external_candidates(num=1)[0] links.append((c, d)) break else: raise Exception("No exit segment available.") elif not inter.empty: if not b.singleton: excands = inter.get_external_candidates(num=2) if excands is None: raise Exception("No exit segment available. (2)") random.shuffle(excands) links.append((bent, excands[1])) b.entering = True else: excands = inter.get_external_candidates(num=1) links.append((aent, excands[0])) a.exiting = True elif (inter.empty and not b.singleton): links.append((aent, bent)) a.exiting = True b.entering = True elif (inter.empty and b.singleton): inter2 = self.intersegments[i+1] assert not inter2.empty excands = inter2.get_external_candidates(num=1) links.append((aent, excands[0])) a.exiting = True else: import pdb; pdb.set_trace() assert False for i, a in enumerate(self.clusters): aid = self.entids[i] if not (a.entering or i == 0): if a.singleton: aent = a.entrances[0] else: acands = [e for e in a.entrances if e.entid == aid] aent = acands[0] while i > 0: inter = self.intersegments[i-1] if not inter.empty: break i += -1 if inter.empty: raise Exception("Routing error.") excands = inter.get_external_candidates(num=1) links.append((aent, excands[0])) a.entering = True self.links = links self.check_links()
def interconnect(self): links = [] for segment in self.intersegments: segment.interconnect() for i, (a, b) in enumerate(zip(self.clusters, self.clusters[1:])): aid = self.entids[i] bid = self.entids[i + 1] if a.singleton: acands = a.entrances elif i == 0: acands = [e for e in a.entrances if e.entid == aid] else: acands = [e for e in a.entrances if e.entid != aid] aent = random.choice(acands) bcands = [e for e in b.entrances if e.entid == bid] bent = bcands[0] inter = self.intersegments[i] if a.singleton: previnter = self.intersegments[i - 1] if i > 0 else None thresh = 3 for j in range(thresh): k = thresh - j intercands = [] excands = inter.get_external_candidates(num=k, test=True) if excands: intercands.append(inter) k = max(1, k - 1) if previnter is not None: excands = previnter.get_external_candidates(num=k, test=True) if excands: intercands.append(previnter) if intercands: break else: raise Exception("No available intersegments.") chosen = random.choice(intercands) excands = (chosen.get_external_candidates(num=1)) if excands is None: raise Exception("Routing error.") links.append((aent, excands[0])) a.entering, a.exiting = True, True if previnter and not previnter.empty: # TODO: Sometimes this fails for j in range(i, len(self.intersegments)): nextinter = self.intersegments[j] if nextinter.empty: continue c = previnter.get_external_candidates(num=1)[0] d = nextinter.get_external_candidates(num=1)[0] links.append((c, d)) break else: raise Exception("No exit segment available.") elif not inter.empty: if not b.singleton: excands = inter.get_external_candidates(num=2) if excands is None: raise Exception("No exit segment available. (2)") random.shuffle(excands) links.append((bent, excands[1])) b.entering = True else: excands = inter.get_external_candidates(num=1) links.append((aent, excands[0])) a.exiting = True elif (inter.empty and not b.singleton): links.append((aent, bent)) a.exiting = True b.entering = True elif (inter.empty and b.singleton): inter2 = self.intersegments[i + 1] assert not inter2.empty excands = inter2.get_external_candidates(num=1) links.append((aent, excands[0])) a.exiting = True else: import pdb pdb.set_trace() assert False for i, a in enumerate(self.clusters): aid = self.entids[i] if not (a.entering or i == 0): if a.singleton: aent = a.entrances[0] else: acands = [e for e in a.entrances if e.entid == aid] aent = acands[0] while i > 0: inter = self.intersegments[i - 1] if not inter.empty: break i += -1 if inter.empty: raise Exception("Routing error.") excands = inter.get_external_candidates(num=1) links.append((aent, excands[0])) a.entering = True self.links = links self.check_links()
def recolor_character_palette(fout, pointer, palette=None, flesh=False, middle=True, santa=False, skintones=None, char_hues=None, trance=False): fout.seek(pointer) if palette is None: palette = [read_multi(fout, length=2) for _ in range(16)] outline, eyes, hair, skintone, outfit1, outfit2, NPC = (palette[:2], palette[2:4], palette[4:6], palette[6:8], palette[8:10], palette[10:12], palette[12:]) def components_to_color(xxx_todo_changeme): (red, green, blue) = xxx_todo_changeme return red | (green << 5) | (blue << 10) new_style_palette = None if skintones and char_hues: new_style_palette = generate_character_palette(skintones, char_hues, trance=trance) # aliens, available in palette 5 only if flesh and random.randint(1, 20) == 1: transformer = get_palette_transformer(middle=middle) new_style_palette = transformer(new_style_palette) elif trance: new_style_palette = generate_character_palette(trance=True) new_palette = new_style_palette if new_style_palette else [] if not flesh: pieces = (outline, eyes, hair, skintone, outfit1, outfit2, NPC) if not new_style_palette else [NPC] for piece in pieces: transformer = get_palette_transformer(middle=middle) piece = list(piece) piece = transformer(piece) new_palette += piece if not new_style_palette: new_palette[6:8] = skintone if options_.is_code_active('christmas'): if santa: # color kefka's palette to make him look santa-ish new_palette = palette new_palette[8] = components_to_color((0x18, 0x18, 0x16)) new_palette[9] = components_to_color((0x16, 0x15, 0x0F)) new_palette[10] = components_to_color((0x1C, 0x08, 0x03)) new_palette[11] = components_to_color((0x18, 0x02, 0x05)) else: # give them red & green outfits red = [ components_to_color((0x19, 0x00, 0x05)), components_to_color((0x1c, 0x02, 0x04)) ] green = [ components_to_color((0x07, 0x12, 0x0b)), components_to_color((0x03, 0x0d, 0x07)) ] random.shuffle(red) random.shuffle(green) outfit = [red, green] random.shuffle(outfit) new_palette[8:10] = outfit[0] new_palette[10:12] = outfit[1] else: transformer = get_palette_transformer(middle=middle) new_palette = transformer(palette) if new_style_palette: new_palette = new_style_palette[0:12] + new_palette[12:] palette = new_palette fout.seek(pointer) for p in palette: write_multi(fout, p, length=2) return palette
def get_sprite_swaps(char_ids, male, female, vswaps): sprite_swap_mode = options_.is_code_active('makeover') wild = options_.is_code_active('partyparty') clone_mode = options_.is_code_active('cloneparty') replace_all = options_.is_code_active( 'novanilla') or options_.is_code_active('frenchvanilla') external_vanillas = False if options_.is_code_active('novanilla') else ( options_.is_code_active('frenchvanilla') or clone_mode) if not sprite_swap_mode: return [] class SpriteReplacement: def __init__(self, file, name, gender, riding=None, fallback_portrait_id=0xE, portrait_filename=None, uniqueids=None, groups=None): self.file = file.strip() self.name = name.strip() self.gender = gender.strip().lower() self.size = 0x16A0 if riding is not None and riding.lower( ) == "true" else 0x1560 self.uniqueids = [s.strip() for s in uniqueids.split('|') ] if uniqueids else [] self.groups = [s.strip() for s in groups.split('|')] if groups else [] if self.gender == "female": self.groups.append("girls") if self.gender == "male": self.groups.append("boys") self.weight = 1.0 if fallback_portrait_id == '': fallback_portrait_id = 0xE self.fallback_portrait_id = int(fallback_portrait_id) self.portrait_filename = portrait_filename if self.portrait_filename is not None: self.portrait_filename = self.portrait_filename.strip() if self.portrait_filename: self.portrait_palette_filename = portrait_filename.strip() if self.portrait_palette_filename and self.portrait_palette_filename: if self.portrait_palette_filename[-4:] == ".bin": self.portrait_palette_filename = self.portrait_palette_filename[: -4] self.portrait_palette_filename = self.portrait_palette_filename + ".pal" else: self.portrait_filename = None def has_custom_portrait(self): return self.portrait_filename is not None and self.portrait_palette_filename is not None def is_on(self, checklist): for g in self.uniqueids: if g in checklist: return True return False f = open_mei_fallback(SPRITE_REPLACEMENT_TABLE) known_replacements = [ SpriteReplacement(*line.strip().split(',')) for line in f.readlines() ] f.close() #uniqueids for sprites pulled from rom vuids = { 0: "terra", 1: "locke", 2: "cyan", 3: "shadow", 4: "edgar", 5: "sabin", 6: "celes", 7: "strago", 8: "relm", 9: "setzer", 10: "moogle", 11: "gau", 12: "gogo6", 13: "umaro", 16: "leo", 17: "banon", 18: "terra", 21: "kefka" } #determine which character ids are makeover'd blacklist = set() if replace_all: num_to_replace = len(char_ids) is_replaced = [True] * num_to_replace else: replace_min = 8 if not wild else 16 replace_max = 12 if not wild else 20 num_to_replace = min(len(known_replacements), random.randint(replace_min, replace_max)) is_replaced = [True] * num_to_replace + [False] * (len(char_ids) - num_to_replace) random.shuffle(is_replaced) for i, t in enumerate(is_replaced): if i in vuids and not t: blacklist.update([s.strip() for s in vuids[i].split('|')]) if external_vanillas: #include vanilla characters, but using the same system/chances as all others og_replacements = [ SpriteReplacement("ogterra.bin", "Terra", "female", "true", 0, None, "terra"), SpriteReplacement("oglocke.bin", "Locke", "male", "true", 1, None, "locke"), SpriteReplacement("ogcyan.bin", "Cyan", "male", "true", 2, None, "cyan"), SpriteReplacement("ogshadow.bin", "Shadow", "male", "true", 3, None, "shadow"), SpriteReplacement("ogedgar.bin", "Edgar", "male", "true", 4, None, "edgar"), SpriteReplacement("ogsabin.bin", "Sabin", "male", "true", 5, None, "sabin"), SpriteReplacement("ogceles.bin", "Celes", "female", "true", 6, None, "celes"), SpriteReplacement("ogstrago.bin", "Strago", "male", "true", 7, None, "strago"), SpriteReplacement("ogrelm.bin", "Relm", "female", "true", 8, None, "relm", "kids"), SpriteReplacement("ogsetzer.bin", "Setzer", "male", "true", 9, None, "setzer"), SpriteReplacement("ogmog.bin", "Mog", "neutral", "true", 10, None, "moogle"), SpriteReplacement("oggau.bin", "Gau", "male", "true", 11, None, "gau", "kids"), SpriteReplacement("oggogo.bin", "Gogo", "neutral", "true", 12, None, "gogo6"), SpriteReplacement("ogumaro.bin", "Umaro", "neutral", "true", 13, None, "umaro") ] if wild: og_replacements.extend([ SpriteReplacement("ogtrooper.bin", "Trooper", "neutral", "true", 14), SpriteReplacement("ogimp.bin", "Imp", "neutral", "true", 15), SpriteReplacement("ogleo.bin", "Leo", "male", "true", 16, None, "leo"), SpriteReplacement("ogbanon.bin", "Banon", "male", "true", 17, None, "banon"), SpriteReplacement("ogesperterra.bin", "Esper Terra", "female", "true", 0, "esperterra-p.bin", "terra"), SpriteReplacement("ogmerchant.bin", "Merchant", "male", "true", 1), SpriteReplacement("ogghost.bin", "Ghost", "neutral", "true", 18), SpriteReplacement("ogkefka.bin", "Kefka", "male", "true", 17, "kefka-p.bin", "kefka") ]) if clone_mode: used_vanilla = [ NAME_ID_DICT[vswaps[n]] for i, n in enumerate(char_ids) if not is_replaced[i] ] og_replacements = [ r for r in og_replacements if r.name not in used_vanilla ] known_replacements.extend(og_replacements) #weight selection based on no*/hate*/like*/love* codes whitelist = [ c.name[4:] for c in options_.active_codes if c.name.startswith("love") ] replace_candidates = [] for r in known_replacements: whitelisted = False for g in r.groups: if not r.weight: break if g in whitelist: whitelisted = True if options_.is_code_active("no" + g): r.weight = 0 elif options_.is_code_active("hate" + g): r.weight /= 3 elif options_.is_code_active("like" + g): r.weight *= 2 if whitelist and not whitelisted: r.weight = 0 if r.weight: replace_candidates.append(r) #select sprite replacements if not wild: female_candidates = [ c for c in replace_candidates if c.gender == "female" ] male_candidates = [c for c in replace_candidates if c.gender == "male"] neutral_candidates = [ c for c in replace_candidates if c.gender != "male" and c.gender != "female" ] swap_to = {} for char_id in random.sample(char_ids, len(char_ids)): if not is_replaced[char_id]: continue if wild: candidates = replace_candidates else: if char_id in female: candidates = female_candidates elif char_id in male: candidates = male_candidates else: candidates = neutral_candidates if random.randint(0, len(neutral_candidates) + 2 * len(candidates)) <= len(neutral_candidates): candidates = neutral_candidates if clone_mode: reverse_blacklist = [c for c in candidates if c.is_on(blacklist)] if reverse_blacklist: weights = [c.weight for c in reverse_blacklist] swap_to[char_id] = random.choices(reverse_blacklist, weights)[0] blacklist.update(swap_to[char_id].uniqueids) candidates.remove(swap_to[char_id]) continue final_candidates = [c for c in candidates if not c.is_on(blacklist)] if final_candidates: weights = [c.weight for c in final_candidates] swap_to[char_id] = random.choices(final_candidates, weights)[0] blacklist.update(swap_to[char_id].uniqueids) candidates.remove(swap_to[char_id]) else: print( f"custom sprite pool for {char_id} empty, using a vanilla sprite" ) return swap_to
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 manage_ancient(options_, fout, sourcefile, form_music_overrides={}): change_battle_commands = [41, 42, 43] if not options_.shuffle_commands: alrs = AutoLearnRageSub(require_gau=True) alrs.set_location(0x23b73) alrs.write(fout) enable_morph_sub = Substitution() enable_morph_sub.bytestring = bytes([0xEA] * 2) enable_morph_sub.set_location(0x25410) enable_morph_sub.write(fout) enable_mpoint_sub = Substitution() enable_mpoint_sub.bytestring = bytes([0xEA] * 2) enable_mpoint_sub.set_location(0x25E38) enable_mpoint_sub.write(fout) change_battle_commands += list(range(18, 28)) moogle_commands = [ 0x03, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x10, 0x12, 0x13, 0x16, 0x18, 0x1a, 0x1b, 0x1d ] for i in change_battle_commands: commands = random.sample(moogle_commands, 2) c = get_character(i) c.battle_commands = [0x00, commands[0], commands[1], 0x01] c.write_battle_commands(fout) for i in [32, 33]: c = get_character(i) c.battle_commands = [0x00, 0x1D, 0xFF, 0x01] c.write_battle_commands(fout) characters = get_characters() gau = [c for c in characters if c.id == 11][0] if not options_.replace_commands and gau.battle_commands[1] in [ 0x11, None ]: gau.battle_commands[1] = 0xFF gau.write_battle_commands(fout) to_dummy = [get_item(0xF6), get_item(0xF7)] dummy_names = ["Pebble", "Tissue"] for dummy_name, item in zip(dummy_names, to_dummy): name = bytes([0xFF]) + name_to_bytes(dummy_name, 12) item.dataname = name item.price = 4 item.itemtype = 6 item.write_stats(fout) blank_sub = Substitution() blank_sub.set_location(0x2D76C1) blank_sub.bytestring = bytearray([0xFF] * (0x2D76F5 - blank_sub.location)) blank_sub.bytestring[blank_sub.size // 2] = 0 blank_sub.write(fout) goddess_save_sub = Substitution() goddess_save_sub.bytestring = bytes([0xFD, 0xFD]) goddess_save_sub.set_location(0xC170A) goddess_save_sub.write(fout) goddess_save_sub.set_location(0xC1743) goddess_save_sub.write(fout) goddess_save_sub.set_location(0xC1866) goddess_save_sub.write(fout) # decrease exp needed for level up if options_.is_code_active('racecave'): maxlevel = 49 divisor = 12.0 elif options_.is_code_active('speedcave'): maxlevel = 49 divisor = 8.0 else: maxlevel = 49 divisor = 2.0 for level in range(maxlevel): ratio = (float(level) / maxlevel)**2 ratio = min(ratio, 1.0) xptr = 0x2d8220 + (level * 2) fout.seek(xptr) exp = read_multi(fout, length=2) newexp = (exp / divisor) remaining = exp - newexp newexp = int(round(newexp + (ratio * remaining))) newexp = max(newexp, 1) fout.seek(xptr) write_multi(fout, newexp, length=2) startsub = Substitution() startsub.bytestring = bytearray([ 0xD7, 0xF3, # remove Daryl 0xD5, 0xF0, # remove Terra from party 0xD5, 0xE0, # remove Terra from party 0xDC, 0x7E, # fix ending? $1F4F bit 6 0xB8, 0x43, # show magic points after battle 0x3F, 0x0E, 0x00, 0x3F, 0x0F, 0x00, ]) if options_.is_code_active('racecave'): num_starting = 9 + random.randint(0, 2) + random.randint(0, 1) elif options_.is_code_active('speedcave'): num_starting = 4 + random.randint(0, 3) + random.randint(0, 2) else: num_starting = 4 + random.randint(0, 1) + random.randint(0, 1) starting = random.sample(list(range(14)), num_starting) for c in starting: startsub.bytestring += bytearray([0xD4, 0xF0 | c]) startsub.bytestring += bytearray([0xD4, 0xE0 | c]) for c in characters: i = c.id cptr = 0x2d7ca0 + 0x15 + (i * 22) fout.flush() fout.seek(cptr) level = ord(fout.read(1)) level &= 0xF3 if i >= 14 or options_.is_code_active( "speedcave") and i not in starting: level |= 0b1000 fout.seek(cptr) fout.write(bytes([level])) fout.seek(0xa5e74) fout.write(b'\x00') # remove Terra's magitek tempcands = [ 14, 15, random.choice(list(range(18, 28))), random.choice([32, 33]) ] if options_.is_code_active('speedcave'): tempcands.append(random.choice([16, 17])) tempcands.append(random.choice([41, 42, 43])) charcands = list(range(14)) + random.sample(tempcands, 2) chargraphics = { 14: 0x11, 15: 0x10, 16: 0x14, 17: 0x14, 32: 0xE, 33: 0xE, 41: 0x15, 42: 0x15, 43: 0x15 } for c in range(14): chargraphics[c] = c for c in range(18, 28): chargraphics[c] = 0xA for n, i in enumerate(charcands): c = [x for x in characters if x.id == i][0] if i in chargraphics: g = chargraphics[i] else: g = i startsub.bytestring.extend( [0x7F, n, i, 0x37, n, g, 0x43, n, c.palette, 0x40, n, i]) c.slotid = n runaway = random.choice([ c for c in characters if hasattr(c, "slotid") and c.id == c.slotid ]).slotid if runaway in starting: byte, bit = runaway // 8, runaway % 8 mem_addr = ((0x1b + byte) << 3) | bit startsub.bytestring += bytearray([0xD7, mem_addr]) shadow_leaving_sub = Substitution() shadow_leaving_sub.set_location(0x248A6) shadow_leaving_sub.bytestring = bytearray([ 0x1C, 0xDE + (runaway // 8), 0x1E, # TRB $1ede 0x20, 0xE3, 0x47, 0xAD, 0xFB + (runaway // 8), 0x1E, # LDA $1efb 0x09, 1 << (runaway % 8), # ORA #$08 0x8D, 0xFB + (runaway // 8), 0x1E, # STA $1efb 0xAD, 0xDE + (runaway // 8), 0x1E, # LDA $1ede 0x29, 0xFF ^ (1 << (runaway % 8)), # AND #$F7 0x8D, 0xDE + (runaway // 8), 0x1E, # STA $1ede ]) while len(shadow_leaving_sub.bytestring) < 23: shadow_leaving_sub.bytestring.append(0xEA) shadow_leaving_sub.bytestring += bytearray([0xA9, 0xFE, 0x20, 0x92, 0x07]) shadow_leaving_sub.write(fout) shadow_leaving_sub.set_location(0x24861) shadow_leaving_sub.bytestring = bytearray([ 0xAE, runaway, 0x30, 0x30, 0x26, 0x20, 0x5A, 0x4B, 0xC9, random.choice([0x20, 0x10, 0x8, 0x4, 0x2, 0x1]), 0xB0, 0x1F, 0xAD, 0x1F, 0x20, 0xD0, 0x1A, 0xAD, 0x76, 0x3A, 0xC9, 0x02, 0x90, 0x13, 0xBD, 0xE4, 0x3E, 0x89, 0xC2, 0xD0, 0x0C, 0xA9, 1 << (runaway % 8), 0x2C, 0xBD + (runaway // 8), 0x3E, 0xD0, 0x05, 0x2C, 0xDE + (runaway // 8), 0x1E, ]) shadow_leaving_sub.write(fout) shadow_leaving_sub.set_location(0x10A851) shadow_leaving_sub.bytestring = bytearray([ 0x0E, 0x03, runaway, 0x6A, 0xA8, 0x0F, 0x11, 0x01, 0xFB, 0x0E, 0x03, runaway, 0x7E, 0xA8, 0x0F, 0x01, 0xFC, 0x0E, 0x03, runaway, 0x92, 0xA8, 0x0F, 0x10, 0xFF, ]) shadow_leaving_sub.write(fout) shadow_leaving_sub.bytestring = bytearray([runaway]) shadow_leaving_sub.set_location(0x10FC2F) shadow_leaving_sub.write(fout) shadow_leaving_sub.set_location(0x10FC5D) shadow_leaving_sub.write(fout) esperevents = [ "Ramuh", "Ifrit", "Shiva", "Siren", "Terrato", "Shoat", "Maduin", "Bismark", "Stray", "Palidor", "Tritoch", "Odin", "Raiden", "Bahamut", "Alexandr", "Crusader", "Ragnarok", "Kirin", "ZoneSeek", "Carbunkl", "Phantom", "Sraphim", "Golem", "Unicorn", "Fenrir", "Starlet", "Phoenix" ] esperevents = dict([(n, i) for (i, n) in enumerate(esperevents)]) espers = list(get_espers(sourcefile)) num_espers = 3 for i in range(num_espers): if options_.is_code_active("speedcave"): esperrank = 999 else: esperrank = 0 while random.randint(1, 3) == 3: esperrank += 1 candidates = [e for e in espers if e.rank <= esperrank] esper = random.choice(candidates) espers.remove(esper) event_value = esperevents[esper.name] + 0x36 startsub.bytestring += bytearray([0x86, event_value]) for i in range(27): # espers byte, bit = i // 8, i % 8 mem_addr = ((0x17 + byte) << 3) | bit startsub.bytestring += bytearray([0xD6, mem_addr]) for i in range(16): # characters if i in starting: continue byte, bit = i // 8, i % 8 mem_addr = ((0x1b + byte) << 3) | bit startsub.bytestring += bytearray([0xD6, mem_addr]) startsub.bytestring += bytearray([ 0xB2, 0x09, 0x21, 0x02, # start on airship ]) startsub.bytestring.append(0xFE) startsub.set_location(0xADD1E) startsub.write(fout) startsub0 = Substitution() startsub0.bytestring = bytearray([0xB2, 0x1E, 0xDD, 0x00, 0xFE]) startsub0.set_location(0xC9A4F) startsub0.write(fout) set_airship_sub = Substitution() set_airship_sub.bytestring = bytearray([0xB2, 0xD6, 0x02, 0x00, 0xFE]) set_airship_sub.set_location(0xAF53A) # need first branch for button press set_airship_sub.write(fout) tower_msg_sub = Substitution() tower_msg_sub.bytestring = bytearray([0xD6, 0xE6, 0xD6, 0xE7]) # reset temp chars while len(tower_msg_sub.bytestring) < 12: tower_msg_sub.bytestring.append(0xFD) tower_msg_sub.set_location(0xA03A7) tower_msg_sub.write(fout) from locationrandomizer import NPCBlock, EventBlock falcon = get_location(0xb) save_point = NPCBlock(pointer=None, locid=falcon.locid) attributes = { "graphics": 0x6f, "palette": 6, "x": 20, "y": 8, "show_on_vehicle": False, "speed": 0, "event_addr": 0x5eb3, "facing": 3, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 2, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 1, "vehicle": 0 } for key, value in attributes.items(): setattr(save_point, key, value) save_point.set_id(len(falcon.npcs)) falcon.npcs.append(save_point) save_event = EventBlock(pointer=None, locid=falcon.locid) attributes = {"event_addr": 0x29aeb, "x": 20, "y": 8} for key, value in attributes.items(): setattr(save_event, key, value) falcon.events.append(save_event) partyswitch = NPCBlock(pointer=None, locid=falcon.locid) attributes = { "graphics": 0x17, "palette": 0, "x": 16, "y": 6, "show_on_vehicle": False, "speed": 0, "event_addr": 0x047d, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0, "npcid": 2 } for key, value in attributes.items(): setattr(partyswitch, key, value) falcon.npcs.append(partyswitch) pilot = random.choice([s for s in starting if s < 12]) pilot_sub = Substitution() pilot_sub.bytestring = bytearray([0x3D, pilot, 0x45, 0x3F, pilot, 0x01]) for i in range(14): if i == pilot: continue pilot_sub.bytestring += bytearray([0x3F, i, 0x00]) pilot_sub.set_location(0xC2110) pilot_sub.write(fout) if options_.is_code_active("racecave"): randomize_tower(filename=sourcefile, ancient=True, nummaps=50) elif options_.is_code_active("speedcave"): randomize_tower(filename=sourcefile, ancient=True, nummaps=85) else: randomize_tower(filename=sourcefile, ancient=True, nummaps=300) manage_map_names(fout) unused_enemies = [u for u in get_monsters() if u.id in REPLACE_ENEMIES] def safe_boss_validator(formation): if formation.is_fanatics: return False if set(formation.present_enemies) & set(unused_enemies): return False if formation.formid in NOREPLACE_FORMATIONS: return False if not (any([m.boss_death for m in formation.present_enemies]) or formation.get_music() in [1, 2, 5]): return False if formation.get_music() == 0: return False if formation.formid in [0x1b0, 0x1b3, 0x1d9, 0x1db, 0x1d7]: return False if formation.formid in [ 0x1a4, 0x1d4, 0x1d5, 0x1d6, 0x1e4, 0x1e2, 0x1ff, 0x1bd, 0x1be ]: return False if (options_.is_code_active("racecave") and formation.formid in [0x162, 0x1c8, 0x1d3]): return False return True def challenge_battle_validator(formation): if len(formation.present_enemies) == 0: return False if set(formation.present_enemies) & set(unused_enemies): return False if formation.formid in NOREPLACE_FORMATIONS: return False if formation.battle_event: return False if formation.formid in [ 0x1a4, 0x1ff, 0x1bd, 0x1d7, 0x200, 0x201, 0x23f ]: return False if formation.get_music() == 0: if any([ f for f in formations if f.formid != formation.formid and set(f.enemy_ids) == set(formation.enemy_ids) and f.get_music() != 0 ]): return False best_drop = formation.get_best_drop() if best_drop and (best_drop.price <= 2 or best_drop.price >= 30000 or options_.is_code_active("madworld")): return True return False formations = sorted(get_formations(), key=lambda f: f.rank()) enemy_formations = [ f for f in formations if f.is_fanatics or ( f.present_enemies and not f.has_event and not f.has_boss) ] enemy_formations = [ f for f in enemy_formations if f.formid not in REPLACE_FORMATIONS + NOREPLACE_FORMATIONS ] boss_formations = [f for f in formations if safe_boss_validator(f)] used_formations = [] challenges = sorted( [f for f in formations if challenge_battle_validator(f)], key=lambda f: f.get_best_drop().rank())[-48:] challenges = sorted(random.sample(challenges, 24), key=lambda f: f.rank()) challenges = [f.formid for f in challenges] challenges = { 1: challenges[:6], 2: challenges[6:12], 3: challenges[12:18], 4: challenges[18:24] } ch_bgs = list(range(0x31)) + [0x36, 0x37] waters = [0xD, 0x1F, 0x23] snows = [0x12] ch_bgs = random.sample(ch_bgs, 10) + [random.choice(waters), snows[0]] random.shuffle(ch_bgs) for l in get_locations(): if not hasattr(l, "ancient_rank"): l.entrance_set.entrances = [] l.entrance_set.longentrances = [] l.chests = [] l.attacks = 0 l.write_data(fout) pointer = 0xB4E35 if options_.is_code_active('racecave'): candidates = [c for c in starting if c != runaway] leaders = random.sample(candidates, 3) subptr = pointer - 0xa0000 leader_sub = Substitution() # makes switching impossible and makes row change instant # could freeze the game d+pad and A on same frame tho leader_sub.set_location(0x324b7) leader_sub.bytestring = bytes([0xEA, 0xEA, 0xEA]) leader_sub.write(fout) leader_sub.set_location(0x32473) leader_sub.bytestring = bytes([0xEA, 0xEA]) leader_sub.write(fout) leader_sub.set_location(0xa02da) leader_sub.bytestring = bytes( [0xB2, subptr & 0xFF, (subptr >> 8) & 0xFF, subptr >> 16]) leader_sub.write(fout) leader_sub.set_location(pointer) leader_sub.bytestring = bytearray([]) locked = 0 for i, c in enumerate(leaders): leader_sub.bytestring += bytearray([0x3F, c, i + 1]) locked |= (1 << c) for c in range(16): if c in leaders: continue leader_sub.bytestring += bytearray([0x3F, c, 0x00]) leader_sub.bytestring += bytearray([0x3E, c]) leader_sub.bytestring += bytearray( [0x47, 0xE1, 0xB2, 0x0B, 0xC9, 0x00, 0x45]) for i, c in enumerate(leaders): leader_sub.bytestring += bytearray([0x3F, c, 0]) leader_sub.bytestring += bytearray([0x3F, c, i + 1]) leader_sub.bytestring += bytearray( [0x99, 0x03, locked & 0xFF, locked >> 8]) for i in [14, 15]: byte, bit = i // 8, i % 8 mem_addr = ((0x1b + byte) << 3) | bit leader_sub.bytestring += bytearray([0xD6, mem_addr]) leader_sub.bytestring += bytearray([0x96, 0xFE]) leader_sub.write(fout) pswitch_ptr = pointer - 0xa0000 pointer += len(leader_sub.bytestring) espersubs = {} for esper, event_value in esperevents.items(): byte, bit = event_value // 8, event_value % 8 mem_addr = ((0x17 + byte) << 3) | bit espersub = Substitution() espersub.set_location(pointer) espersub.bytestring = [ 0xF4, 0x8D, 0x86, event_value + 0x36, 0xD7, mem_addr, 0x3E, None, 0xFE ] espersubs[esper] = espersub pointer += espersub.size inn_template = [ 0x4B, None, None, 0x4B, 0x11, 0x81, 0xB6, None, None, None, None, None, None, 0xFE ] inn_template2 = [ 0x85, None, None, 0xC0, 0xBE, 0x81, 0xFF, 0x69, 0x01, 0x31, 0x84, 0xC3, 0x8F, 0x84, 0xFF, 0xF4, 0x2C, 0x73, 0x30, 0x0E, 0x01, 0x02, 0x06, 0x16, 0x31, 0x86, 0xC3, 0x9C, 0x80, 0x8D, 0xCE, 0xFF, 0xB2, 0x67, 0xCF, 0x00, 0xF0, 0xB8, 0xFA, 0x31, 0x85, 0xD5, 0x36, 0x05, 0xCE, 0xFF, 0xB2, 0x96, 0xCF, 0x00, 0xFE ] prices = { 1: (500, 0xA6E), 2: (2000, 0xA71), 3: (8000, 0xA5F), 4: (30000, 0xA64) } if options_.is_code_active("racecave"): partyswitch_template = [ 0x4B, None, None, 0x4B, 0x86, 0x83, 0xB6, None, None, None, None, None, None, 0xFE ] partyswitch_template2 = [ 0x85, None, None, 0xC0, 0xBE, 0x81, 0xFF, 0x69, 0x01, 0xB2, pswitch_ptr & 0xFF, (pswitch_ptr >> 8) & 0xFF, pswitch_ptr >> 16, 0xFE ] save_template = [ 0x4B, None, None, 0x4B, 0x24, 0x85, 0xB6, None, None, None, None, None, None, 0xFE ] save_template2 = [ 0x85, None, None, 0xC0, 0xBE, 0x81, 0xFF, 0x69, 0x01, 0xB2, 0xEB, 0x9A, 0x02, 0xFE ] enemy_template = [ 0x4B, 0x0B, 0x07, 0xB6, None, None, None, None, None, None, 0xA0, None, None, 0xB3, 0x5E, 0x70, 0x4D, None, None, 0xA1, 0x00, 0x96, 0x5C, 0xFE ] 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) shops = get_shops(sourcefile) shopranks = {} itemshops = [s for s in shops if s.shoptype_pretty in ["items", "misc"]] othershops = [s for s in shops if s not in itemshops] othershops = othershops[random.randint(0, len(othershops) // 2):] itemshops = sorted(random.sample(itemshops, 5), key=lambda p: p.rank()) othershops = sorted(random.sample(othershops, 7), key=lambda p: p.rank()) for i in range(1, 5): if i > 1: shopranks[i] = othershops[:2] + itemshops[:1] othershops = othershops[2:] itemshops = itemshops[1:] else: shopranks[i] = othershops[:1] + itemshops[:2] othershops = othershops[1:] itemshops = itemshops[2:] assert len(shopranks[i]) == 3 random.shuffle(shopranks[i]) shopranks[random.randint(1, 4)][random.randint(0, 2)] = None levelmusic = {} dungeonmusics = [23, 24, 33, 35, 55, 71, 40, 41, 75, 77, 78] random.shuffle(dungeonmusics) for i in range(5): levelmusic[i] = dungeonmusics.pop() locations = [l for l in get_locations() if hasattr(l, "ancient_rank")] locations = sorted(locations, key=lambda l: l.ancient_rank) restlocs = [l for l in locations if hasattr(l, "restrank")] ban_musics = [0, 36, 56, 57, 58, 73, 74, 75] + list(levelmusic.values()) restmusics = [m for m in range(1, 85) if m not in ban_musics] random.shuffle(restmusics) optional_chars = [c for c in characters if hasattr(c, "slotid")] optional_chars = [ c for c in optional_chars if c.slotid == runaway or (c.id not in starting and c.id in charcands) ] if options_.is_code_active("speedcave"): while len(optional_chars) < 24: if random.choice([True, True, False]): supplement = [ c for c in optional_chars if c.id >= 14 or c.slotid == runaway ] else: supplement = list(optional_chars) supplement = sorted(set(supplement), key=lambda c: c.id) optional_chars.append(random.choice(supplement)) random.shuffle(optional_chars) ptr = pointer - 0xA0000 c0, b0, a0 = ptr & 0xFF, (ptr >> 8) & 0xFF, ptr >> 16 ptr = (pointer + 10) - 0xA0000 c1, b1, a1 = ptr & 0xFF, (ptr >> 8) & 0xFF, ptr >> 16 ptr = (pointer + 20) - 0xA0000 c2, b2, a2 = ptr & 0xFF, (ptr >> 8) & 0xFF, ptr >> 16 num_in_party_sub = Substitution() num_in_party_sub.set_location(0xAC654) num_in_party_sub.bytestring = [0xB2, c0, b0, a0] num_in_party_sub.write(fout) num_in_party_sub.set_location(pointer) num_in_party_sub.bytestring = bytes([ 0xC0, 0xAE, 0x01, c1, b1, a1, 0xB2, 0x80, 0xC6, 0x00, 0xC0, 0xAF, 0x01, c2, b2, a2, 0xB2, 0x80, 0xC6, 0x00, 0xD3, 0xA3, 0xD3, 0xA2, 0xFE ]) num_in_party_sub.write(fout) pointer += len(num_in_party_sub.bytestring) ally_addrs = {} for chosen in set(optional_chars): byte, bit = chosen.slotid // 8, chosen.slotid % 8 mem_addr = ((0x1b + byte) << 3) | bit allysub = Substitution() for party_id in range(1, 4): for npc_id in range(4, 6): allysub.set_location(pointer) allysub.bytestring = [ 0xB2, 0xC1, 0xC5, 0x00, # set caseword 0xC0, 0xA3, 0x81, None, None, None ] allysub.bytestring += [ 0xD4, 0xF0 | chosen.slotid, 0xD4, 0xE0 | chosen.slotid, 0xD7, mem_addr ] if chosen.id >= 14 or options_.is_code_active("speedcave"): allysub.bytestring += [ 0x77, chosen.slotid, 0x8b, chosen.slotid, 0x7F, 0x8c, chosen.slotid, 0x7F, 0x88, chosen.slotid, 0x00, 0x00 ] allysub.bytestring += [ 0x3E, 0x10 | npc_id, 0x3D, chosen.slotid, 0x3F, chosen.slotid, party_id, 0x47, 0x45, 0xF4, 0xD0, 0xFE ] pointer = pointer + len(allysub.bytestring) uptr = (pointer - 1) - 0xa0000 a, b, c = (uptr >> 16, (uptr >> 8) & 0xFF, uptr & 0xFF) allysub.bytestring[7:10] = [c, b, a] allysub.write(fout) event_addr = (allysub.location - 0xa0000) & 0x3FFFF ally_addrs[chosen.id, party_id, npc_id] = event_addr npc_palettes = get_npc_palettes() for g in npc_palettes: npc_palettes[g] = [v for v in npc_palettes[g] if 0 <= v <= 5] for g in range(14, 63): if g not in npc_palettes or not npc_palettes[g]: npc_palettes[g] = list(range(6)) def make_paysub(template, template2, loc, ptr): sub = Substitution() sub.set_location(ptr) price, message = prices[loc.restrank] message |= 0x8000 sub.bytestring = list(template) ptr += len(template) price = [price & 0xFF, price >> 8] message = [message & 0xFF, message >> 8] p = (ptr - 0xA0000) & 0x3FFFF p2 = p - 1 ptrbytes = [p & 0xFF, (p >> 8) & 0xFF, p >> 16] ptrbytes2 = [p2 & 0xFF, (p2 >> 8) & 0xFF, p2 >> 16] mapid = [loc.locid & 0xFF, loc.locid >> 8] mapid[1] |= 0x23 sub.bytestring[1:3] = message sub.bytestring[7:10] = ptrbytes sub.bytestring[10:13] = ptrbytes2 assert None not in sub.bytestring assert len(sub.bytestring) == 14 sub.bytestring += template2 ptr += len(template2) sub.bytestring[15:17] = price assert None not in sub.bytestring sub.bytestring = bytes(sub.bytestring) sub.write(fout) return sub random.shuffle(restlocs) for l in restlocs: assert l.ancient_rank == 0 l.music = restmusics.pop() l.make_warpable() innsub = make_paysub(inn_template, inn_template2, l, pointer) pointer += innsub.size savesub = make_paysub(save_template, save_template2, l, pointer) pointer += savesub.size if options_.is_code_active('racecave'): pswitch_sub = make_paysub(partyswitch_template, partyswitch_template2, l, pointer) pointer += pswitch_sub.size event_addr = (innsub.location - 0xa0000) & 0x3FFFF innkeeper = NPCBlock(pointer=None, locid=l.locid) graphics = random.randint(14, 62) palette = random.choice(npc_palettes[graphics]) attributes = { "graphics": graphics, "palette": palette, "x": 52, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(innkeeper, key, value) l.npcs.append(innkeeper) unequipper = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x1e, "palette": 3, "x": 49, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": 0x23510, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(unequipper, key, value) l.npcs.append(unequipper) event_addr = (savesub.location - 0xa0000) & 0x3FFFF pay_to_save = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x6f, "palette": 6, "x": 47, "y": 4, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 3, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 2, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(pay_to_save, key, value) l.npcs.append(pay_to_save) if l.restrank == 4: final_loc = get_location(412) if len(final_loc.npcs) < 2: final_save = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x6f, "palette": 6, "x": 82, "y": 43, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 3, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 2, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0, "npcid": 1 } for key, value in attributes.items(): setattr(final_save, key, value) final_loc.npcs.append(final_save) shop = shopranks[l.restrank].pop() if shop is not None: shopsub = Substitution() shopsub.set_location(pointer) shopsub.bytestring = bytes([0x9B, shop.shopid, 0xFE]) shopsub.write(fout) pointer += len(shopsub.bytestring) event_addr = (shopsub.location - 0xa0000) & 0x3FFFF else: event_addr = 0x178cb colsub = Substitution() colsub.set_location(0xb78ea) colsub.bytestring = bytes([0x59, 0x04, 0x5C, 0xFE]) colsub.write(fout) shopkeeper = NPCBlock(pointer=None, locid=l.locid) graphics = random.randint(14, 62) palette = random.choice(npc_palettes[graphics]) attributes = { "graphics": graphics, "palette": palette, "x": 39, "y": 11, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 1, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(shopkeeper, key, value) l.npcs.append(shopkeeper) if optional_chars: chosen = optional_chars.pop() assert chosen.palette is not None if chosen.id >= 14 and False: byte, bit = 0, 0 else: byte, bit = (chosen.slotid // 8) + 0x1b, chosen.slotid % 8 event_addr = ally_addrs[chosen.id, l.party_id, len(l.npcs)] ally = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": chargraphics[chosen.id], "palette": chosen.palette, "x": 54, "y": 18, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": byte, "membit": bit, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(ally, key, value) l.npcs.append(ally) if (len(optional_chars) == 12 or (len(optional_chars) > 0 and options_.is_code_active('speedcave'))): temp = optional_chars.pop() if chosen.id != temp.id: chosen = temp if chosen.id >= 14 and False: byte, bit = 0, 0 else: byte, bit = (chosen.slotid // 8) + 0x1b, chosen.slotid % 8 event_addr = ally_addrs[chosen.id, l.party_id, len(l.npcs)] attributes = { "graphics": chargraphics[chosen.id], "palette": chosen.palette, "x": 53, "y": 18, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": byte, "membit": bit, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } ally = NPCBlock(pointer=None, locid=l.locid) for key, value in attributes.items(): setattr(ally, key, value) l.npcs.append(ally) if l.restrank == 1: num_espers = 3 elif l.restrank in [2, 3]: num_espers = 2 elif l.restrank == 4: num_espers = 1 for i in range(num_espers): if len(espers) == 0: break if options_.is_code_active('speedcave'): candidates = espers else: esperrank = l.restrank if random.randint(1, 7) == 7: esperrank += 1 candidates = [] while not candidates: candidates = [e for e in espers if e.rank == esperrank] if not candidates or random.randint(1, 3) == 3: candidates = [e for e in espers if e.rank <= esperrank] if not candidates: esperrank += 1 esper = random.choice(candidates) espers.remove(esper) espersub = espersubs[esper.name] index = espersub.bytestring.index(None) espersub.bytestring[index] = 0x10 | len(l.npcs) espersub.write(fout) event_addr = (espersub.location - 0xa0000) & 0x3FFFF event_value = esperevents[esper.name] byte, bit = event_value // 8, event_value % 8 magicite = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x5B, "palette": 2, "x": 44 + i, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 0, "no_turn_when_speaking": True, "layer_priority": 2, "special_anim": 2, "memaddr": byte + 0x17, "membit": bit, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(magicite, key, value) l.npcs.append(magicite) event_addr = pointer - 0xa0000 pointer = make_challenge_event(l, pointer) enemy = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x3e, "palette": 2, "x": 42, "y": 6, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(enemy, key, value) l.npcs.append(enemy) if options_.is_code_active('racecave'): event_addr = (pswitch_sub.location - 0xa0000) & 0x3FFFF partyswitch = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x17, "palette": 0, "x": 55, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(partyswitch, key, value) l.npcs.append(partyswitch) assert len(optional_chars) == 0 if pointer >= 0xb6965: raise Exception("Cave events out of bounds. %x" % pointer) # lower encounter rate dungeon_rates = [ 0x38, 0, 0x20, 0, 0xb0, 0, 0x00, 1, 0x1c, 0, 0x10, 0, 0x58, 0, 0x80, 0 ] + ([0] * 16) assert len(dungeon_rates) == 32 encrate_sub = Substitution() encrate_sub.set_location(0xC2BF) encrate_sub.bytestring = bytes(dungeon_rates) encrate_sub.write(fout) maxrank = max(locations, key=lambda l: l.ancient_rank).ancient_rank for l in locations: if l not in restlocs and (l.npcs or l.events): for n in l.npcs: if n == final_save: continue if n.graphics == 0x6F: n.memaddr, n.membit, n.event_addr = 0x73, 1, 0x5EB3 success = False for e in l.events: if e.x % 128 == n.x % 128 and e.y % 128 == n.y % 128: if success: raise Exception("Duplicate events found.") e.event_addr = 0x5EB3 success = True if not success: raise Exception("No corresponding event found.") for e in l.entrances: e.dest |= 0x800 rank = l.ancient_rank l.name_id = min(rank, 0xFF) if not hasattr(l, "restrank"): if hasattr(l, "secret_treasure") and l.secret_treasure: pass elif l.locid == 334 or not hasattr(l, "routerank"): l.music = 58 elif l.routerank in levelmusic: l.music = levelmusic[l.routerank] else: raise Exception l.setid = rank if rank == 0: l.attacks = 0 elif rank > 0xFF: l.setid = random.randint(0xF0, 0xFF) else: 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 low = enrank(rank - 3) high = enrank(rank + 2) high = int(round(high * len(enemy_formations))) low = int(round(low * len(enemy_formations))) while high - low < 4: high = min(high + 1, len(enemy_formations)) low = max(low - 1, 0) candidates = enemy_formations[low:high] chosen_enemies = random.sample(candidates, 4) chosen_enemies = sorted(chosen_enemies, key=lambda f: f.rank()) if options_.is_code_active('racecave'): bossify = False elif rank >= maxrank * 0.9: bossify = True else: if options_.is_code_active('speedcave'): thresh = 0.5 else: thresh = 0.1 bossify = rank >= random.randint(int(maxrank * thresh), int(maxrank * 0.9)) bossify = bossify and random.randint(1, 3) == 3 if bossify: formrank = chosen_enemies[0].rank() candidates = [ c for c in boss_formations if c.rank() >= formrank ] if candidates: if rank < maxrank * 0.75: candidates = candidates[:random.randint(2, 4)] chosen_boss = random.choice(candidates) chosen_enemies[3] = chosen_boss if options_.is_code_active('speedcave'): thresh, bossthresh = 2, 1 else: # allow up to three of the same formation thresh, bossthresh = 3, 2 for c in chosen_enemies: used_formations.append(c) if used_formations.count(c) >= bossthresh: if c in boss_formations: boss_formations.remove(c) if used_formations.count(c) >= thresh: if c in enemy_formations: enemy_formations.remove(c) fset = get_fset(rank) fset.formids = [f.formid for f in chosen_enemies] for formation in fset.formations: if formation.get_music() == 0: formation.set_music(6) formation.set_continuous_music() formation.write_data(fout) fset.write_data(fout) if not (hasattr(l, "secret_treasure") and l.secret_treasure): if options_.is_code_active('speedcave') or rank == 0: low = random.randint(0, 400) high = random.randint(low, low * 5) high = random.randint(low, high) else: low = rank * 2 high = low * 1.5 while random.choice([True, False, False]): high = high * 1.5 if rank < maxrank * 0.4: monster = False else: monster = None if 0 < rank < maxrank * 0.75: enemy_limit = sorted([f.rank() for f in fset.formations])[-2] enemy_limit *= 1.5 else: enemy_limit = None l.unlock_chests(int(low), int(high), monster=monster, guarantee_miab_treasure=True, enemy_limit=enemy_limit, uncapped_monsters=options_.is_code_active('bsiab')) l.write_data(fout) final_cut = Substitution() final_cut.set_location(0xA057D) final_cut.bytestring = bytearray([ 0x3F, 0x0E, 0x00, 0x3F, 0x0F, 0x00, ]) if not options_.is_code_active("racecave"): final_cut.bytestring += bytearray( [0x9D, 0x4D, 0x65, 0x33, 0xB2, 0xA9, 0x5E, 0x00]) else: for i in range(16): final_cut.bytestring += bytearray([0x3F, i, 0x00]) locked = 0 protected = random.sample(starting, 4) assignments = {0: [], 1: [], 2: [], 3: []} for i, c in enumerate(protected): if 1 <= i <= 3 and random.choice([True, False]): assignments[i].append(c) chars = list(range(16)) random.shuffle(chars) for c in chars: if c in protected: continue if c >= 14 and random.choice([True, False]): continue if random.choice([True, True, False]): i = random.randint(0, 3) if len(assignments[i]) >= 3: continue elif len(assignments[i]) == 2 and random.choice([True, False]): continue assignments[i].append(c) for key in assignments: for c in assignments[key]: locked |= (1 << c) if key > 0: final_cut.bytestring += bytearray([0x3F, c, key]) final_cut.bytestring += bytearray( [0x99, 0x03, locked & 0xFF, locked >> 8]) from chestrandomizer import get_2pack event_bosses = { 1: [0xC18A4, 0xC184B], 2: [0xC16DD, 0xC171D, 0xC1756], 3: [None, None, None] } fout.seek(0xA0F6F) fout.write(bytes([0x36])) candidates = sorted(boss_formations, key=lambda b: b.rank()) candidates = [c for c in candidates if c.inescapable] candidates = candidates[random.randint(0, len(candidates) - 16):] chosens = random.sample(candidates, 8) chosens = sorted(chosens, key=lambda b: b.rank()) for rank in sorted(event_bosses): num = len(event_bosses[rank]) rankchosens, chosens = chosens[:num], chosens[num:] assert len(rankchosens) == num random.shuffle(rankchosens) if rank == 3: bgs = random.sample([ 0x07, 0x0D, 0x17, 0x18, 0x19, 0x1C, 0x1F, 0x21, 0x22, 0x23, 0x29, 0x2C, 0x30, 0x36, 0x37 ], 3) for i, (address, chosen) in enumerate(zip(event_bosses[rank], rankchosens)): if rank == 3: chosen.set_music(5) elif rank == 2: chosen.set_music(2) else: chosen.set_music(4) form_music_overrides[chosen.formid] = chosen.get_music() chosen.set_appearing([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]) fset = get_2pack(chosen) if address is not None: fout.seek(address) fout.write(bytes([fset.setid & 0xFF])) else: bg = bgs.pop() final_cut.bytestring += bytearray([ 0x46, i + 1, 0x4D, fset.setid & 0xFF, bg, 0xB2, 0xA9, 0x5E, 0x00 ]) assert len(chosens) == 0 final_cut.bytestring += bytearray([0xB2, 0x64, 0x13, 0x00]) final_cut.write(fout)