Esempio n. 1
0
    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
Esempio n. 3
0
    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
Esempio n. 5
0
    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 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
Esempio n. 7
0
    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
Esempio n. 15
0
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)
Esempio n. 16
0
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
Esempio n. 18
0
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()
Esempio n. 19
0
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)
Esempio n. 21
0
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
Esempio n. 22
0
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
Esempio n. 23
0
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
Esempio n. 24
0
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
Esempio n. 25
0
    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()
Esempio n. 27
0
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
Esempio n. 28
0
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
Esempio n. 29
0
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
Esempio n. 30
0
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)
Esempio n. 31
0
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