mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-06-15 08:18:13 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e92bd65f08 | |||
| 3c7331b206 | |||
| 71475230ba |
@@ -1,154 +0,0 @@
|
||||
name: Build and Publish Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "**"
|
||||
- "!docs/**"
|
||||
- "!deploy/**"
|
||||
- "!setup.py"
|
||||
- "!.gitignore"
|
||||
- "!.github/workflows/**"
|
||||
- ".github/workflows/docker.yml"
|
||||
branches:
|
||||
- "*"
|
||||
tags:
|
||||
- "v?[0-9]+.[0-9]+.[0-9]*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image-name: ${{ steps.image.outputs.name }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
package-name: ${{ steps.package.outputs.name }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set lowercase image name
|
||||
id: image
|
||||
run: |
|
||||
echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set package name
|
||||
id: package
|
||||
run: |
|
||||
echo "name=$(basename ${GITHUB_REPOSITORY,,})" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
|
||||
tags: |
|
||||
type=ref,event=branch,enable={{is_not_default_branch}}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=nightly,enable={{is_default_branch}}
|
||||
|
||||
- name: Compute final tags
|
||||
id: final-tags
|
||||
run: |
|
||||
readarray -t tags <<< "${{ steps.meta.outputs.tags }}"
|
||||
|
||||
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
tag="${{ github.ref_name }}"
|
||||
if [[ "$tag" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
full_latest="${{ env.REGISTRY }}/${{ steps.image.outputs.name }}:latest"
|
||||
# Check if latest is already in tags to avoid duplicates
|
||||
if ! printf '%s\n' "${tags[@]}" | grep -q "^$full_latest$"; then
|
||||
tags+=("$full_latest")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set multiline output
|
||||
echo "tags<<EOF" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "${tags[@]}" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: prepare
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: amd64
|
||||
runner: ubuntu-latest
|
||||
suffix: amd64
|
||||
cache-scope: amd64
|
||||
- platform: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
suffix: arm64
|
||||
cache-scope: arm64
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Compute suffixed tags
|
||||
id: tags
|
||||
run: |
|
||||
readarray -t tags <<< "${{ needs.prepare.outputs.tags }}"
|
||||
suffixed=()
|
||||
for t in "${tags[@]}"; do
|
||||
suffixed+=("$t-${{ matrix.suffix }}")
|
||||
done
|
||||
echo "tags=$(IFS=','; echo "${suffixed[*]}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/${{ matrix.platform }}
|
||||
push: true
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
labels: ${{ needs.prepare.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ matrix.cache-scope }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.cache-scope }}
|
||||
provenance: false
|
||||
|
||||
manifest:
|
||||
needs: [prepare, build]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push multi-arch manifest
|
||||
run: |
|
||||
readarray -t tag_array <<< "${{ needs.prepare.outputs.tags }}"
|
||||
|
||||
for tag in "${tag_array[@]}"; do
|
||||
docker manifest create "$tag" \
|
||||
"$tag-amd64" \
|
||||
"$tag-arm64"
|
||||
|
||||
docker manifest push "$tag"
|
||||
done
|
||||
@@ -12,6 +12,7 @@ env:
|
||||
jobs:
|
||||
labeler:
|
||||
name: 'Apply content-based labels'
|
||||
if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
+4
-6
@@ -261,7 +261,6 @@ class MultiWorld():
|
||||
"local_items": set(item_link.get("local_items", [])),
|
||||
"non_local_items": set(item_link.get("non_local_items", [])),
|
||||
"link_replacement": replacement_prio.index(item_link["link_replacement"]),
|
||||
"skip_if_solo": item_link.get("skip_if_solo", False),
|
||||
}
|
||||
|
||||
for _name, item_link in item_links.items():
|
||||
@@ -285,8 +284,6 @@ class MultiWorld():
|
||||
|
||||
for group_name, item_link in item_links.items():
|
||||
game = item_link["game"]
|
||||
if item_link["skip_if_solo"] and len(item_link["players"]) == 1:
|
||||
continue
|
||||
group_id, group = self.add_group(group_name, game, set(item_link["players"]))
|
||||
|
||||
group["item_pool"] = item_link["item_pool"]
|
||||
@@ -1722,9 +1719,10 @@ class Spoiler:
|
||||
logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (
|
||||
location.item.name, location.item.player, location.name, location.player) for location in
|
||||
sphere_candidates])
|
||||
if any([multiworld.worlds[location.item.player].options.accessibility != 'minimal' for location in sphere_candidates]):
|
||||
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
|
||||
f'Something went terribly wrong here.')
|
||||
if not multiworld.has_beaten_game(state):
|
||||
raise RuntimeError("During playthrough generation, the game was determined to be unbeatable. "
|
||||
"Something went terribly wrong here. "
|
||||
f"Unreachable progression items: {sphere_candidates}")
|
||||
else:
|
||||
self.unreachables = sphere_candidates
|
||||
break
|
||||
|
||||
+1
-16
@@ -486,22 +486,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
||||
if required_plando_options:
|
||||
raise Exception(f"Settings reports required plando module {str(required_plando_options)}, "
|
||||
f"which is not enabled.")
|
||||
games = requirements.get("game", {})
|
||||
for game, version in games.items():
|
||||
if game not in AutoWorldRegister.world_types:
|
||||
continue
|
||||
if not version:
|
||||
raise Exception(f"Invalid version for game {game}: {version}.")
|
||||
if isinstance(version, str):
|
||||
version = {"min": version}
|
||||
if "min" in version and tuplize_version(version["min"]) > AutoWorldRegister.world_types[game].world_version:
|
||||
raise Exception(f"Settings reports required version of world \"{game}\" is at least {version['min']}, "
|
||||
f"however world is of version "
|
||||
f"{AutoWorldRegister.world_types[game].world_version.as_simple_string()}.")
|
||||
if "max" in version and tuplize_version(version["max"]) < AutoWorldRegister.world_types[game].world_version:
|
||||
raise Exception(f"Settings reports required version of world \"{game}\" is no later than {version['max']}, "
|
||||
f"however world is of version "
|
||||
f"{AutoWorldRegister.world_types[game].world_version.as_simple_string()}.")
|
||||
|
||||
ret = argparse.Namespace()
|
||||
for option_key in Options.PerGameCommonOptions.type_hints:
|
||||
if option_key in weights and option_key not in Options.CommonOptions.type_hints:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
if __name__ == '__main__':
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
import Utils
|
||||
Utils.init_logging("KH1Client", exception_logger="Client")
|
||||
|
||||
from worlds.kh1.Client import launch
|
||||
launch()
|
||||
@@ -0,0 +1,8 @@
|
||||
import ModuleUpdate
|
||||
import Utils
|
||||
from worlds.kh2.Client import launch
|
||||
ModuleUpdate.update()
|
||||
|
||||
if __name__ == '__main__':
|
||||
Utils.init_logging("KH2Client", exception_logger="Client")
|
||||
launch()
|
||||
@@ -412,10 +412,10 @@ class LinksAwakeningClient():
|
||||
status = (await self.gameboy.async_read_memory_safe(LAClientConstants.wLinkStatusBits))[0]
|
||||
|
||||
item_id -= LABaseID
|
||||
# The player name table only goes up to 101, so don't go past that
|
||||
# The player name table only goes up to 100, so don't go past that
|
||||
# Even if it didn't, the remote player _index_ byte is just a byte, so 255 max
|
||||
if from_player > 101:
|
||||
from_player = 101
|
||||
if from_player > 100:
|
||||
from_player = 100
|
||||
|
||||
next_index += 1
|
||||
self.gameboy.write_memory(LAClientConstants.wLinkGiveItem, [
|
||||
|
||||
@@ -59,9 +59,7 @@ def main(args, seed=None, baked_server_options: dict[str, object] | None = None)
|
||||
|
||||
for name, cls in AutoWorld.AutoWorldRegister.world_types.items():
|
||||
if not cls.hidden and len(cls.item_names) > 0:
|
||||
logger.info(f" {name:{longest_name}}: "
|
||||
f"v{cls.world_version.as_simple_string()} |"
|
||||
f"Items: {len(cls.item_names):{item_count}} | "
|
||||
logger.info(f" {name:{longest_name}}: Items: {len(cls.item_names):{item_count}} | "
|
||||
f"Locations: {len(cls.location_names):{location_count}}")
|
||||
|
||||
del item_count, location_count
|
||||
|
||||
+50
-69
@@ -1135,13 +1135,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
|
||||
ctx.save()
|
||||
|
||||
|
||||
def collect_hints(ctx: Context, team: int, slot: int, item: typing.Union[int, str],
|
||||
status: HintStatus | None = None) -> typing.List[Hint]:
|
||||
"""
|
||||
Collect a new hint for a given item id or name, with a given status.
|
||||
If status is None (which is the default value), an automatic status will be determined from the item's quality.
|
||||
"""
|
||||
|
||||
def collect_hints(ctx: Context, team: int, slot: int, item: typing.Union[int, str], auto_status: HintStatus) \
|
||||
-> typing.List[Hint]:
|
||||
hints = []
|
||||
slots: typing.Set[int] = {slot}
|
||||
for group_id, group in ctx.groups.items():
|
||||
@@ -1157,38 +1152,25 @@ def collect_hints(ctx: Context, team: int, slot: int, item: typing.Union[int, st
|
||||
else:
|
||||
found = location_id in ctx.location_checks[team, finding_player]
|
||||
entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "")
|
||||
|
||||
new_status = auto_status
|
||||
if found:
|
||||
status = HintStatus.HINT_FOUND
|
||||
elif status is None:
|
||||
if item_flags & ItemClassification.trap:
|
||||
status = HintStatus.HINT_AVOID
|
||||
else:
|
||||
status = HintStatus.HINT_PRIORITY
|
||||
|
||||
hints.append(
|
||||
Hint(receiving_player, finding_player, location_id, item_id, found, entrance, item_flags, status)
|
||||
)
|
||||
new_status = HintStatus.HINT_FOUND
|
||||
elif item_flags & ItemClassification.trap:
|
||||
new_status = HintStatus.HINT_AVOID
|
||||
hints.append(Hint(receiving_player, finding_player, location_id, item_id, found, entrance,
|
||||
item_flags, new_status))
|
||||
|
||||
return hints
|
||||
|
||||
|
||||
def collect_hint_location_name(ctx: Context, team: int, slot: int, location: str,
|
||||
status: HintStatus | None = HintStatus.HINT_UNSPECIFIED) -> typing.List[Hint]:
|
||||
"""
|
||||
Collect a new hint for a given location name, with a given status (defaults to "unspecified").
|
||||
If None is passed for the status, then an automatic status will be determined from the item's quality.
|
||||
"""
|
||||
def collect_hint_location_name(ctx: Context, team: int, slot: int, location: str, auto_status: HintStatus) \
|
||||
-> typing.List[Hint]:
|
||||
seeked_location: int = ctx.location_names_for_game(ctx.games[slot])[location]
|
||||
return collect_hint_location_id(ctx, team, slot, seeked_location, status)
|
||||
return collect_hint_location_id(ctx, team, slot, seeked_location, auto_status)
|
||||
|
||||
|
||||
def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location: int,
|
||||
status: HintStatus | None = HintStatus.HINT_UNSPECIFIED) -> typing.List[Hint]:
|
||||
"""
|
||||
Collect a new hint for a given location id, with a given status (defaults to "unspecified").
|
||||
If None is passed for the status, then an automatic status will be determined from the item's quality.
|
||||
"""
|
||||
def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location: int, auto_status: HintStatus) \
|
||||
-> typing.List[Hint]:
|
||||
prev_hint = ctx.get_hint(team, slot, seeked_location)
|
||||
if prev_hint:
|
||||
return [prev_hint]
|
||||
@@ -1198,16 +1180,13 @@ def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location
|
||||
|
||||
found = seeked_location in ctx.location_checks[team, slot]
|
||||
entrance = ctx.er_hint_data.get(slot, {}).get(seeked_location, "")
|
||||
|
||||
new_status = auto_status
|
||||
if found:
|
||||
status = HintStatus.HINT_FOUND
|
||||
elif status is None:
|
||||
if item_flags & ItemClassification.trap:
|
||||
status = HintStatus.HINT_AVOID
|
||||
else:
|
||||
status = HintStatus.HINT_PRIORITY
|
||||
|
||||
return [Hint(receiving_player, slot, seeked_location, item_id, found, entrance, item_flags, status)]
|
||||
new_status = HintStatus.HINT_FOUND
|
||||
elif item_flags & ItemClassification.trap:
|
||||
new_status = HintStatus.HINT_AVOID
|
||||
return [Hint(receiving_player, slot, seeked_location, item_id, found, entrance, item_flags,
|
||||
new_status)]
|
||||
return []
|
||||
|
||||
|
||||
@@ -1321,8 +1300,7 @@ class CommandProcessor(metaclass=CommandMeta):
|
||||
argname += "=" + parameter.default
|
||||
argtext += argname
|
||||
argtext += " "
|
||||
doctext = '\n '.join(inspect.getdoc(method).split('\n'))
|
||||
s += f"{self.marker}{command} {argtext}\n {doctext}\n"
|
||||
s += f"{self.marker}{command} {argtext}\n {method.__doc__}\n"
|
||||
return s
|
||||
|
||||
def _cmd_help(self):
|
||||
@@ -1351,6 +1329,19 @@ class CommandProcessor(metaclass=CommandMeta):
|
||||
class CommonCommandProcessor(CommandProcessor):
|
||||
ctx: Context
|
||||
|
||||
def _cmd_countdown(self, seconds: str = "10") -> bool:
|
||||
"""Start a countdown in seconds"""
|
||||
try:
|
||||
timer = int(seconds, 10)
|
||||
except ValueError:
|
||||
timer = 10
|
||||
else:
|
||||
if timer > 60 * 60:
|
||||
raise ValueError(f"{timer} is invalid. Maximum is 1 hour.")
|
||||
|
||||
async_start(countdown(self.ctx, timer))
|
||||
return True
|
||||
|
||||
def _cmd_options(self):
|
||||
"""List all current options. Warning: lists password."""
|
||||
self.output("Current options:")
|
||||
@@ -1619,6 +1610,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
def get_hints(self, input_text: str, for_location: bool = False) -> bool:
|
||||
points_available = get_client_points(self.ctx, self.client)
|
||||
cost = self.ctx.get_hint_cost(self.client.slot)
|
||||
auto_status = HintStatus.HINT_UNSPECIFIED if for_location else HintStatus.HINT_PRIORITY
|
||||
if not input_text:
|
||||
hints = {hint.re_check(self.ctx, self.client.team) for hint in
|
||||
self.ctx.hints[self.client.team, self.client.slot]}
|
||||
@@ -1644,9 +1636,9 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
|
||||
hints = []
|
||||
elif not for_location:
|
||||
hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_id)
|
||||
hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_id, auto_status)
|
||||
else:
|
||||
hints = collect_hint_location_id(self.ctx, self.client.team, self.client.slot, hint_id)
|
||||
hints = collect_hint_location_id(self.ctx, self.client.team, self.client.slot, hint_id, auto_status)
|
||||
|
||||
else:
|
||||
game = self.ctx.games[self.client.slot]
|
||||
@@ -1666,18 +1658,16 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
hints = []
|
||||
for item_name in self.ctx.item_name_groups[game][hint_name]:
|
||||
if item_name in self.ctx.item_names_for_game(game): # ensure item has an ID
|
||||
hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item_name))
|
||||
hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item_name, auto_status))
|
||||
elif not for_location and hint_name in self.ctx.item_names_for_game(game): # item name
|
||||
hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name)
|
||||
hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name, auto_status)
|
||||
elif hint_name in self.ctx.location_name_groups[game]: # location group name
|
||||
hints = []
|
||||
for loc_name in self.ctx.location_name_groups[game][hint_name]:
|
||||
if loc_name in self.ctx.location_names_for_game(game):
|
||||
hints.extend(
|
||||
collect_hint_location_name(self.ctx, self.client.team, self.client.slot, loc_name)
|
||||
)
|
||||
hints.extend(collect_hint_location_name(self.ctx, self.client.team, self.client.slot, loc_name, auto_status))
|
||||
else: # location name
|
||||
hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name)
|
||||
hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name, auto_status)
|
||||
|
||||
else:
|
||||
self.output(response)
|
||||
@@ -1955,7 +1945,8 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
||||
|
||||
target_item, target_player, flags = ctx.locations[client.slot][location]
|
||||
if create_as_hint:
|
||||
hints.extend(collect_hint_location_id(ctx, client.team, client.slot, location))
|
||||
hints.extend(collect_hint_location_id(ctx, client.team, client.slot, location,
|
||||
HintStatus.HINT_UNSPECIFIED))
|
||||
locs.append(NetworkItem(target_item, location, target_player, flags))
|
||||
ctx.notify_hints(client.team, hints, only_new=create_as_hint == 2, persist_even_if_found=True)
|
||||
if locs and create_as_hint:
|
||||
@@ -2247,19 +2238,6 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
self.output(f"Could not find player {player_name} to collect")
|
||||
return False
|
||||
|
||||
def _cmd_countdown(self, seconds: str = "10") -> bool:
|
||||
"""Start a countdown in seconds"""
|
||||
try:
|
||||
timer = int(seconds, 10)
|
||||
except ValueError:
|
||||
timer = 10
|
||||
else:
|
||||
if timer > 60 * 60:
|
||||
raise ValueError(f"{timer} is invalid. Maximum is 1 hour.")
|
||||
|
||||
async_start(countdown(self.ctx, timer))
|
||||
return True
|
||||
|
||||
@mark_raw
|
||||
def _cmd_release(self, player_name: str) -> bool:
|
||||
"""Send out the remaining items from a player to their intended recipients."""
|
||||
@@ -2381,9 +2359,9 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
hints = []
|
||||
for item_name_from_group in self.ctx.item_name_groups[game][item]:
|
||||
if item_name_from_group in self.ctx.item_names_for_game(game): # ensure item has an ID
|
||||
hints.extend(collect_hints(self.ctx, team, slot, item_name_from_group))
|
||||
hints.extend(collect_hints(self.ctx, team, slot, item_name_from_group, HintStatus.HINT_PRIORITY))
|
||||
else: # item name or id
|
||||
hints = collect_hints(self.ctx, team, slot, item)
|
||||
hints = collect_hints(self.ctx, team, slot, item, HintStatus.HINT_PRIORITY)
|
||||
|
||||
if hints:
|
||||
self.ctx.notify_hints(team, hints)
|
||||
@@ -2417,14 +2395,17 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
|
||||
if usable:
|
||||
if isinstance(location, int):
|
||||
hints = collect_hint_location_id(self.ctx, team, slot, location)
|
||||
hints = collect_hint_location_id(self.ctx, team, slot, location,
|
||||
HintStatus.HINT_UNSPECIFIED)
|
||||
elif game in self.ctx.location_name_groups and location in self.ctx.location_name_groups[game]:
|
||||
hints = []
|
||||
for loc_name_from_group in self.ctx.location_name_groups[game][location]:
|
||||
if loc_name_from_group in self.ctx.location_names_for_game(game):
|
||||
hints.extend(collect_hint_location_name(self.ctx, team, slot, loc_name_from_group))
|
||||
hints.extend(collect_hint_location_name(self.ctx, team, slot, loc_name_from_group,
|
||||
HintStatus.HINT_UNSPECIFIED))
|
||||
else:
|
||||
hints = collect_hint_location_name(self.ctx, team, slot, location)
|
||||
hints = collect_hint_location_name(self.ctx, team, slot, location,
|
||||
HintStatus.HINT_UNSPECIFIED)
|
||||
if hints:
|
||||
self.ctx.notify_hints(team, hints)
|
||||
else:
|
||||
|
||||
+2
-6
@@ -1446,7 +1446,6 @@ class ItemLinks(OptionList):
|
||||
Optional("local_items"): [And(str, len)],
|
||||
Optional("non_local_items"): [And(str, len)],
|
||||
Optional("link_replacement"): Or(None, bool),
|
||||
Optional("skip_if_solo"): Or(None, bool),
|
||||
}
|
||||
])
|
||||
|
||||
@@ -1710,7 +1709,7 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
from jinja2 import Template
|
||||
|
||||
from worlds import AutoWorldRegister
|
||||
from Utils import local_path, __version__, tuplize_version
|
||||
from Utils import local_path, __version__
|
||||
|
||||
full_path: str
|
||||
|
||||
@@ -1753,10 +1752,7 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
res = template.render(
|
||||
option_groups=option_groups,
|
||||
__version__=__version__,
|
||||
game=game_name,
|
||||
world_version=world.world_version.as_simple_string(),
|
||||
yaml_dump=yaml_dump_scalar,
|
||||
__version__=__version__, game=game_name, yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
cleandoc=cleandoc,
|
||||
)
|
||||
|
||||
@@ -49,7 +49,6 @@ class Version(typing.NamedTuple):
|
||||
|
||||
__version__ = "0.6.4"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
version = Version(*version_tuple)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
is_macos = sys.platform == "darwin"
|
||||
@@ -323,13 +322,11 @@ def get_options() -> Settings:
|
||||
return get_settings()
|
||||
|
||||
|
||||
def persistent_store(category: str, key: str, value: typing.Any, force_store: bool = False):
|
||||
def persistent_store(category: str, key: str, value: typing.Any):
|
||||
path = user_path("_persistent_storage.yaml")
|
||||
storage = persistent_load()
|
||||
if not force_store and category in storage and key in storage[category] and storage[category][key] == value:
|
||||
return # no changes necessary
|
||||
category_dict = storage.setdefault(category, {})
|
||||
category_dict[key] = value
|
||||
path = user_path("_persistent_storage.yaml")
|
||||
with open(path, "wt") as f:
|
||||
f.write(dump(storage, Dumper=Dumper))
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import json
|
||||
import typing
|
||||
from uuid import UUID
|
||||
|
||||
from flask import request, session, url_for
|
||||
from markupsafe import Markup
|
||||
from pony.orm import commit, select
|
||||
from pony.orm import commit
|
||||
|
||||
from Utils import restricted_dumps
|
||||
from WebHostLib import app, cache
|
||||
from WebHostLib import app
|
||||
from WebHostLib.check import get_yaml_data, roll_options
|
||||
from WebHostLib.generate import get_meta
|
||||
from WebHostLib.models import Generation, STATE_QUEUED, STATE_STARTED, Seed, STATE_ERROR
|
||||
from WebHostLib.models import Generation, STATE_QUEUED, Seed, STATE_ERROR
|
||||
from . import api_endpoints
|
||||
|
||||
|
||||
@@ -75,23 +74,12 @@ def generate_api():
|
||||
def wait_seed_api(seed: UUID):
|
||||
seed_id = seed
|
||||
seed = Seed.get(id=seed_id)
|
||||
reply_dict: dict[str, typing.Any] = {"queue_len": get_queue_length()}
|
||||
if seed:
|
||||
reply_dict["text"] = "Generation done"
|
||||
return reply_dict, 201
|
||||
return {"text": "Generation done"}, 201
|
||||
generation = Generation.get(id=seed_id)
|
||||
|
||||
if not generation:
|
||||
reply_dict["text"] = "Generation not found"
|
||||
return reply_dict, 404
|
||||
return {"text": "Generation not found"}, 404
|
||||
elif generation.state == STATE_ERROR:
|
||||
reply_dict["text"] = "Generation failed"
|
||||
return reply_dict, 500
|
||||
reply_dict["text"] = "Generation running"
|
||||
return reply_dict, 202
|
||||
|
||||
|
||||
@cache.memoize(timeout=5)
|
||||
def get_queue_length() -> int:
|
||||
return select(generation for generation in Generation if
|
||||
generation.state == STATE_STARTED or generation.state == STATE_QUEUED).count()
|
||||
return {"text": "Generation failed"}, 500
|
||||
return {"text": "Generation running"}, 202
|
||||
|
||||
+127
-136
@@ -11,53 +11,6 @@ from WebHostLib.models import Room
|
||||
from WebHostLib.tracker import TrackerData
|
||||
|
||||
|
||||
class PlayerAlias(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
alias: str | None
|
||||
|
||||
|
||||
class PlayerItemsReceived(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
items: list[NetworkItem]
|
||||
|
||||
|
||||
class PlayerChecksDone(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
locations: list[int]
|
||||
|
||||
|
||||
class TeamTotalChecks(TypedDict):
|
||||
team: int
|
||||
checks_done: int
|
||||
|
||||
|
||||
class PlayerHints(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
hints: list[Hint]
|
||||
|
||||
|
||||
class PlayerTimer(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
time: datetime | None
|
||||
|
||||
|
||||
class PlayerStatus(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
status: ClientStatus
|
||||
|
||||
|
||||
class PlayerLocationsTotal(TypedDict):
|
||||
team: int
|
||||
player: int
|
||||
total_locations: int
|
||||
|
||||
|
||||
@api_endpoints.route("/tracker/<suuid:tracker>")
|
||||
@cache.memoize(timeout=60)
|
||||
def tracker_data(tracker: UUID) -> dict[str, Any]:
|
||||
@@ -76,77 +29,122 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
|
||||
|
||||
all_players: dict[int, list[int]] = tracker_data.get_all_players()
|
||||
|
||||
player_aliases: list[PlayerAlias] = []
|
||||
class PlayerAlias(TypedDict):
|
||||
player: int
|
||||
name: str | None
|
||||
|
||||
player_aliases: list[dict[str, int | list[PlayerAlias]]] = []
|
||||
"""Slot aliases of all players."""
|
||||
for team, players in all_players.items():
|
||||
team_player_aliases: list[PlayerAlias] = []
|
||||
team_aliases = {"team": team, "players": team_player_aliases}
|
||||
player_aliases.append(team_aliases)
|
||||
for player in players:
|
||||
player_aliases.append({"team": team, "player": player, "alias": tracker_data.get_player_alias(team, player)})
|
||||
team_player_aliases.append({"player": player, "alias": tracker_data.get_player_alias(team, player)})
|
||||
|
||||
player_items_received: list[PlayerItemsReceived] = []
|
||||
class PlayerItemsReceived(TypedDict):
|
||||
player: int
|
||||
items: list[NetworkItem]
|
||||
|
||||
player_items_received: list[dict[str, int | list[PlayerItemsReceived]]] = []
|
||||
"""Items received by each player."""
|
||||
for team, players in all_players.items():
|
||||
player_received_items: list[PlayerItemsReceived] = []
|
||||
team_items_received = {"team": team, "players": player_received_items}
|
||||
player_items_received.append(team_items_received)
|
||||
for player in players:
|
||||
player_items_received.append(
|
||||
{"team": team, "player": player, "items": tracker_data.get_player_received_items(team, player)})
|
||||
player_received_items.append(
|
||||
{"player": player, "items": tracker_data.get_player_received_items(team, player)})
|
||||
|
||||
player_checks_done: list[PlayerChecksDone] = []
|
||||
class PlayerChecksDone(TypedDict):
|
||||
player: int
|
||||
locations: list[int]
|
||||
|
||||
player_checks_done: list[dict[str, int | list[PlayerChecksDone]]] = []
|
||||
"""ID of all locations checked by each player."""
|
||||
for team, players in all_players.items():
|
||||
per_player_checks: list[PlayerChecksDone] = []
|
||||
team_checks_done = {"team": team, "players": per_player_checks}
|
||||
player_checks_done.append(team_checks_done)
|
||||
for player in players:
|
||||
player_checks_done.append(
|
||||
{"team": team, "player": player, "locations": sorted(tracker_data.get_player_checked_locations(team, player))})
|
||||
per_player_checks.append(
|
||||
{"player": player, "locations": sorted(tracker_data.get_player_checked_locations(team, player))})
|
||||
|
||||
total_checks_done: list[TeamTotalChecks] = [
|
||||
total_checks_done: list[dict[str, int]] = [
|
||||
{"team": team, "checks_done": checks_done}
|
||||
for team, checks_done in tracker_data.get_team_locations_checked_count().items()
|
||||
]
|
||||
"""Total number of locations checked for the entire multiworld per team."""
|
||||
|
||||
hints: list[PlayerHints] = []
|
||||
class PlayerHints(TypedDict):
|
||||
player: int
|
||||
hints: list[Hint]
|
||||
|
||||
hints: list[dict[str, int | list[PlayerHints]]] = []
|
||||
"""Hints that all players have used or received."""
|
||||
for team, players in tracker_data.get_all_slots().items():
|
||||
per_player_hints: list[PlayerHints] = []
|
||||
team_hints = {"team": team, "players": per_player_hints}
|
||||
hints.append(team_hints)
|
||||
for player in players:
|
||||
player_hints = sorted(tracker_data.get_player_hints(team, player))
|
||||
hints.append({"team": team, "player": player, "hints": player_hints})
|
||||
slot_info = tracker_data.get_slot_info(player)
|
||||
per_player_hints.append({"player": player, "hints": player_hints})
|
||||
slot_info = tracker_data.get_slot_info(team, player)
|
||||
# this assumes groups are always after players
|
||||
if slot_info.type != SlotType.group:
|
||||
continue
|
||||
for member in slot_info.group_members:
|
||||
hints[member - 1]["hints"] += player_hints
|
||||
team_hints[member]["hints"] += player_hints
|
||||
|
||||
activity_timers: list[PlayerTimer] = []
|
||||
class PlayerTimer(TypedDict):
|
||||
player: int
|
||||
time: datetime | None
|
||||
|
||||
activity_timers: list[dict[str, int | list[PlayerTimer]]] = []
|
||||
"""Time of last activity per player. Returned as RFC 1123 format and null if no connection has been made."""
|
||||
for team, players in all_players.items():
|
||||
player_timers: list[PlayerTimer] = []
|
||||
team_timers = {"team": team, "players": player_timers}
|
||||
activity_timers.append(team_timers)
|
||||
for player in players:
|
||||
activity_timers.append({"team": team, "player": player, "time": None})
|
||||
player_timers.append({"player": player, "time": None})
|
||||
|
||||
for (team, player), timestamp in tracker_data._multisave.get("client_activity_timers", []):
|
||||
for entry in activity_timers:
|
||||
if entry["team"] == team and entry["player"] == player:
|
||||
entry["time"] = datetime.fromtimestamp(timestamp, timezone.utc)
|
||||
break
|
||||
client_activity_timers: tuple[tuple[int, int], float] = tracker_data._multisave.get("client_activity_timers", ())
|
||||
for (team, player), timestamp in client_activity_timers:
|
||||
# use index since we can rely on order
|
||||
# FIX: key is "players" (not "player_timers")
|
||||
activity_timers[team]["players"][player - 1]["time"] = datetime.fromtimestamp(timestamp, timezone.utc)
|
||||
|
||||
connection_timers: list[PlayerTimer] = []
|
||||
|
||||
connection_timers: list[dict[str, int | list[PlayerTimer]]] = []
|
||||
"""Time of last connection per player. Returned as RFC 1123 format and null if no connection has been made."""
|
||||
for team, players in all_players.items():
|
||||
player_timers: list[PlayerTimer] = []
|
||||
team_connection_timers = {"team": team, "players": player_timers}
|
||||
connection_timers.append(team_connection_timers)
|
||||
for player in players:
|
||||
connection_timers.append({"team": team, "player": player, "time": None})
|
||||
player_timers.append({"player": player, "time": None})
|
||||
|
||||
for (team, player), timestamp in tracker_data._multisave.get("client_connection_timers", []):
|
||||
# find the matching entry
|
||||
for entry in connection_timers:
|
||||
if entry["team"] == team and entry["player"] == player:
|
||||
entry["time"] = datetime.fromtimestamp(timestamp, timezone.utc)
|
||||
break
|
||||
client_connection_timers: tuple[tuple[int, int], float] = tracker_data._multisave.get(
|
||||
"client_connection_timers", ())
|
||||
for (team, player), timestamp in client_connection_timers:
|
||||
connection_timers[team]["players"][player - 1]["time"] = datetime.fromtimestamp(timestamp, timezone.utc)
|
||||
|
||||
player_status: list[PlayerStatus] = []
|
||||
class PlayerStatus(TypedDict):
|
||||
player: int
|
||||
status: ClientStatus
|
||||
|
||||
player_status: list[dict[str, int | list[PlayerStatus]]] = []
|
||||
"""The current client status for each player."""
|
||||
for team, players in all_players.items():
|
||||
player_statuses: list[PlayerStatus] = []
|
||||
team_status = {"team": team, "players": player_statuses}
|
||||
player_status.append(team_status)
|
||||
for player in players:
|
||||
player_status.append({"team": team, "player": player, "status": tracker_data.get_player_client_status(team, player)})
|
||||
player_statuses.append({"player": player, "status": tracker_data.get_player_client_status(team, player)})
|
||||
|
||||
return {
|
||||
**get_static_tracker_data(room),
|
||||
"aliases": player_aliases,
|
||||
"player_items_received": player_items_received,
|
||||
"player_checks_done": player_checks_done,
|
||||
@@ -155,87 +153,80 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
|
||||
"activity_timers": activity_timers,
|
||||
"connection_timers": connection_timers,
|
||||
"player_status": player_status,
|
||||
"datapackage": tracker_data._multidata["datapackage"],
|
||||
}
|
||||
|
||||
|
||||
class PlayerGroups(TypedDict):
|
||||
slot: int
|
||||
name: str
|
||||
members: list[int]
|
||||
|
||||
|
||||
class PlayerSlotData(TypedDict):
|
||||
player: int
|
||||
slot_data: dict[str, Any]
|
||||
|
||||
|
||||
@api_endpoints.route("/static_tracker/<suuid:tracker>")
|
||||
@cache.memoize(timeout=300)
|
||||
def static_tracker_data(tracker: UUID) -> dict[str, Any]:
|
||||
@cache.memoize()
|
||||
def get_static_tracker_data(room: Room) -> dict[str, Any]:
|
||||
"""
|
||||
Outputs json data to <root_path>/api/static_tracker/<id of current session tracker>.
|
||||
|
||||
:param tracker: UUID of current session tracker.
|
||||
|
||||
:return: Static tracking data for all players in the room. Typing and docstrings describe the format of each value.
|
||||
Builds and caches the static data for this active session tracker, so that it doesn't need to be recalculated.
|
||||
"""
|
||||
room: Room | None = Room.get(tracker=tracker)
|
||||
if not room:
|
||||
abort(404)
|
||||
|
||||
tracker_data = TrackerData(room)
|
||||
|
||||
all_players: dict[int, list[int]] = tracker_data.get_all_players()
|
||||
|
||||
groups: list[PlayerGroups] = []
|
||||
class PlayerGroups(TypedDict):
|
||||
slot: int
|
||||
name: str
|
||||
members: list[int]
|
||||
|
||||
groups: list[dict[str, int | list[PlayerGroups]]] = []
|
||||
"""The Slot ID of groups and the IDs of the group's members."""
|
||||
for team, players in tracker_data.get_all_slots().items():
|
||||
groups_in_team: list[PlayerGroups] = []
|
||||
team_groups = {"team": team, "groups": groups_in_team}
|
||||
groups.append(team_groups)
|
||||
for player in players:
|
||||
slot_info = tracker_data.get_slot_info(player)
|
||||
slot_info = tracker_data.get_slot_info(team, player)
|
||||
if slot_info.type != SlotType.group or not slot_info.group_members:
|
||||
continue
|
||||
groups.append(
|
||||
groups_in_team.append(
|
||||
{
|
||||
"slot": player,
|
||||
"name": slot_info.name,
|
||||
"members": list(slot_info.group_members),
|
||||
})
|
||||
break
|
||||
class PlayerName(TypedDict):
|
||||
player: int
|
||||
name: str
|
||||
|
||||
player_locations_total: list[PlayerLocationsTotal] = []
|
||||
player_names: list[dict[str, str | list[PlayerName]]] = []
|
||||
"""Slot names of all players."""
|
||||
for team, players in all_players.items():
|
||||
per_team_player_names: list[PlayerName] = []
|
||||
team_names = {"team": team, "players": per_team_player_names}
|
||||
player_names.append(team_names)
|
||||
for player in players:
|
||||
player_locations_total.append(
|
||||
{"team": team, "player": player, "total_locations": len(tracker_data.get_player_locations(player))})
|
||||
per_team_player_names.append({"player": player, "name": tracker_data.get_player_name(team, player)})
|
||||
|
||||
class PlayerGame(TypedDict):
|
||||
player: int
|
||||
game: str
|
||||
|
||||
games: list[dict[str, int | list[PlayerGame]]] = []
|
||||
"""The game each player is playing."""
|
||||
for team, players in all_players.items():
|
||||
player_games: list[PlayerGame] = []
|
||||
team_games = {"team": team, "players": player_games}
|
||||
games.append(team_games)
|
||||
for player in players:
|
||||
player_games.append({"player": player, "game": tracker_data.get_player_game(team, player)})
|
||||
|
||||
class PlayerSlotData(TypedDict):
|
||||
player: int
|
||||
slot_data: dict[str, Any]
|
||||
|
||||
slot_data: list[dict[str, int | list[PlayerSlotData]]] = []
|
||||
"""Slot data for each player."""
|
||||
for team, players in all_players.items():
|
||||
player_slot_data: list[PlayerSlotData] = []
|
||||
team_slot_data = {"team": team, "players": player_slot_data}
|
||||
slot_data.append(team_slot_data)
|
||||
for player in players:
|
||||
player_slot_data.append({"player": player, "slot_data": tracker_data.get_slot_data(team, player)})
|
||||
|
||||
return {
|
||||
"groups": groups,
|
||||
"datapackage": tracker_data._multidata["datapackage"],
|
||||
"player_locations_total": player_locations_total,
|
||||
"slot_data": slot_data,
|
||||
}
|
||||
|
||||
# It should be exceedingly rare that slot data is needed, so it's separated out.
|
||||
@api_endpoints.route("/slot_data_tracker/<suuid:tracker>")
|
||||
@cache.memoize(timeout=300)
|
||||
def tracker_slot_data(tracker: UUID) -> list[PlayerSlotData]:
|
||||
"""
|
||||
Outputs json data to <root_path>/api/slot_data_tracker/<id of current session tracker>.
|
||||
|
||||
:param tracker: UUID of current session tracker.
|
||||
|
||||
:return: Slot data for all players in the room. Typing completely arbitrary per game.
|
||||
"""
|
||||
room: Room | None = Room.get(tracker=tracker)
|
||||
if not room:
|
||||
abort(404)
|
||||
tracker_data = TrackerData(room)
|
||||
|
||||
all_players: dict[int, list[int]] = tracker_data.get_all_players()
|
||||
|
||||
slot_data: list[PlayerSlotData] = []
|
||||
"""Slot data for each player."""
|
||||
for team, players in all_players.items():
|
||||
for player in players:
|
||||
slot_data.append({"player": player, "slot_data": tracker_data.get_slot_data(player)})
|
||||
break
|
||||
|
||||
return slot_data
|
||||
|
||||
+1
-4
@@ -260,10 +260,7 @@ def host_room(room: UUID):
|
||||
# indicate that the page should reload to get the assigned port
|
||||
should_refresh = ((not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3))
|
||||
or room.last_activity < now - datetime.timedelta(seconds=room.timeout))
|
||||
|
||||
if now - room.last_activity > datetime.timedelta(minutes=1):
|
||||
# we only set last_activity if needed, otherwise parallel access on /room will cause an internal server error
|
||||
# due to "pony.orm.core.OptimisticCheckError: Object Room was updated outside of current transaction"
|
||||
with db_session:
|
||||
room.last_activity = now # will trigger a spinup, if it's not already running
|
||||
|
||||
browser_tokens = "Mozilla", "Chrome", "Safari"
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<td>
|
||||
{% if hint.finding_player == player %}
|
||||
<b>{{ player_names_with_alias[(team, hint.finding_player)] }}</b>
|
||||
{% elif get_slot_info(hint.finding_player).type == 2 %}
|
||||
{% elif get_slot_info(team, hint.finding_player).type == 2 %}
|
||||
<i>{{ player_names_with_alias[(team, hint.finding_player)] }}</i>
|
||||
{% else %}
|
||||
<a href="{{ url_for("get_player_tracker", tracker=room.tracker, tracked_team=team, tracked_player=hint.finding_player) }}">
|
||||
@@ -109,7 +109,7 @@
|
||||
<td>
|
||||
{% if hint.receiving_player == player %}
|
||||
<b>{{ player_names_with_alias[(team, hint.receiving_player)] }}</b>
|
||||
{% elif get_slot_info(hint.receiving_player).type == 2 %}
|
||||
{% elif get_slot_info(team, hint.receiving_player).type == 2 %}
|
||||
<i>{{ player_names_with_alias[(team, hint.receiving_player)] }}</i>
|
||||
{% else %}
|
||||
<a href="{{ url_for("get_player_tracker", tracker=room.tracker, tracked_team=team, tracked_player=hint.receiving_player) }}">
|
||||
|
||||
@@ -45,15 +45,15 @@
|
||||
{%- set current_sphere = loop.index %}
|
||||
{%- for player, sphere_location_ids in sphere.items() %}
|
||||
{%- set checked_locations = tracker_data.get_player_checked_locations(team, player) %}
|
||||
{%- set finder_game = tracker_data.get_player_game(player) %}
|
||||
{%- set player_location_data = tracker_data.get_player_locations(player) %}
|
||||
{%- set finder_game = tracker_data.get_player_game(team, player) %}
|
||||
{%- set player_location_data = tracker_data.get_player_locations(team, player) %}
|
||||
{%- for location_id in sphere_location_ids.intersection(checked_locations) %}
|
||||
<tr>
|
||||
{%- set item_id, receiver, item_flags = player_location_data[location_id] %}
|
||||
{%- set receiver_game = tracker_data.get_player_game(receiver) %}
|
||||
{%- set receiver_game = tracker_data.get_player_game(team, receiver) %}
|
||||
<td>{{ current_sphere }}</td>
|
||||
<td>{{ tracker_data.get_player_name(player) }}</td>
|
||||
<td>{{ tracker_data.get_player_name(receiver) }}</td>
|
||||
<td>{{ tracker_data.get_player_name(team, player) }}</td>
|
||||
<td>{{ tracker_data.get_player_name(team, receiver) }}</td>
|
||||
<td>{{ tracker_data.item_id_to_name[receiver_game][item_id] }}</td>
|
||||
<td>{{ tracker_data.location_id_to_name[finder_game][location_id] }}</td>
|
||||
<td>{{ finder_game }}</td>
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
-%}
|
||||
<tr>
|
||||
<td>
|
||||
{% if get_slot_info(hint.finding_player).type == 2 %}
|
||||
{% if get_slot_info(team, hint.finding_player).type == 2 %}
|
||||
<i>{{ player_names_with_alias[(team, hint.finding_player)] }}</i>
|
||||
{% else %}
|
||||
{{ player_names_with_alias[(team, hint.finding_player)] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if get_slot_info(hint.receiving_player).type == 2 %}
|
||||
{% if get_slot_info(team, hint.receiving_player).type == 2 %}
|
||||
<i>{{ player_names_with_alias[(team, hint.receiving_player)] }}</i>
|
||||
{% else %}
|
||||
{{ player_names_with_alias[(team, hint.receiving_player)] }}
|
||||
|
||||
@@ -30,21 +30,10 @@
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.queue_len === 1){
|
||||
waitSeedDiv.innerHTML = `
|
||||
<h1>Generation in Progress</h1>
|
||||
<p>${data.text}</p>
|
||||
<p>This is the only generation in the queue.</p>
|
||||
`;
|
||||
}
|
||||
else {
|
||||
waitSeedDiv.innerHTML = `
|
||||
<h1>Generation in Progress</h1>
|
||||
<p>${data.text}</p>
|
||||
<p>There are ${data.queue_len} generations in the queue.</p>
|
||||
`;
|
||||
}
|
||||
|
||||
waitSeedDiv.innerHTML = `
|
||||
<h1>Generation in Progress</h1>
|
||||
<p>${data.text}</p>
|
||||
`;
|
||||
|
||||
setTimeout(checkStatus, 1000); // Continue polling.
|
||||
} catch (error) {
|
||||
|
||||
+72
-51
@@ -17,6 +17,7 @@ from .models import GameDataPackage, Room
|
||||
# Multisave is currently updated, at most, every minute.
|
||||
TRACKER_CACHE_TIMEOUT_IN_SECONDS = 60
|
||||
|
||||
_multidata_cache = {}
|
||||
_multiworld_trackers: Dict[str, Callable] = {}
|
||||
_player_trackers: Dict[str, Callable] = {}
|
||||
|
||||
@@ -84,27 +85,27 @@ class TrackerData:
|
||||
"""Retrieves the seed name."""
|
||||
return self._multidata["seed_name"]
|
||||
|
||||
def get_slot_data(self, player: int) -> Dict[str, Any]:
|
||||
def get_slot_data(self, team: int, player: int) -> Dict[str, Any]:
|
||||
"""Retrieves the slot data for a given player."""
|
||||
return self._multidata["slot_data"][player]
|
||||
|
||||
def get_slot_info(self, player: int) -> NetworkSlot:
|
||||
def get_slot_info(self, team: int, player: int) -> NetworkSlot:
|
||||
"""Retrieves the NetworkSlot data for a given player."""
|
||||
return self._multidata["slot_info"][player]
|
||||
|
||||
def get_player_name(self, player: int) -> str:
|
||||
def get_player_name(self, team: int, player: int) -> str:
|
||||
"""Retrieves the slot name for a given player."""
|
||||
return self.get_slot_info(player).name
|
||||
return self.get_slot_info(team, player).name
|
||||
|
||||
def get_player_game(self, player: int) -> str:
|
||||
def get_player_game(self, team: int, player: int) -> str:
|
||||
"""Retrieves the game for a given player."""
|
||||
return self.get_slot_info(player).game
|
||||
return self.get_slot_info(team, player).game
|
||||
|
||||
def get_player_locations(self, player: int) -> Dict[int, ItemMetadata]:
|
||||
def get_player_locations(self, team: int, player: int) -> Dict[int, ItemMetadata]:
|
||||
"""Retrieves all locations with their containing item's metadata for a given player."""
|
||||
return self._multidata["locations"][player]
|
||||
|
||||
def get_player_starting_inventory(self, player: int) -> List[int]:
|
||||
def get_player_starting_inventory(self, team: int, player: int) -> List[int]:
|
||||
"""Retrieves a list of all item codes a given slot starts with."""
|
||||
return self._multidata["precollected_items"][player]
|
||||
|
||||
@@ -115,7 +116,7 @@ class TrackerData:
|
||||
@_cache_results
|
||||
def get_player_missing_locations(self, team: int, player: int) -> Set[int]:
|
||||
"""Retrieves the set of all locations not marked complete by this player."""
|
||||
return set(self.get_player_locations(player)) - self.get_player_checked_locations(team, player)
|
||||
return set(self.get_player_locations(team, player)) - self.get_player_checked_locations(team, player)
|
||||
|
||||
def get_player_received_items(self, team: int, player: int) -> List[NetworkItem]:
|
||||
"""Returns all items received to this player in order of received."""
|
||||
@@ -125,7 +126,7 @@ class TrackerData:
|
||||
def get_player_inventory_counts(self, team: int, player: int) -> collections.Counter:
|
||||
"""Retrieves a dictionary of all items received by their id and their received count."""
|
||||
received_items = self.get_player_received_items(team, player)
|
||||
starting_items = self.get_player_starting_inventory(player)
|
||||
starting_items = self.get_player_starting_inventory(team, player)
|
||||
inventory = collections.Counter()
|
||||
for item in received_items:
|
||||
inventory[item.item] += 1
|
||||
@@ -178,7 +179,7 @@ class TrackerData:
|
||||
def get_team_locations_total_count(self) -> Dict[int, int]:
|
||||
"""Retrieves a dictionary of total player locations each team has."""
|
||||
return {
|
||||
team: sum(len(self.get_player_locations(player)) for player in players)
|
||||
team: sum(len(self.get_player_locations(team, player)) for player in players)
|
||||
for team, players in self.get_all_players().items()
|
||||
}
|
||||
|
||||
@@ -209,7 +210,7 @@ class TrackerData:
|
||||
return {
|
||||
0: [
|
||||
player for player, slot_info in self._multidata["slot_info"].items()
|
||||
if self.get_slot_info(player).type == SlotType.player
|
||||
if self.get_slot_info(0, player).type == SlotType.player
|
||||
]
|
||||
}
|
||||
|
||||
@@ -225,7 +226,7 @@ class TrackerData:
|
||||
def get_room_locations(self) -> Dict[TeamPlayer, Dict[int, ItemMetadata]]:
|
||||
"""Retrieves a dictionary of all locations and their associated item metadata per player."""
|
||||
return {
|
||||
(team, player): self.get_player_locations(player)
|
||||
(team, player): self.get_player_locations(team, player)
|
||||
for team, players in self.get_all_players().items() for player in players
|
||||
}
|
||||
|
||||
@@ -233,7 +234,7 @@ class TrackerData:
|
||||
def get_room_games(self) -> Dict[TeamPlayer, str]:
|
||||
"""Retrieves a dictionary of games for each player."""
|
||||
return {
|
||||
(team, player): self.get_player_game(player)
|
||||
(team, player): self.get_player_game(team, player)
|
||||
for team, players in self.get_all_slots().items() for player in players
|
||||
}
|
||||
|
||||
@@ -261,9 +262,9 @@ class TrackerData:
|
||||
for player in players:
|
||||
alias = self.get_player_alias(team, player)
|
||||
if alias:
|
||||
long_player_names[team, player] = f"{alias} ({self.get_player_name(player)})"
|
||||
long_player_names[team, player] = f"{alias} ({self.get_player_name(team, player)})"
|
||||
else:
|
||||
long_player_names[team, player] = self.get_player_name(player)
|
||||
long_player_names[team, player] = self.get_player_name(team, player)
|
||||
|
||||
return long_player_names
|
||||
|
||||
@@ -343,7 +344,7 @@ def get_timeout_and_player_tracker(room: Room, tracked_team: int, tracked_player
|
||||
tracker_data = TrackerData(room)
|
||||
|
||||
# Load and render the game-specific player tracker, or fallback to generic tracker if none exists.
|
||||
game_specific_tracker = _player_trackers.get(tracker_data.get_player_game(tracked_player), None)
|
||||
game_specific_tracker = _player_trackers.get(tracker_data.get_player_game(tracked_team, tracked_player), None)
|
||||
if game_specific_tracker and not generic:
|
||||
tracker = game_specific_tracker(tracker_data, tracked_team, tracked_player)
|
||||
else:
|
||||
@@ -408,10 +409,10 @@ def get_enabled_multiworld_trackers(room: Room) -> Dict[str, Callable]:
|
||||
|
||||
|
||||
def render_generic_tracker(tracker_data: TrackerData, team: int, player: int) -> str:
|
||||
game = tracker_data.get_player_game(player)
|
||||
game = tracker_data.get_player_game(team, player)
|
||||
|
||||
received_items_in_order = {}
|
||||
starting_inventory = tracker_data.get_player_starting_inventory(player)
|
||||
starting_inventory = tracker_data.get_player_starting_inventory(team, player)
|
||||
for index, item in enumerate(starting_inventory):
|
||||
received_items_in_order[item] = index
|
||||
for index, network_item in enumerate(tracker_data.get_player_received_items(team, player),
|
||||
@@ -427,7 +428,7 @@ def render_generic_tracker(tracker_data: TrackerData, team: int, player: int) ->
|
||||
player=player,
|
||||
player_name=tracker_data.get_room_long_player_names()[team, player],
|
||||
inventory=tracker_data.get_player_inventory_counts(team, player),
|
||||
locations=tracker_data.get_player_locations(player),
|
||||
locations=tracker_data.get_player_locations(team, player),
|
||||
checked_locations=tracker_data.get_player_checked_locations(team, player),
|
||||
received_items=received_items_in_order,
|
||||
saving_second=tracker_data.get_room_saving_second(),
|
||||
@@ -499,7 +500,7 @@ if "Factorio" in network_data_package["games"]:
|
||||
tracker_data.item_id_to_name["Factorio"][item_id]: count
|
||||
for item_id, count in tracker_data.get_player_inventory_counts(team, player).items()
|
||||
}) for team, players in tracker_data.get_all_players().items() for player in players
|
||||
if tracker_data.get_player_game(player) == "Factorio"
|
||||
if tracker_data.get_player_game(team, player) == "Factorio"
|
||||
}
|
||||
|
||||
return render_template(
|
||||
@@ -588,7 +589,7 @@ if "A Link to the Past" in network_data_package["games"]:
|
||||
|
||||
# Highlight 'bombs' if we received any bomb upgrades in bombless start.
|
||||
# In race mode, we'll just assume bombless start for simplicity.
|
||||
if tracker_data.get_slot_data(player).get("bombless_start", True):
|
||||
if tracker_data.get_slot_data(team, player).get("bombless_start", True):
|
||||
inventory["Bombs"] = sum(count for item, count in inventory.items() if item.startswith("Bomb Upgrade"))
|
||||
else:
|
||||
inventory["Bombs"] = 1
|
||||
@@ -604,7 +605,7 @@ if "A Link to the Past" in network_data_package["games"]:
|
||||
for code, count in tracker_data.get_player_inventory_counts(team, player).items()
|
||||
})
|
||||
for team, players in tracker_data.get_all_players().items()
|
||||
for player in players if tracker_data.get_slot_info(player).game == "A Link to the Past"
|
||||
for player in players if tracker_data.get_slot_info(team, player).game == "A Link to the Past"
|
||||
}
|
||||
|
||||
# Translate non-progression items to progression items for tracker simplicity.
|
||||
@@ -623,7 +624,7 @@ if "A Link to the Past" in network_data_package["games"]:
|
||||
for region_name in known_regions
|
||||
}
|
||||
for team, players in tracker_data.get_all_players().items()
|
||||
for player in players if tracker_data.get_slot_info(player).game == "A Link to the Past"
|
||||
for player in players if tracker_data.get_slot_info(team, player).game == "A Link to the Past"
|
||||
}
|
||||
|
||||
# Get a totals count.
|
||||
@@ -697,7 +698,7 @@ if "A Link to the Past" in network_data_package["games"]:
|
||||
team=team,
|
||||
player=player,
|
||||
inventory=inventory,
|
||||
player_name=tracker_data.get_player_name(player),
|
||||
player_name=tracker_data.get_player_name(team, player),
|
||||
regions=regions,
|
||||
known_regions=known_regions,
|
||||
)
|
||||
@@ -844,7 +845,7 @@ if "Ocarina of Time" in network_data_package["games"]:
|
||||
return full_name[len(area):]
|
||||
return full_name
|
||||
|
||||
locations = tracker_data.get_player_locations(player)
|
||||
locations = tracker_data.get_player_locations(team, player)
|
||||
checked_locations = tracker_data.get_player_checked_locations(team, player).intersection(set(locations))
|
||||
location_info = {}
|
||||
checks_done = {}
|
||||
@@ -906,7 +907,7 @@ if "Ocarina of Time" in network_data_package["games"]:
|
||||
player=player,
|
||||
team=team,
|
||||
room=tracker_data.room,
|
||||
player_name=tracker_data.get_player_name(player),
|
||||
player_name=tracker_data.get_player_name(team, player),
|
||||
icons=icons,
|
||||
acquired_items={lookup_any_item_id_to_name[id] for id, count in inventory.items() if count > 0},
|
||||
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
|
||||
@@ -953,37 +954,57 @@ if "Timespinner" in network_data_package["games"]:
|
||||
"Lab Glasses": "https://timespinnerwiki.com/mediawiki/images/4/4a/Lab_Glasses.png",
|
||||
"Eye Orb": "https://timespinnerwiki.com/mediawiki/images/a/a4/Eye_Orb.png",
|
||||
"Lab Coat": "https://timespinnerwiki.com/mediawiki/images/5/51/Lab_Coat.png",
|
||||
"Demon": "https://timespinnerwiki.com/mediawiki/images/f/f8/Familiar_Demon.png",
|
||||
"Cube of Bodie": "https://timespinnerwiki.com/mediawiki/images/1/14/Menu_Icon_Stats.png"
|
||||
"Demon": "https://timespinnerwiki.com/mediawiki/images/f/f8/Familiar_Demon.png",
|
||||
}
|
||||
|
||||
timespinner_location_ids = {
|
||||
"Present": list(range(1337000, 1337085)),
|
||||
"Past": list(range(1337086, 1337175)),
|
||||
"Present": [
|
||||
1337000, 1337001, 1337002, 1337003, 1337004, 1337005, 1337006, 1337007, 1337008, 1337009,
|
||||
1337010, 1337011, 1337012, 1337013, 1337014, 1337015, 1337016, 1337017, 1337018, 1337019,
|
||||
1337020, 1337021, 1337022, 1337023, 1337024, 1337025, 1337026, 1337027, 1337028, 1337029,
|
||||
1337030, 1337031, 1337032, 1337033, 1337034, 1337035, 1337036, 1337037, 1337038, 1337039,
|
||||
1337040, 1337041, 1337042, 1337043, 1337044, 1337045, 1337046, 1337047, 1337048, 1337049,
|
||||
1337050, 1337051, 1337052, 1337053, 1337054, 1337055, 1337056, 1337057, 1337058, 1337059,
|
||||
1337060, 1337061, 1337062, 1337063, 1337064, 1337065, 1337066, 1337067, 1337068, 1337069,
|
||||
1337070, 1337071, 1337072, 1337073, 1337074, 1337075, 1337076, 1337077, 1337078, 1337079,
|
||||
1337080, 1337081, 1337082, 1337083, 1337084, 1337085],
|
||||
"Past": [
|
||||
1337086, 1337087, 1337088, 1337089,
|
||||
1337090, 1337091, 1337092, 1337093, 1337094, 1337095, 1337096, 1337097, 1337098, 1337099,
|
||||
1337100, 1337101, 1337102, 1337103, 1337104, 1337105, 1337106, 1337107, 1337108, 1337109,
|
||||
1337110, 1337111, 1337112, 1337113, 1337114, 1337115, 1337116, 1337117, 1337118, 1337119,
|
||||
1337120, 1337121, 1337122, 1337123, 1337124, 1337125, 1337126, 1337127, 1337128, 1337129,
|
||||
1337130, 1337131, 1337132, 1337133, 1337134, 1337135, 1337136, 1337137, 1337138, 1337139,
|
||||
1337140, 1337141, 1337142, 1337143, 1337144, 1337145, 1337146, 1337147, 1337148, 1337149,
|
||||
1337150, 1337151, 1337152, 1337153, 1337154, 1337155,
|
||||
1337171, 1337172, 1337173, 1337174, 1337175],
|
||||
"Ancient Pyramid": [
|
||||
1337236,
|
||||
1337246, 1337247, 1337248, 1337249]
|
||||
}
|
||||
|
||||
slot_data = tracker_data.get_slot_data(player)
|
||||
slot_data = tracker_data.get_slot_data(team, player)
|
||||
if (slot_data["DownloadableItems"]):
|
||||
timespinner_location_ids["Present"] += [1337156, 1337157] + list(range(1337159, 1337170))
|
||||
timespinner_location_ids["Present"] += [
|
||||
1337156, 1337157, 1337159,
|
||||
1337160, 1337161, 1337162, 1337163, 1337164, 1337165, 1337166, 1337167, 1337168, 1337169,
|
||||
1337170]
|
||||
if (slot_data["Cantoran"]):
|
||||
timespinner_location_ids["Past"].append(1337176)
|
||||
if (slot_data["LoreChecks"]):
|
||||
timespinner_location_ids["Present"] += list(range(1337177, 1337187))
|
||||
timespinner_location_ids["Past"] += list(range(1337188, 1337198))
|
||||
timespinner_location_ids["Present"] += [
|
||||
1337177, 1337178, 1337179,
|
||||
1337180, 1337181, 1337182, 1337183, 1337184, 1337185, 1337186, 1337187]
|
||||
timespinner_location_ids["Past"] += [
|
||||
1337188, 1337189,
|
||||
1337190, 1337191, 1337192, 1337193, 1337194, 1337195, 1337196, 1337197, 1337198]
|
||||
if (slot_data["GyreArchives"]):
|
||||
timespinner_location_ids["Ancient Pyramid"] += list(range(1337237, 1337245))
|
||||
timespinner_location_ids["Ancient Pyramid"] += [
|
||||
1337237, 1337238, 1337239,
|
||||
1337240, 1337241, 1337242, 1337243, 1337244, 1337245]
|
||||
if (slot_data["PyramidStart"]):
|
||||
timespinner_location_ids["Ancient Pyramid"] += [
|
||||
1337233, 1337234, 1337235]
|
||||
if (slot_data["PureTorcher"]):
|
||||
timespinner_location_ids["Present"] += list(range(1337250, 1337352)) + list(range(1337422, 1337496)) + [1337506] + list(range(1337712, 1337779)) + [1337781, 1337782]
|
||||
timespinner_location_ids["Past"] += list(range(1337497, 1337505)) + list(range(1337507, 1337711)) + [1337780]
|
||||
timespinner_location_ids["Ancient Pyramid"] += list(range(1337369, 1337421))
|
||||
if (slot_data["GyreArchives"]):
|
||||
timespinner_location_ids["Ancient Pyramid"] += list(range(1337353, 1337368))
|
||||
|
||||
display_data = {}
|
||||
|
||||
@@ -1014,7 +1035,7 @@ if "Timespinner" in network_data_package["games"]:
|
||||
player=player,
|
||||
team=team,
|
||||
room=tracker_data.room,
|
||||
player_name=tracker_data.get_player_name(player),
|
||||
player_name=tracker_data.get_player_name(team, player),
|
||||
checks_done=checks_done,
|
||||
checks_in_area=checks_in_area,
|
||||
location_info=location_info,
|
||||
@@ -1123,7 +1144,7 @@ if "Super Metroid" in network_data_package["games"]:
|
||||
player=player,
|
||||
team=team,
|
||||
room=tracker_data.room,
|
||||
player_name=tracker_data.get_player_name(player),
|
||||
player_name=tracker_data.get_player_name(team, player),
|
||||
checks_done=checks_done,
|
||||
checks_in_area=checks_in_area,
|
||||
location_info=location_info,
|
||||
@@ -1173,7 +1194,7 @@ if "ChecksFinder" in network_data_package["games"]:
|
||||
|
||||
display_data = {}
|
||||
inventory = tracker_data.get_player_inventory_counts(team, player)
|
||||
locations = tracker_data.get_player_locations(player)
|
||||
locations = tracker_data.get_player_locations(team, player)
|
||||
|
||||
# Multi-items
|
||||
multi_items = {
|
||||
@@ -1215,7 +1236,7 @@ if "ChecksFinder" in network_data_package["games"]:
|
||||
player=player,
|
||||
team=team,
|
||||
room=tracker_data.room,
|
||||
player_name=tracker_data.get_player_name(player),
|
||||
player_name=tracker_data.get_player_name(team, player),
|
||||
checks_done=checks_done,
|
||||
checks_in_area=checks_in_area,
|
||||
location_info=location_info,
|
||||
@@ -1243,7 +1264,7 @@ if "Starcraft 2" in network_data_package["games"]:
|
||||
UPGRADE_RESEARCH_SPEED_ITEM_ID = 1807
|
||||
UPGRADE_RESEARCH_COST_ITEM_ID = 1808
|
||||
REDUCED_MAX_SUPPLY_ITEM_ID = 1850
|
||||
slot_data = tracker_data.get_slot_data(player)
|
||||
slot_data = tracker_data.get_slot_data(team, player)
|
||||
inventory: collections.Counter[int] = tracker_data.get_player_inventory_counts(team, player)
|
||||
item_id_to_name = tracker_data.item_id_to_name["Starcraft 2"]
|
||||
location_id_to_name = tracker_data.location_id_to_name["Starcraft 2"]
|
||||
@@ -1259,10 +1280,10 @@ if "Starcraft 2" in network_data_package["games"]:
|
||||
display_data["shield_regen_count"] = inventory.get(SHIELD_REGENERATION_ITEM_ID, 0)
|
||||
display_data["upgrade_speed_count"] = inventory.get(UPGRADE_RESEARCH_SPEED_ITEM_ID, 0)
|
||||
display_data["research_cost_count"] = inventory.get(UPGRADE_RESEARCH_COST_ITEM_ID, 0)
|
||||
|
||||
|
||||
# Locations
|
||||
have_nco_locations = False
|
||||
locations = tracker_data.get_player_locations(player)
|
||||
locations = tracker_data.get_player_locations(team, player)
|
||||
checked_locations = tracker_data.get_player_checked_locations(team, player)
|
||||
missions: dict[str, list[tuple[str, bool]]] = {}
|
||||
for location_id in locations:
|
||||
@@ -1417,7 +1438,7 @@ if "Starcraft 2" in network_data_package["games"]:
|
||||
# the maximum bundle contribution, not the sum
|
||||
inventory[upgrade_id] = bundle_amount
|
||||
|
||||
|
||||
|
||||
# Victory condition
|
||||
game_state = tracker_data.get_player_client_status(team, player)
|
||||
display_data["game_finished"] = game_state == ClientStatus.CLIENT_GOAL
|
||||
@@ -1435,7 +1456,7 @@ if "Starcraft 2" in network_data_package["games"]:
|
||||
player=player,
|
||||
team=team,
|
||||
room=tracker_data.room,
|
||||
player_name=tracker_data.get_player_name(player),
|
||||
player_name=tracker_data.get_player_name(team, player),
|
||||
missions=missions,
|
||||
locations=locations,
|
||||
checked_locations=checked_locations,
|
||||
|
||||
@@ -33,10 +33,6 @@ description: {{ yaml_dump("Default %s Template" % game) }}
|
||||
game: {{ yaml_dump(game) }}
|
||||
requires:
|
||||
version: {{ __version__ }} # Version of Archipelago required for this yaml to work as expected.
|
||||
{%- if world_version != "0.0.0" %}
|
||||
game:
|
||||
{{ yaml_dump(game) }}: {{ world_version }} # Version of the world required for this yaml to work as expected.
|
||||
{%- endif %}
|
||||
|
||||
{%- macro range_option(option) %}
|
||||
# You can define additional values between the minimum and maximum values.
|
||||
|
||||
+3
-3
@@ -72,9 +72,6 @@
|
||||
# Faxanadu
|
||||
/worlds/faxanadu/ @Daivuk
|
||||
|
||||
# Final Fantasy (1)
|
||||
/worlds/ff1/ @Rosalie-A
|
||||
|
||||
# Final Fantasy Mystic Quest
|
||||
/worlds/ffmq/ @Alchav @wildham0
|
||||
|
||||
@@ -244,6 +241,9 @@
|
||||
# compatibility, these worlds may be deleted. If you are interested in stepping up as maintainer for
|
||||
# any of these worlds, please review `/docs/world maintainer.md` documentation.
|
||||
|
||||
# Final Fantasy (1)
|
||||
# /worlds/ff1/
|
||||
|
||||
# Ocarina of Time
|
||||
# /worlds/oot/
|
||||
|
||||
|
||||
@@ -19,21 +19,7 @@ the world's folder in `worlds/`. I.e. `worlds/ror2.apworld` containing `ror2/__i
|
||||
|
||||
## Metadata
|
||||
|
||||
Metadata about the apworld is defined in an `archipelago.json` file inside the zip archive.
|
||||
The current format version has at minimum:
|
||||
```json
|
||||
{
|
||||
"version": 6,
|
||||
"compatible_version": 5,
|
||||
"game": "Game Name"
|
||||
}
|
||||
```
|
||||
|
||||
with the following optional version fields using the format `"1.0.0"` to represent major.minor.build:
|
||||
* `minimum_ap_version` and `maximum_ap_version` - which if present will each be compared against the current
|
||||
Archipelago version respectively to filter those files from being loaded
|
||||
* `world_version` - an arbitrary version for that world in order to only load the newest valid world.
|
||||
An apworld without a world_version is always treated as older than one with a version
|
||||
No metadata is specified yet.
|
||||
|
||||
|
||||
## Extra Data
|
||||
|
||||
@@ -352,14 +352,14 @@ direction_matching_group_lookup = {
|
||||
|
||||
Terrain matching or dungeon shuffle:
|
||||
```python
|
||||
def randomize_within_same_group(group: int) -> list[int]:
|
||||
def randomize_within_same_group(group: int) -> List[int]:
|
||||
return [group]
|
||||
identity_group_lookup = bake_target_group_lookup(world, randomize_within_same_group)
|
||||
```
|
||||
|
||||
Directional + area shuffle:
|
||||
```python
|
||||
def get_target_groups(group: int) -> list[int]:
|
||||
def get_target_groups(group: int) -> List[int]:
|
||||
# example group: LEFT | CAVE
|
||||
# example result: [RIGHT | CAVE, DOOR | CAVE]
|
||||
direction = group & Groups.DIRECTION_MASK
|
||||
|
||||
@@ -79,7 +79,7 @@ Sent to clients when they connect to an Archipelago server.
|
||||
| generator_version | [NetworkVersion](#NetworkVersion) | Object denoting the version of Archipelago which generated the multiworld. |
|
||||
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. Example: `WebHost` |
|
||||
| password | bool | Denoted whether a password is required to join this room. |
|
||||
| permissions | dict\[str, [Permission](#Permission)\] | Mapping of permission name to [Permission](#Permission), keys are: "release", "collect" and "remaining". |
|
||||
| permissions | dict\[str, [Permission](#Permission)\[int\]\] | Mapping of permission name to [Permission](#Permission), keys are: "release", "collect" and "remaining". |
|
||||
| hint_cost | int | The percentage of total locations that need to be checked to receive a hint from the server. |
|
||||
| location_check_points | int | The amount of hint points you receive per item/location check completed. |
|
||||
| games | list\[str\] | List of games present in this multiworld. |
|
||||
@@ -662,14 +662,13 @@ class SlotType(enum.IntFlag):
|
||||
An object representing static information about a slot.
|
||||
|
||||
```python
|
||||
from collections.abc import Sequence
|
||||
from typing import NamedTuple
|
||||
import typing
|
||||
from NetUtils import SlotType
|
||||
class NetworkSlot(NamedTuple):
|
||||
class NetworkSlot(typing.NamedTuple):
|
||||
name: str
|
||||
game: str
|
||||
type: SlotType
|
||||
group_members: Sequence[int] = [] # only populated if type == group
|
||||
group_members: typing.List[int] = [] # only populated if type == group
|
||||
```
|
||||
|
||||
### Permission
|
||||
@@ -687,8 +686,8 @@ class Permission(enum.IntEnum):
|
||||
### Hint
|
||||
An object representing a Hint.
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
class Hint(NamedTuple):
|
||||
import typing
|
||||
class Hint(typing.NamedTuple):
|
||||
receiving_player: int
|
||||
finding_player: int
|
||||
location: int
|
||||
|
||||
@@ -28,7 +28,7 @@ if it does not exist.
|
||||
## Global Settings
|
||||
|
||||
All non-world-specific settings are defined directly in settings.py.
|
||||
Each value needs to have a default. If the default should be `None`, annotate it using `T | None = None`.
|
||||
Each value needs to have a default. If the default should be `None`, define it as `typing.Optional` and assign `None`.
|
||||
|
||||
To access a "global" config value, with correct typing, use one of
|
||||
```python
|
||||
|
||||
+2
-10
@@ -15,10 +15,8 @@
|
||||
* Prefer [format string literals](https://peps.python.org/pep-0498/) over string concatenation,
|
||||
use single quotes inside them: `f"Like {dct['key']}"`
|
||||
* Use type annotations where possible for function signatures and class members.
|
||||
* Use type annotations where appropriate for local variables (e.g. `var: list[int] = []`, or when the
|
||||
type is hard or impossible to deduce). Clear annotations help developers look up and validate API calls.
|
||||
* Prefer new style type annotations for new code (e.g. `var: dict[str, str | int]` over
|
||||
`var: Dict[str, Union[str, int]]`).
|
||||
* Use type annotations where appropriate for local variables (e.g. `var: List[int] = []`, or when the
|
||||
type is hard or impossible to deduce.) Clear annotations help developers look up and validate API calls.
|
||||
* If a line ends with an open bracket/brace/parentheses, the matching closing bracket should be at the
|
||||
beginning of a line at the same indentation as the beginning of the line with the open bracket.
|
||||
```python
|
||||
@@ -62,9 +60,3 @@
|
||||
* Indent `case` inside `switch ` with 2 spaces.
|
||||
* Use single quotes.
|
||||
* Semicolons are required after every statement.
|
||||
|
||||
## KV
|
||||
|
||||
* Style should be defined in `.kv` as much as possible, only Python when unavailable.
|
||||
* Should follow [our Python style](#python-code) where appropriate (quotation marks, indentation).
|
||||
* When escaping a line break, add a space between code and backslash.
|
||||
|
||||
+133
-133
@@ -18,8 +18,6 @@ Current endpoints:
|
||||
- [`/room_status/<suuid:room_id>`](#roomstatus)
|
||||
- Tracker API
|
||||
- [`/tracker/<suuid:tracker>`](#tracker)
|
||||
- [`/static_tracker/<suuid:tracker>`](#statictracker)
|
||||
- [`/slot_data_tracker/<suuid:tracker>`](#slotdatatracker)
|
||||
- User API
|
||||
- [`/get_rooms`](#getrooms)
|
||||
- [`/get_seeds`](#getseeds)
|
||||
@@ -256,6 +254,8 @@ can either be viewed while on a room tracker page, or from the [room's endpoint]
|
||||
<a name=tracker></a>
|
||||
Will provide a dict of tracker data with the following keys:
|
||||
|
||||
- item_link groups and their players (`groups`)
|
||||
- Each player's slot_data (`slot_data`)
|
||||
- Each player's current alias (`aliases`)
|
||||
- Will return the name if there is none
|
||||
- A list of items each player has received as a NetworkItem (`player_items_received`)
|
||||
@@ -265,55 +265,111 @@ Will provide a dict of tracker data with the following keys:
|
||||
- The time of last activity of each player in RFC 1123 format (`activity_timers`)
|
||||
- The time of last active connection of each player in RFC 1123 format (`connection_timers`)
|
||||
- The current client status of each player (`player_status`)
|
||||
- The datapackage hash for each player (`datapackage`)
|
||||
- This hash can then be sent to the datapackage API to receive the appropriate datapackage as necessary
|
||||
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"team": 0,
|
||||
"groups": [
|
||||
{
|
||||
"slot": 5,
|
||||
"name": "testGroup",
|
||||
"members": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"name": "myCoolLink",
|
||||
"members": [
|
||||
3,
|
||||
4
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"slot_data": [
|
||||
{
|
||||
"team": 0,
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"slot_data": {
|
||||
"example_option": 1,
|
||||
"other_option": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"slot_data": {
|
||||
"example_option": 1,
|
||||
"other_option": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"alias": "Incompetence"
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"alias": "Slot_Name_2"
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"alias": "Incompetence"
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"alias": "Slot_Name_2"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"player_items_received": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"items": [
|
||||
[1, 1, 1, 0],
|
||||
[2, 2, 2, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"items": [
|
||||
[1, 1, 1, 2],
|
||||
[2, 2, 2, 0]
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"items": [
|
||||
[1, 1, 1, 0],
|
||||
[2, 2, 2, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"items": [
|
||||
[1, 1, 1, 2],
|
||||
[2, 2, 2, 0]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"player_checks_done": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"locations": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"locations": [
|
||||
1,
|
||||
2
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"locations": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"locations": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -326,132 +382,76 @@ Example:
|
||||
"hints": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"hints": [
|
||||
[1, 2, 4, 6, 0, "", 4, 0]
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"hints": [
|
||||
[1, 2, 4, 6, 0, "", 4, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"hints": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"hints": []
|
||||
}
|
||||
],
|
||||
"activity_timers": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"time": "Fri, 18 Apr 2025 20:35:45 GMT"
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"time": "Fri, 18 Apr 2025 20:42:46 GMT"
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"time": "Fri, 18 Apr 2025 20:35:45 GMT"
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"time": "Fri, 18 Apr 2025 20:42:46 GMT"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"connection_timers": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"time": "Fri, 18 Apr 2025 20:38:25 GMT"
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"time": "Fri, 18 Apr 2025 21:03:00 GMT"
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"time": "Fri, 18 Apr 2025 20:38:25 GMT"
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"time": "Fri, 18 Apr 2025 21:03:00 GMT"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"player_status": [
|
||||
{
|
||||
"team": 0,
|
||||
"player": 1,
|
||||
"status": 0
|
||||
},
|
||||
{
|
||||
"team": 0,
|
||||
"player": 2,
|
||||
"status": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `/static_tracker/<suuid:tracker>`
|
||||
<a name=statictracker></a>
|
||||
Will provide a dict of static tracker data with the following keys:
|
||||
|
||||
- item_link groups and their players (`groups`)
|
||||
- The datapackage hash for each game (`datapackage`)
|
||||
- This hash can then be sent to the datapackage API to receive the appropriate datapackage as necessary
|
||||
- The number of checks found vs. total checks available per player (`player_locations_total`)
|
||||
- Same logic as the multitracker template: found = len(player_checks_done.locations) / total = player_locations_total.total_locations (all available checks).
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"slot": 5,
|
||||
"name": "testGroup",
|
||||
"members": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"name": "myCoolLink",
|
||||
"members": [
|
||||
3,
|
||||
4
|
||||
"players": [
|
||||
{
|
||||
"player": 1,
|
||||
"status": 0
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"status": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"datapackage": {
|
||||
"Archipelago": {
|
||||
"checksum": "ac9141e9ad0318df2fa27da5f20c50a842afeecb",
|
||||
"version": 0
|
||||
},
|
||||
"The Messenger": {
|
||||
"checksum": "6991cbcda7316b65bcb072667f3ee4c4cae71c0b",
|
||||
}
|
||||
},
|
||||
"player_locations_total": [
|
||||
{
|
||||
"player": 1,
|
||||
"team" : 0,
|
||||
"total_locations": 10
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"team" : 0,
|
||||
"total_locations": 20
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### `/slot_data_tracker/<suuid:tracker>`
|
||||
<a name=slotdatatracker></a>
|
||||
Will provide a list of each player's slot_data.
|
||||
|
||||
Example:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"player": 1,
|
||||
"slot_data": {
|
||||
"example_option": 1,
|
||||
"other_option": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"player": 2,
|
||||
"slot_data": {
|
||||
"example_option": 1,
|
||||
"other_option": 2
|
||||
"version": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## User Endpoints
|
||||
@@ -554,4 +554,4 @@ Example:
|
||||
"seed_id": "a528e34c-3b4f-42a9-9f8f-00a4fd40bacb"
|
||||
}
|
||||
]
|
||||
```
|
||||
```
|
||||
|
||||
+5
-5
@@ -76,8 +76,8 @@ webhost:
|
||||
* `game_info_languages` (optional) list of strings for defining the existing game info pages your game supports. The
|
||||
documents must be prefixed with the same string as defined here. Default already has 'en'.
|
||||
|
||||
* `options_presets` (optional) `dict[str, dict[str, Any]]` where the keys are the names of the presets and the values
|
||||
are the options to be set for that preset. The options are defined as a `dict[str, Any]` where the keys are the names
|
||||
* `options_presets` (optional) `Dict[str, Dict[str, Any]]` where the keys are the names of the presets and the values
|
||||
are the options to be set for that preset. The options are defined as a `Dict[str, Any]` where the keys are the names
|
||||
of the options and the values are the values to be set for that option. These presets will be available for users to
|
||||
select from on the game's options page.
|
||||
|
||||
@@ -753,7 +753,7 @@ from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.AutoWorld import LogicMixin
|
||||
|
||||
class MyGameState(LogicMixin):
|
||||
mygame_defeatable_enemies: dict[int, set[str]] # per player
|
||||
mygame_defeatable_enemies: Dict[int, Set[str]] # per player
|
||||
|
||||
def init_mixin(self, multiworld: MultiWorld) -> None:
|
||||
# Initialize per player with the corresponding "nothing" value, such as 0 or an empty set.
|
||||
@@ -882,11 +882,11 @@ item/location pairs is unnecessary since the AP server already retains and freel
|
||||
that request it. The most common usage of slot data is sending option results that the client needs to be aware of.
|
||||
|
||||
```python
|
||||
def fill_slot_data(self) -> dict[str, Any]:
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
# In order for our game client to handle the generated seed correctly we need to know what the user selected
|
||||
# for their difficulty and final boss HP.
|
||||
# A dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting.
|
||||
# The options dataclass has a method to return a `dict[str, Any]` of each option name provided and the relevant
|
||||
# The options dataclass has a method to return a `Dict[str, Any]` of each option name provided and the relevant
|
||||
# option's value.
|
||||
return self.options.as_dict("difficulty", "final_boss_hp")
|
||||
```
|
||||
|
||||
@@ -22,7 +22,7 @@ SNI_VERSION = "v0.0.100" # change back to "latest" once tray icon issues are fi
|
||||
|
||||
|
||||
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
|
||||
requirement = 'cx-Freeze==8.4.0'
|
||||
requirement = 'cx-Freeze==8.0.0'
|
||||
try:
|
||||
import pkg_resources
|
||||
try:
|
||||
@@ -371,8 +371,6 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
os.makedirs(self.buildfolder / "Players" / "Templates", exist_ok=True)
|
||||
from Options import generate_yaml_templates
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
from worlds.Files import APWorldContainer
|
||||
from Utils import version
|
||||
assert not non_apworlds - set(AutoWorldRegister.world_types), \
|
||||
f"Unknown world {non_apworlds - set(AutoWorldRegister.world_types)} designated for .apworld"
|
||||
folders_to_remove: list[str] = []
|
||||
@@ -381,26 +379,13 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
if worldname not in non_apworlds:
|
||||
file_name = os.path.split(os.path.dirname(worldtype.__file__))[1]
|
||||
world_directory = self.libfolder / "worlds" / file_name
|
||||
if os.path.isfile(world_directory / "archipelago.json"):
|
||||
manifest = json.load(open(world_directory / "archipelago.json"))
|
||||
else:
|
||||
manifest = {}
|
||||
# this method creates an apworld that cannot be moved to a different OS or minor python version,
|
||||
# which should be ok
|
||||
zip_path = self.libfolder / "worlds" / (file_name + ".apworld")
|
||||
apworld = APWorldContainer(str(zip_path))
|
||||
apworld.minimum_ap_version = version
|
||||
apworld.maximum_ap_version = version
|
||||
apworld.game = worldtype.game
|
||||
manifest.update(apworld.get_manifest())
|
||||
apworld.manifest_path = f"{file_name}/archipelago.json"
|
||||
with zipfile.ZipFile(zip_path, "x", zipfile.ZIP_DEFLATED,
|
||||
with zipfile.ZipFile(self.libfolder / "worlds" / (file_name + ".apworld"), "x", zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9) as zf:
|
||||
for path in world_directory.rglob("*.*"):
|
||||
relative_path = os.path.join(*path.parts[path.parts.index("worlds")+1:])
|
||||
if not relative_path.endswith("archipelago.json"):
|
||||
zf.write(path, relative_path)
|
||||
zf.writestr(apworld.manifest_path, json.dumps(manifest))
|
||||
zf.write(path, relative_path)
|
||||
folders_to_remove.append(file_name)
|
||||
shutil.rmtree(world_directory)
|
||||
shutil.copyfile("meta.yaml", self.buildfolder / "Players" / "Templates" / "meta.yaml")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from argparse import Namespace
|
||||
from typing import Any, List, Optional, Tuple, Type
|
||||
from typing import List, Optional, Tuple, Type, Union
|
||||
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
|
||||
from worlds import network_data_package
|
||||
@@ -31,8 +31,8 @@ def setup_solo_multiworld(
|
||||
return setup_multiworld(world_type, steps, seed)
|
||||
|
||||
|
||||
def setup_multiworld(worlds: list[type[World]] | type[World], steps: tuple[str, ...] = gen_steps,
|
||||
seed: int | None = None, options: dict[str, Any] | list[dict[str, Any]] = None) -> MultiWorld:
|
||||
def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps,
|
||||
seed: Optional[int] = None) -> MultiWorld:
|
||||
"""
|
||||
Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and
|
||||
calling the provided gen steps.
|
||||
@@ -40,27 +40,20 @@ def setup_multiworld(worlds: list[type[World]] | type[World], steps: tuple[str,
|
||||
:param worlds: Type/s of worlds to generate a multiworld for
|
||||
:param steps: Gen steps that should be called before returning. Default calls through pre_fill
|
||||
:param seed: The seed to be used when creating this multiworld
|
||||
:param options: Options to set on each world. If just one dict of options is passed, it will be used for all worlds.
|
||||
:return: The generated multiworld
|
||||
"""
|
||||
if not isinstance(worlds, list):
|
||||
worlds = [worlds]
|
||||
|
||||
if options is None:
|
||||
options = [{}] * len(worlds)
|
||||
elif not isinstance(options, list):
|
||||
options = [options] * len(worlds)
|
||||
|
||||
players = len(worlds)
|
||||
multiworld = MultiWorld(players)
|
||||
multiworld.game = {player: world_type.game for player, world_type in enumerate(worlds, 1)}
|
||||
multiworld.player_name = {player: f"Tester{player}" for player in multiworld.player_ids}
|
||||
multiworld.set_seed(seed)
|
||||
args = Namespace()
|
||||
for player, (world_type, option_overrides) in enumerate(zip(worlds, options), 1):
|
||||
for player, world_type in enumerate(worlds, 1):
|
||||
for key, option in world_type.options_dataclass.type_hints.items():
|
||||
updated_options = getattr(args, key, {})
|
||||
updated_options[player] = option.from_any(option_overrides.get(key, option.default))
|
||||
updated_options[player] = option.from_any(option.default)
|
||||
setattr(args, key, updated_options)
|
||||
multiworld.set_options(args)
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
|
||||
@@ -93,13 +93,3 @@ class TestTracker(TestBase):
|
||||
headers={"If-Modified-Since": "Wed, 21 Oct 2015 07:28:00"}, # missing timezone
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_tracker_api(self) -> None:
|
||||
"""Verify that tracker api gives a reply for the room."""
|
||||
with self.app.test_request_context():
|
||||
with self.client.open(url_for("api.tracker_data", tracker=self.tracker_uuid)) as response:
|
||||
self.assertEqual(response.status_code, 200)
|
||||
with self.client.open(url_for("api.static_tracker_data", tracker=self.tracker_uuid)) as response:
|
||||
self.assertEqual(response.status_code, 200)
|
||||
with self.client.open(url_for("api.tracker_slot_data", tracker=self.tracker_uuid)) as response:
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
+1
-7
@@ -12,7 +12,7 @@ from typing import (Any, Callable, ClassVar, Dict, FrozenSet, Iterable, List, Ma
|
||||
|
||||
from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions
|
||||
from BaseClasses import CollectionState
|
||||
from Utils import Version
|
||||
from Utils import deprecate
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance
|
||||
@@ -75,10 +75,6 @@ class AutoWorldRegister(type):
|
||||
if "required_client_version" in base.__dict__:
|
||||
dct["required_client_version"] = max(dct["required_client_version"],
|
||||
base.__dict__["required_client_version"])
|
||||
if "world_version" in dct:
|
||||
if dct["world_version"] != Version(0, 0, 0):
|
||||
raise RuntimeError(f"{name} is attempting to set 'world_version' from within the class. world_version "
|
||||
f"can only be set from manifest.")
|
||||
|
||||
# construct class
|
||||
new_class = super().__new__(mcs, name, bases, dct)
|
||||
@@ -341,8 +337,6 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""If loaded from a .apworld, this is the Path to it."""
|
||||
__file__: ClassVar[str]
|
||||
"""path it was loaded from"""
|
||||
world_version: ClassVar[Version] = Version(0, 0, 0)
|
||||
"""Optional world version loaded from archipelago.json"""
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
assert multiworld is not None
|
||||
|
||||
+9
-50
@@ -8,8 +8,7 @@ import os
|
||||
import threading
|
||||
from io import BytesIO
|
||||
|
||||
from typing import (ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload, Sequence,
|
||||
TYPE_CHECKING)
|
||||
from typing import ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload, Sequence
|
||||
|
||||
import bsdiff4
|
||||
|
||||
@@ -17,9 +16,6 @@ semaphore = threading.Semaphore(os.cpu_count() or 4)
|
||||
|
||||
del threading
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from Utils import Version
|
||||
|
||||
|
||||
class AutoPatchRegister(abc.ABCMeta):
|
||||
patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {}
|
||||
@@ -69,7 +65,7 @@ class AutoPatchExtensionRegister(abc.ABCMeta):
|
||||
return handler
|
||||
|
||||
|
||||
container_version: int = 7
|
||||
container_version: int = 6
|
||||
|
||||
|
||||
def is_ap_player_container(game: str, data: bytes, player: int):
|
||||
@@ -96,7 +92,7 @@ class APContainer:
|
||||
version: ClassVar[int] = container_version
|
||||
compression_level: ClassVar[int] = 9
|
||||
compression_method: ClassVar[int] = zipfile.ZIP_DEFLATED
|
||||
manifest_path: str = "archipelago.json"
|
||||
|
||||
path: Optional[str]
|
||||
|
||||
def __init__(self, path: Optional[str] = None):
|
||||
@@ -120,7 +116,7 @@ class APContainer:
|
||||
except Exception as e:
|
||||
raise Exception(f"Manifest {manifest} did not convert to json.") from e
|
||||
else:
|
||||
opened_zipfile.writestr(self.manifest_path, manifest_str)
|
||||
opened_zipfile.writestr("archipelago.json", manifest_str)
|
||||
|
||||
def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None:
|
||||
"""Read data into patch object. file can be file-like, such as an outer zip file's stream."""
|
||||
@@ -141,18 +137,7 @@ class APContainer:
|
||||
raise InvalidDataError(f"{message}This might be the incorrect world version for this file") from e
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
try:
|
||||
assert self.manifest_path.endswith("archipelago.json"), "Filename should be archipelago.json"
|
||||
manifest_info = opened_zipfile.getinfo(self.manifest_path)
|
||||
except KeyError as e:
|
||||
for info in opened_zipfile.infolist():
|
||||
if info.filename.endswith("archipelago.json"):
|
||||
manifest_info = info
|
||||
self.manifest_path = info.filename
|
||||
break
|
||||
else:
|
||||
raise e
|
||||
with opened_zipfile.open(manifest_info, "r") as f:
|
||||
with opened_zipfile.open("archipelago.json", "r") as f:
|
||||
manifest = json.load(f)
|
||||
if manifest["compatible_version"] > self.version:
|
||||
raise Exception(f"File (version: {manifest['compatible_version']}) too new "
|
||||
@@ -167,33 +152,6 @@ class APContainer:
|
||||
}
|
||||
|
||||
|
||||
class APWorldContainer(APContainer):
|
||||
"""A zipfile containing a world implementation."""
|
||||
game: str | None = None
|
||||
world_version: "Version | None" = None
|
||||
minimum_ap_version: "Version | None" = None
|
||||
maximum_ap_version: "Version | None" = None
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
from Utils import tuplize_version, Version
|
||||
manifest = super().read_contents(opened_zipfile)
|
||||
self.game = manifest["game"]
|
||||
for version_key in ("world_version", "minimum_ap_version", "maximum_ap_version"):
|
||||
if version_key in manifest:
|
||||
setattr(self, version_key, Version(*tuplize_version(manifest[version_key])))
|
||||
return manifest
|
||||
|
||||
def get_manifest(self) -> Dict[str, Any]:
|
||||
manifest = super().get_manifest()
|
||||
manifest["game"] = self.game
|
||||
manifest["compatible_version"] = 7
|
||||
for version_key in ("world_version", "minimum_ap_version", "maximum_ap_version"):
|
||||
version = getattr(self, version_key)
|
||||
if version:
|
||||
manifest[version_key] = version.as_simple_string()
|
||||
return manifest
|
||||
|
||||
|
||||
class APPlayerContainer(APContainer):
|
||||
"""A zipfile containing at least archipelago.json meant for a player"""
|
||||
game: ClassVar[Optional[str]] = None
|
||||
@@ -290,8 +248,10 @@ class APProcedurePatch(APAutoPatchInterface):
|
||||
manifest["compatible_version"] = 5
|
||||
return manifest
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
manifest = super(APProcedurePatch, self).read_contents(opened_zipfile)
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||
super(APProcedurePatch, self).read_contents(opened_zipfile)
|
||||
with opened_zipfile.open("archipelago.json", "r") as f:
|
||||
manifest = json.load(f)
|
||||
if "procedure" not in manifest:
|
||||
# support patching files made before moving to procedures
|
||||
self.procedure = [("apply_bsdiff4", ["delta.bsdiff4"])]
|
||||
@@ -300,7 +260,6 @@ class APProcedurePatch(APAutoPatchInterface):
|
||||
for file in opened_zipfile.namelist():
|
||||
if file not in ["archipelago.json"]:
|
||||
self.files[file] = opened_zipfile.read(file)
|
||||
return manifest
|
||||
|
||||
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||
super(APProcedurePatch, self).write_contents(opened_zipfile)
|
||||
|
||||
@@ -5,7 +5,7 @@ import weakref
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, Callable, List, Iterable, Tuple
|
||||
|
||||
from Utils import local_path, open_filename, is_frozen, is_kivy_running
|
||||
from Utils import local_path, open_filename
|
||||
|
||||
|
||||
class Type(Enum):
|
||||
@@ -177,9 +177,10 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
|
||||
if module_name == loaded_name:
|
||||
found_already_loaded = True
|
||||
break
|
||||
if found_already_loaded and is_kivy_running():
|
||||
raise Exception(f"Installed APWorld successfully, but '{module_name}' is already loaded, "
|
||||
"so a Launcher restart is required to use the new installation.")
|
||||
if found_already_loaded:
|
||||
raise Exception(f"Installed APWorld successfully, but '{module_name}' is already loaded,\n"
|
||||
"so a Launcher restart is required to use the new installation.\n"
|
||||
"If the Launcher is not open, no action needs to be taken.")
|
||||
world_source = worlds.WorldSource(str(target), is_zip=True)
|
||||
bisect.insort(worlds.world_sources, world_source)
|
||||
world_source.load()
|
||||
@@ -196,7 +197,7 @@ def install_apworld(apworld_path: str = "") -> None:
|
||||
source, target = res
|
||||
except Exception as e:
|
||||
import Utils
|
||||
Utils.messagebox("Notice", str(e), error=True)
|
||||
Utils.messagebox(e.__class__.__name__, str(e), error=True)
|
||||
logging.exception(e)
|
||||
else:
|
||||
import Utils
|
||||
@@ -242,39 +243,3 @@ icon_paths = {
|
||||
'icon': local_path('data', 'icon.png'),
|
||||
'discord': local_path('data', 'discord-mark-blue.png'),
|
||||
}
|
||||
|
||||
if not is_frozen():
|
||||
def _build_apworlds():
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from worlds import AutoWorldRegister
|
||||
from worlds.Files import APWorldContainer
|
||||
|
||||
apworlds_folder = os.path.join("build", "apworlds")
|
||||
os.makedirs(apworlds_folder, exist_ok=True)
|
||||
for worldname, worldtype in AutoWorldRegister.world_types.items():
|
||||
file_name = os.path.split(os.path.dirname(worldtype.__file__))[1]
|
||||
world_directory = os.path.join("worlds", file_name)
|
||||
if os.path.isfile(os.path.join(world_directory, "archipelago.json")):
|
||||
manifest = json.load(open(os.path.join(world_directory, "archipelago.json")))
|
||||
else:
|
||||
manifest = {}
|
||||
|
||||
zip_path = os.path.join(apworlds_folder, file_name + ".apworld")
|
||||
apworld = APWorldContainer(str(zip_path))
|
||||
apworld.game = worldtype.game
|
||||
manifest.update(apworld.get_manifest())
|
||||
apworld.manifest_path = f"{file_name}/archipelago.json"
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9) as zf:
|
||||
for path in pathlib.Path(world_directory).rglob("*.*"):
|
||||
relative_path = os.path.join(*path.parts[path.parts.index("worlds") + 1:])
|
||||
if "__MACOSX" in relative_path or ".DS_STORE" in relative_path or "__pycache__" in relative_path:
|
||||
continue
|
||||
if not relative_path.endswith("archipelago.json"):
|
||||
zf.write(path, relative_path)
|
||||
zf.writestr(apworld.manifest_path, json.dumps(manifest))
|
||||
|
||||
components.append(Component('Build apworlds', func=_build_apworlds, cli=True,))
|
||||
|
||||
+4
-88
@@ -7,11 +7,10 @@ import warnings
|
||||
import zipimport
|
||||
import time
|
||||
import dataclasses
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
from NetUtils import DataPackage
|
||||
from Utils import local_path, user_path, Version, version_tuple, tuplize_version
|
||||
from Utils import local_path, user_path
|
||||
|
||||
local_folder = os.path.dirname(__file__)
|
||||
user_folder = user_path("worlds") if user_path() != local_path() else user_path("custom_worlds")
|
||||
@@ -39,7 +38,6 @@ class WorldSource:
|
||||
is_zip: bool = False
|
||||
relative: bool = True # relative to regular world import folder
|
||||
time_taken: float = -1.0
|
||||
version: Version = Version(0, 0, 0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
|
||||
@@ -104,94 +102,12 @@ for folder in (folder for folder in (user_folder, local_folder) if folder):
|
||||
|
||||
# import all submodules to trigger AutoWorldRegister
|
||||
world_sources.sort()
|
||||
apworlds: list[WorldSource] = []
|
||||
for world_source in world_sources:
|
||||
# load all loose files first:
|
||||
if world_source.is_zip:
|
||||
apworlds.append(world_source)
|
||||
else:
|
||||
world_source.load()
|
||||
|
||||
|
||||
from .AutoWorld import AutoWorldRegister
|
||||
|
||||
for world_source in world_sources:
|
||||
if not world_source.is_zip:
|
||||
# look for manifest
|
||||
manifest = {}
|
||||
for dirpath, dirnames, filenames in os.walk(world_source.resolved_path):
|
||||
for file in filenames:
|
||||
if file.endswith("archipelago.json"):
|
||||
manifest = json.load(open(os.path.join(dirpath, file), "r"))
|
||||
break
|
||||
if manifest:
|
||||
break
|
||||
game = manifest.get("game")
|
||||
if game in AutoWorldRegister.world_types:
|
||||
AutoWorldRegister.world_types[game].world_version = Version(*tuplize_version(manifest.get("world_version",
|
||||
"0.0.0")))
|
||||
|
||||
if apworlds:
|
||||
# encapsulation for namespace / gc purposes
|
||||
def load_apworlds() -> None:
|
||||
global apworlds
|
||||
from .Files import APWorldContainer, InvalidDataError
|
||||
core_compatible: list[tuple[WorldSource, APWorldContainer]] = []
|
||||
|
||||
def fail_world(game_name: str, reason: str, add_as_failed_to_load: bool = True) -> None:
|
||||
if add_as_failed_to_load:
|
||||
failed_world_loads.append(game_name)
|
||||
logging.warning(reason)
|
||||
|
||||
for apworld_source in apworlds:
|
||||
apworld: APWorldContainer = APWorldContainer(apworld_source.resolved_path)
|
||||
# populate metadata
|
||||
try:
|
||||
apworld.read()
|
||||
except InvalidDataError as e:
|
||||
if version_tuple < (0, 7, 0):
|
||||
logging.error(
|
||||
f"Invalid or missing manifest file for {apworld_source.resolved_path}. "
|
||||
"This apworld will stop working with Archipelago 0.7.0."
|
||||
)
|
||||
logging.error(e)
|
||||
else:
|
||||
raise e
|
||||
|
||||
if apworld.minimum_ap_version and apworld.minimum_ap_version > version_tuple:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its minimum core version {apworld.minimum_ap_version} "
|
||||
f"is higher than current core version {version_tuple}.")
|
||||
elif apworld.maximum_ap_version and apworld.maximum_ap_version < version_tuple:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its maximum core version {apworld.maximum_ap_version} "
|
||||
f"is lower than current core version {version_tuple}.")
|
||||
else:
|
||||
core_compatible.append((apworld_source, apworld))
|
||||
# load highest version first
|
||||
core_compatible.sort(
|
||||
key=lambda element: element[1].world_version if element[1].world_version else Version(0, 0, 0),
|
||||
reverse=True)
|
||||
for apworld_source, apworld in core_compatible:
|
||||
if apworld.game and apworld.game in AutoWorldRegister.world_types:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its game {apworld.game} is already loaded.",
|
||||
add_as_failed_to_load=False)
|
||||
else:
|
||||
apworld_source.load()
|
||||
if apworld.game in AutoWorldRegister.world_types:
|
||||
# world could fail to load at this point
|
||||
if apworld.world_version:
|
||||
AutoWorldRegister.world_types[apworld.game].world_version = apworld.world_version
|
||||
load_apworlds()
|
||||
del load_apworlds
|
||||
|
||||
del apworlds
|
||||
world_source.load()
|
||||
|
||||
# Build the data package for each game.
|
||||
from .AutoWorld import AutoWorldRegister
|
||||
|
||||
network_data_package: DataPackage = {
|
||||
"games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()},
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Bumper Stickers",
|
||||
"authors": ["KewlioMZX"],
|
||||
"world_version": "1.0.0",
|
||||
"minimum_ap_version": "0.6.4"
|
||||
}
|
||||
@@ -232,9 +232,11 @@ def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) ->
|
||||
# Connect the Regions by named Entrances that must have access Rules
|
||||
menu_region.connect(start_camp_region)
|
||||
menu_region.connect(tony_tiddle_mission_region)
|
||||
menu_region.connect(barn_region, "Barn Door")
|
||||
menu_region.connect(barn_region)
|
||||
tony_tiddle_mission_region.connect(barn_region, "Barn Door")
|
||||
menu_region.connect(candice_mission_region)
|
||||
menu_region.connect(tutorial_house_region, "Tutorial House Door")
|
||||
menu_region.connect(tutorial_house_region)
|
||||
candice_mission_region.connect(tutorial_house_region, "Tutorial House Door")
|
||||
menu_region.connect(swamp_edges_region)
|
||||
menu_region.connect(swamp_mission_region)
|
||||
menu_region.connect(junkyard_area_region)
|
||||
@@ -242,6 +244,7 @@ def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) ->
|
||||
menu_region.connect(junkyard_shed_region)
|
||||
menu_region.connect(military_base_region)
|
||||
menu_region.connect(south_mine_outside_region)
|
||||
menu_region.connect(south_mine_inside_region)
|
||||
south_mine_outside_region.connect(south_mine_inside_region, "South Mine Gate")
|
||||
menu_region.connect(middle_station_region)
|
||||
menu_region.connect(canyon_region)
|
||||
@@ -255,11 +258,13 @@ def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) ->
|
||||
menu_region.connect(lost_stairs_region)
|
||||
menu_region.connect(east_house_region)
|
||||
menu_region.connect(rockets_testing_ground_region)
|
||||
menu_region.connect(rockets_testing_bunker_region)
|
||||
rockets_testing_ground_region.connect(rockets_testing_bunker_region, "Stuck Bunker Door")
|
||||
menu_region.connect(workshop_region)
|
||||
menu_region.connect(east_tower_region)
|
||||
menu_region.connect(lighthouse_region)
|
||||
menu_region.connect(north_mine_outside_region)
|
||||
menu_region.connect(north_mine_inside_region)
|
||||
north_mine_outside_region.connect(north_mine_inside_region, "North Mine Gate")
|
||||
menu_region.connect(wood_bridge_region)
|
||||
menu_region.connect(museum_region)
|
||||
@@ -273,9 +278,11 @@ def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) ->
|
||||
menu_region.connect(north_beach_region)
|
||||
menu_region.connect(mine_shaft_region)
|
||||
menu_region.connect(mob_camp_region)
|
||||
menu_region.connect(mob_camp_locked_room_region)
|
||||
mob_camp_region.connect(mob_camp_locked_room_region, "Mob Camp Locked Door")
|
||||
menu_region.connect(mine_elevator_exit_region)
|
||||
menu_region.connect(mountain_ruin_outside_region)
|
||||
menu_region.connect(mountain_ruin_inside_region)
|
||||
mountain_ruin_outside_region.connect(mountain_ruin_inside_region, "Mountain Ruin Gate")
|
||||
menu_region.connect(prism_temple_region)
|
||||
menu_region.connect(pickle_val_region)
|
||||
|
||||
@@ -59,19 +59,6 @@ class FactorioCommandProcessor(ClientCommandProcessor):
|
||||
def _cmd_toggle_chat(self):
|
||||
"""Toggle sending of chat messages from players on the Factorio server to Archipelago."""
|
||||
self.ctx.toggle_bridge_chat_out()
|
||||
|
||||
def _cmd_rcon_reconnect(self) -> bool:
|
||||
"""Reconnect the RCON client if its disconnected."""
|
||||
try:
|
||||
result = self.ctx.rcon_client.send_command("/help")
|
||||
if result:
|
||||
self.output("RCON Client already connected.")
|
||||
return True
|
||||
except factorio_rcon.RCONNetworkError:
|
||||
self.ctx.rcon_client = factorio_rcon.RCONClient("localhost", self.ctx.rcon_port, self.ctx.rcon_password, timeout=5)
|
||||
self.output("RCON Client successfully reconnected.")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FactorioContext(CommonContext):
|
||||
@@ -255,13 +242,7 @@ async def game_watcher(ctx: FactorioContext):
|
||||
if ctx.rcon_client and time.perf_counter() > next_bridge:
|
||||
next_bridge = time.perf_counter() + 1
|
||||
ctx.awaiting_bridge = False
|
||||
try:
|
||||
data = json.loads(ctx.rcon_client.send_command("/ap-sync"))
|
||||
except factorio_rcon.RCONNotConnected:
|
||||
continue
|
||||
except factorio_rcon.RCONNetworkError:
|
||||
bridge_logger.warning("RCON Client has unexpectedly lost connection. Please issue /rcon_reconnect.")
|
||||
continue
|
||||
data = json.loads(ctx.rcon_client.send_command("/ap-sync"))
|
||||
if not ctx.auth:
|
||||
pass # auth failed, wait for new attempt
|
||||
elif data["slot_name"] != ctx.auth:
|
||||
@@ -313,13 +294,9 @@ async def game_watcher(ctx: FactorioContext):
|
||||
"cmd": "Set", "key": ctx.energylink_key, "operations":
|
||||
[{"operation": "add", "value": value}]
|
||||
}]))
|
||||
try:
|
||||
ctx.rcon_client.send_command(
|
||||
f"/ap-energylink -{value}")
|
||||
except factorio_rcon.RCONNetworkError:
|
||||
bridge_logger.warning("RCON Client has unexpectedly lost connection. Please issue /rcon_reconnect.")
|
||||
else:
|
||||
logger.debug(f"EnergyLink: Sent {format_SI_prefix(value)}J")
|
||||
ctx.rcon_client.send_command(
|
||||
f"/ap-energylink -{value}")
|
||||
logger.debug(f"EnergyLink: Sent {format_SI_prefix(value)}J")
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
@@ -81,8 +81,7 @@ are `description`, `name`, `game`, `requires`, and the name of the games you wan
|
||||
* `requires` details different requirements from the generator for the YAML to work as you expect it to. Generally this
|
||||
is good for detailing the version of Archipelago this YAML was prepared for. If it is rolled on an older version,
|
||||
options may be missing and as such it will not work as expected. If any plando is used in the file then requiring it
|
||||
here to ensure it will be used is good practice. Specific versions of custom worlds can also be required, ensuring
|
||||
that the generator is using a compatible version.
|
||||
here to ensure it will be used is good practice.
|
||||
|
||||
## Game Options
|
||||
|
||||
@@ -166,9 +165,7 @@ game:
|
||||
A Link to the Past: 10
|
||||
Timespinner: 10
|
||||
requires:
|
||||
version: 0.6.4
|
||||
game:
|
||||
A Link to the Past: 0.6.4
|
||||
version: 0.4.1
|
||||
A Link to the Past:
|
||||
accessibility: minimal
|
||||
progression_balancing: 50
|
||||
@@ -217,13 +214,12 @@ Timespinner:
|
||||
progression_balancing: 50
|
||||
item_links: # Share part of your item pool with other players.
|
||||
- name: TSAll
|
||||
item_pool:
|
||||
item_pool:
|
||||
- Everything
|
||||
local_items:
|
||||
- Twin Pyramid Key
|
||||
- Timespinner Wheel
|
||||
replacement_item: null
|
||||
skip_if_solo: true
|
||||
```
|
||||
|
||||
#### This is a fully functional yaml file that will do all the following things:
|
||||
@@ -232,7 +228,7 @@ Timespinner:
|
||||
* `name` is `Example Player` and this will be used in the server console when sending and receiving items.
|
||||
* `game` has an equal chance of being either `A Link to the Past` or `Timespinner` with a 10/20 chance for each. This is
|
||||
because each game has a weight of 10 and the total of all weights is 20.
|
||||
* `requires` is set to require Archipelago release version 0.6.4 or higher, as well as A Link to the Past version 0.6.4.
|
||||
* `requires` is set to required release version 0.3.2 or higher.
|
||||
* `accessibility` for both games is set to `minimal` which will set this seed to beatable only, so some locations and
|
||||
items may be completely inaccessible but the seed will still be completable.
|
||||
* `progression_balancing` for both games is set to 50, the default value, meaning we will likely receive important items
|
||||
@@ -266,7 +262,7 @@ Timespinner:
|
||||
* For `Timespinner` all players in the `TSAll` item link group will share their entire item pool and the `Twin Pyramid
|
||||
Key` and `Timespinner Wheel` will be forced among the worlds of those in the group. The `null` replacement item
|
||||
will, instead of forcing a specific chosen item, allow the generator to randomly pick a filler item to replace the
|
||||
player items. This item link will only be created if there are at least two players in the group.
|
||||
player items.
|
||||
* `triggers` allows us to define a trigger such that if our `smallkey_shuffle` option happens to roll the `any_world`
|
||||
result it will also ensure that `bigkey_shuffle`, `map_shuffle`, and `compass_shuffle` are also forced to the
|
||||
`any_world` result. More information on triggers can be found in the
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Jak and Daxter: The Precursor Legacy",
|
||||
"world_version": "1.0.0",
|
||||
"minimum_ap_version": "0.6.2",
|
||||
"authors": ["markustulliuscicero"]
|
||||
}
|
||||
@@ -6,8 +6,7 @@ class TradesCostNothingTest(JakAndDaxterTestBase):
|
||||
"enable_orbsanity": 2,
|
||||
"global_orbsanity_bundle_size": 10,
|
||||
"citizen_orb_trade_amount": 0,
|
||||
"oracle_orb_trade_amount": 0,
|
||||
"start_inventory": {"Power Cell": 100},
|
||||
"oracle_orb_trade_amount": 0
|
||||
}
|
||||
|
||||
def test_orb_items_are_filler(self):
|
||||
@@ -25,8 +24,7 @@ class TradesCostEverythingTest(JakAndDaxterTestBase):
|
||||
"enable_orbsanity": 2,
|
||||
"global_orbsanity_bundle_size": 10,
|
||||
"citizen_orb_trade_amount": 120,
|
||||
"oracle_orb_trade_amount": 150,
|
||||
"start_inventory": {"Power Cell": 100},
|
||||
"oracle_orb_trade_amount": 150
|
||||
}
|
||||
|
||||
def test_orb_items_are_progression(self):
|
||||
|
||||
@@ -303,6 +303,9 @@ class KDL3World(World):
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
self.stage_shuffle_enabled = self.options.stage_shuffle > 0
|
||||
goal = self.options.goal.value
|
||||
goal_location = self.multiworld.get_location(location_name.goals[goal], self.player)
|
||||
goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, self.player))
|
||||
for level in range(1, 6):
|
||||
self.multiworld.get_location(f"Level {level} Boss - Defeated", self.player) \
|
||||
.place_locked_item(
|
||||
@@ -310,6 +313,7 @@ class KDL3World(World):
|
||||
self.multiworld.get_location(f"Level {level} Boss - Purified", self.player) \
|
||||
.place_locked_item(
|
||||
KDL3Item(f"Level {level} Boss Purified", ItemClassification.progression, None, self.player))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Love-Love Rod", self.player)
|
||||
# this can technically be done at any point before generate_output
|
||||
if self.options.allow_bb:
|
||||
if self.options.allow_bb == self.options.allow_bb.option_enforced:
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from BaseClasses import ItemClassification
|
||||
from worlds.generic.Rules import set_rule, add_rule
|
||||
from .items import KDL3Item
|
||||
from .locations import location_table
|
||||
from .names import location_name, enemy_abilities, animal_friend_spawns
|
||||
from .locations import location_table
|
||||
from .options import GoalSpeed
|
||||
import typing
|
||||
|
||||
@@ -113,11 +111,6 @@ def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: t
|
||||
|
||||
|
||||
def set_rules(world: "KDL3World") -> None:
|
||||
goal = world.options.goal.value
|
||||
goal_location = world.multiworld.get_location(location_name.goals[goal], world.player)
|
||||
goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, world.player))
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has("Love-Love Rod", world.player)
|
||||
|
||||
# Level 1
|
||||
set_rule(world.multiworld.get_location(location_name.grass_land_muchi, world.player),
|
||||
lambda state: can_reach_chuchu(state, world.player))
|
||||
|
||||
+89
-97
@@ -13,6 +13,8 @@ import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
import Utils
|
||||
death_link = False
|
||||
item_num = 1
|
||||
|
||||
logger = logging.getLogger("Client")
|
||||
|
||||
@@ -32,57 +34,62 @@ class KH1ClientCommandProcessor(ClientCommandProcessor):
|
||||
def __init__(self, ctx):
|
||||
super().__init__(ctx)
|
||||
|
||||
def _cmd_slot_data(self):
|
||||
"""Prints slot data settings for the connected seed"""
|
||||
for key in self.ctx.slot_data.keys():
|
||||
if key not in ["remote_location_ids", "synthesis_item_name_byte_arrays"]:
|
||||
self.output(str(key) + ": " + str(self.ctx.slot_data[key]))
|
||||
|
||||
def _cmd_deathlink(self):
|
||||
"""If your Death Link setting is set to "Toggle", use this command to turn Death Link on and off."""
|
||||
if "death_link" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["death_link"] == "toggle":
|
||||
if self.ctx.death_link:
|
||||
self.ctx.death_link = False
|
||||
self.output(f"Death Link turned off")
|
||||
else:
|
||||
self.ctx.death_link = True
|
||||
self.output(f"Death Link turned on")
|
||||
else:
|
||||
self.output(f"'death_link' is not set to 'toggle' for this seed.")
|
||||
self.output(f"'death_link' = " + str(self.ctx.slot_data["death_link"]))
|
||||
"""Toggles Deathlink"""
|
||||
global death_link
|
||||
if death_link:
|
||||
death_link = False
|
||||
self.output(f"Death Link turned off")
|
||||
else:
|
||||
self.output(f"No 'death_link' in slot_data keys. You probably aren't connected or are playing an older seed.")
|
||||
death_link = True
|
||||
self.output(f"Death Link turned on")
|
||||
|
||||
def _cmd_communication_path(self):
|
||||
"""Opens a file browser to allow Linux users to manually set their %LOCALAPPDATA% path"""
|
||||
directory = Utils.open_directory("Select %LOCALAPPDATA% dir", "~/.local/share/Steam/steamapps/compatdata/2552430/pfx/drive_c/users/steamuser/AppData/Local")
|
||||
if directory:
|
||||
directory += "/KH1FM"
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
self.ctx.game_communication_path = directory
|
||||
def _cmd_goal(self):
|
||||
"""Prints goal setting"""
|
||||
if "goal" in self.ctx.slot_data.keys():
|
||||
self.output(str(self.ctx.slot_data["goal"]))
|
||||
else:
|
||||
self.output(self.ctx.game_communication_path)
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_eotw_unlock(self):
|
||||
"""Prints End of the World Unlock setting"""
|
||||
if "required_reports_door" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["required_reports_door"] > 13:
|
||||
self.output("Item")
|
||||
else:
|
||||
self.output(str(self.ctx.slot_data["required_reports_eotw"]) + " reports")
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_door_unlock(self):
|
||||
"""Prints Final Rest Door Unlock setting"""
|
||||
if "door" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["door"] == "reports":
|
||||
self.output(str(self.ctx.slot_data["required_reports_door"]) + " reports")
|
||||
else:
|
||||
self.output(str(self.ctx.slot_data["door"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_advanced_logic(self):
|
||||
"""Prints advanced logic setting"""
|
||||
if "advanced_logic" in self.ctx.slot_data.keys():
|
||||
self.output(str(self.ctx.slot_data["advanced_logic"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
class KH1Context(CommonContext):
|
||||
command_processor: int = KH1ClientCommandProcessor
|
||||
game = "Kingdom Hearts"
|
||||
items_handling = 0b011 # full remote except start inventory
|
||||
items_handling = 0b111 # full remote
|
||||
|
||||
def __init__(self, server_address, password):
|
||||
super(KH1Context, self).__init__(server_address, password)
|
||||
self.send_index: int = 0
|
||||
self.syncing = False
|
||||
self.awaiting_bridge = False
|
||||
self.hinted_location_ids: list[int] = []
|
||||
self.slot_data: dict = {}
|
||||
|
||||
# Moved globals into instance attributes
|
||||
self.death_link: bool = False
|
||||
self.item_num: int = 1
|
||||
self.remote_location_ids: list[int] = []
|
||||
|
||||
self.hinted_synth_location_ids = False
|
||||
self.slot_data = {}
|
||||
# self.game_communication_path: files go in this path to pass data between us and the actual game
|
||||
if "localappdata" in os.environ:
|
||||
self.game_communication_path = os.path.expandvars(r"%localappdata%/KH1FM")
|
||||
@@ -96,10 +103,6 @@ class KH1Context(CommonContext):
|
||||
os.remove(root+"/"+file)
|
||||
|
||||
async def server_auth(self, password_requested: bool = False):
|
||||
for root, dirs, files in os.walk(self.game_communication_path):
|
||||
for file in files:
|
||||
if file.find("obtain") <= -1:
|
||||
os.remove(root+"/"+file)
|
||||
if password_requested and not self.password:
|
||||
await super(KH1Context, self).server_auth(password_requested)
|
||||
await self.get_username()
|
||||
@@ -111,7 +114,8 @@ class KH1Context(CommonContext):
|
||||
for file in files:
|
||||
if file.find("obtain") <= -1:
|
||||
os.remove(root + "/" + file)
|
||||
self.item_num = 1
|
||||
global item_num
|
||||
item_num = 1
|
||||
|
||||
@property
|
||||
def endpoints(self):
|
||||
@@ -126,7 +130,8 @@ class KH1Context(CommonContext):
|
||||
for file in files:
|
||||
if file.find("obtain") <= -1:
|
||||
os.remove(root+"/"+file)
|
||||
self.item_num = 1
|
||||
global item_num
|
||||
item_num = 1
|
||||
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
if cmd in {"Connected"}:
|
||||
@@ -137,34 +142,38 @@ class KH1Context(CommonContext):
|
||||
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
|
||||
f.close()
|
||||
|
||||
# Handle Slot Data
|
||||
#Handle Slot Data
|
||||
self.slot_data = args['slot_data']
|
||||
for key in list(args['slot_data'].keys()):
|
||||
with open(os.path.join(self.game_communication_path, key + ".cfg"), 'w') as f:
|
||||
f.write(str(args['slot_data'][key]))
|
||||
f.close()
|
||||
if key == "remote_location_ids":
|
||||
self.remote_location_ids = args['slot_data'][key]
|
||||
if key == "death_link":
|
||||
if args['slot_data']["death_link"] != "off":
|
||||
self.death_link = True
|
||||
# End Handle Slot Data
|
||||
|
||||
###Support Legacy Games
|
||||
if "Required Reports" in list(args['slot_data'].keys()) and "required_reports_eotw" not in list(args['slot_data'].keys()):
|
||||
reports_required = args['slot_data']["Required Reports"]
|
||||
with open(os.path.join(self.game_communication_path, "required_reports.cfg"), 'w') as f:
|
||||
f.write(str(reports_required))
|
||||
f.close()
|
||||
###End Support Legacy Games
|
||||
|
||||
#End Handle Slot Data
|
||||
|
||||
if cmd in {"ReceivedItems"}:
|
||||
start_index = args["index"]
|
||||
if start_index != len(self.items_received):
|
||||
global item_num
|
||||
for item in args['items']:
|
||||
found = False
|
||||
item_filename = f"AP_{str(self.item_num)}.item"
|
||||
item_filename = f"AP_{str(item_num)}.item"
|
||||
for filename in os.listdir(self.game_communication_path):
|
||||
if filename == item_filename:
|
||||
found = True
|
||||
if not found:
|
||||
if (NetworkItem(*item).player == self.slot and (NetworkItem(*item).location in self.remote_location_ids) or (NetworkItem(*item).location < 0)) or NetworkItem(*item).player != self.slot:
|
||||
with open(os.path.join(self.game_communication_path, item_filename), 'w') as f:
|
||||
f.write(str(NetworkItem(*item).item) + "\n" + str(NetworkItem(*item).location) + "\n" + str(NetworkItem(*item).player))
|
||||
f.close()
|
||||
self.item_num += 1
|
||||
with open(os.path.join(self.game_communication_path, item_filename), 'w') as f:
|
||||
f.write(str(NetworkItem(*item).item) + "\n" + str(NetworkItem(*item).location) + "\n" + str(NetworkItem(*item).player))
|
||||
f.close()
|
||||
item_num = item_num + 1
|
||||
|
||||
if cmd in {"RoomUpdate"}:
|
||||
if "checked_locations" in args:
|
||||
@@ -177,39 +186,21 @@ class KH1Context(CommonContext):
|
||||
if args["type"] == "ItemSend":
|
||||
item = args["item"]
|
||||
networkItem = NetworkItem(*item)
|
||||
receiverID = args["receiving"]
|
||||
recieverID = args["receiving"]
|
||||
senderID = networkItem.player
|
||||
locationID = networkItem.location
|
||||
if receiverID == self.slot or senderID == self.slot:
|
||||
itemName = self.item_names.lookup_in_slot(networkItem.item, receiverID)[:20]
|
||||
if recieverID != self.slot and senderID == self.slot:
|
||||
itemName = self.item_names.lookup_in_slot(networkItem.item, recieverID)
|
||||
itemCategory = networkItem.flags
|
||||
receiverName = self.player_names[receiverID][:20]
|
||||
senderName = self.player_names[senderID][:20]
|
||||
message = ""
|
||||
if receiverID == self.slot and receiverID != senderID: # Item received from someone else
|
||||
message = "From " + senderName + "\n" + itemName
|
||||
elif senderID == self.slot and receiverID != senderID: # Item sent to someone else
|
||||
message = itemName + "\nTo " + receiverName
|
||||
elif locationID in self.remote_location_ids: # Found a remote item
|
||||
message = itemName
|
||||
filename = "msg"
|
||||
if message != "":
|
||||
if not os.path.exists(self.game_communication_path + "/" + filename):
|
||||
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
|
||||
f.write(message)
|
||||
f.close()
|
||||
if args["type"] == "ItemCheat":
|
||||
item = args["item"]
|
||||
networkItem = NetworkItem(*item)
|
||||
receiverID = args["receiving"]
|
||||
if receiverID == self.slot:
|
||||
itemName = self.item_names.lookup_in_slot(networkItem.item, receiverID)[:20]
|
||||
filename = "msg"
|
||||
message = "Received " + itemName + "\nfrom server"
|
||||
if not os.path.exists(self.game_communication_path + "/" + filename):
|
||||
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
|
||||
f.write(message)
|
||||
f.close()
|
||||
recieverName = self.player_names[recieverID]
|
||||
filename = "sent"
|
||||
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
|
||||
f.write(
|
||||
re.sub('[^A-Za-z0-9 ]+', '',str(itemName))[:15] + "\n"
|
||||
+ re.sub('[^A-Za-z0-9 ]+', '',str(recieverName))[:6] + "\n"
|
||||
+ str(itemCategory) + "\n"
|
||||
+ str(locationID))
|
||||
f.close()
|
||||
|
||||
def on_deathlink(self, data: dict[str, object]):
|
||||
self.last_death_link = max(data["time"], self.last_death_link)
|
||||
@@ -239,11 +230,12 @@ class KH1Context(CommonContext):
|
||||
async def game_watcher(ctx: KH1Context):
|
||||
from .Locations import lookup_id_to_name
|
||||
while not ctx.exit_event.is_set():
|
||||
if ctx.death_link and "DeathLink" not in ctx.tags:
|
||||
await ctx.update_death_link(ctx.death_link)
|
||||
if not ctx.death_link and "DeathLink" in ctx.tags:
|
||||
await ctx.update_death_link(ctx.death_link)
|
||||
if ctx.syncing is True:
|
||||
global death_link
|
||||
if death_link and "DeathLink" not in ctx.tags:
|
||||
await ctx.update_death_link(death_link)
|
||||
if not death_link and "DeathLink" in ctx.tags:
|
||||
await ctx.update_death_link(death_link)
|
||||
if ctx.syncing == True:
|
||||
sync_msg = [{'cmd': 'Sync'}]
|
||||
if ctx.locations_checked:
|
||||
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
|
||||
@@ -264,17 +256,17 @@ async def game_watcher(ctx: KH1Context):
|
||||
if st != "nil":
|
||||
if timegm(time.strptime(st, '%Y%m%d%H%M%S')) > ctx.last_death_link and int(time.time()) % int(timegm(time.strptime(st, '%Y%m%d%H%M%S'))) < 10:
|
||||
await ctx.send_death(death_text = "Sora was defeated!")
|
||||
if file.find("hint") > -1:
|
||||
hint_location_id = int(file.split("hint", -1)[1])
|
||||
if hint_location_id not in ctx.hinted_location_ids:
|
||||
if file.find("insynthshop") > -1:
|
||||
if not ctx.hinted_synth_location_ids:
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "LocationScouts",
|
||||
"locations": [hint_location_id],
|
||||
"locations": [2656401,2656402,2656403,2656404,2656405,2656406],
|
||||
"create_as_hint": 2
|
||||
}])
|
||||
ctx.hinted_location_ids.append(hint_location_id)
|
||||
ctx.hinted_synth_location_ids = True
|
||||
ctx.locations_checked = sending
|
||||
await ctx.check_locations(sending)
|
||||
message = [{"cmd": 'LocationChecks', "locations": sending}]
|
||||
await ctx.send_msgs(message)
|
||||
if not ctx.finished_game and victory:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
VANILLA_KEYBLADE_STATS = [
|
||||
{"STR": 3, "CRR": 20, "CRB": 0, "REC": 30, "MP": 0}, # Kingdom Key
|
||||
{"STR": 1, "CRR": 20, "CRB": 0, "REC": 30, "MP": 0}, # Dream Sword
|
||||
{"STR": 1, "CRR": 0, "CRB": 0, "REC": 60, "MP": 0}, # Dream Shield
|
||||
{"STR": 1, "CRR": 10, "CRB": 0, "REC": 30, "MP": 0}, # Dream Rod
|
||||
{"STR": 0, "CRR": 20, "CRB": 0, "REC": 30, "MP": 0}, # Wooden Sword
|
||||
{"STR": 5, "CRR": 10, "CRB": 0, "REC": 1, "MP": 0}, # Jungle King
|
||||
{"STR": 6, "CRR": 20, "CRB": 0, "REC": 60, "MP": 0}, # Three Wishes
|
||||
{"STR": 8, "CRR": 10, "CRB": 2, "REC": 30, "MP": 1}, # Fairy Harp
|
||||
{"STR": 7, "CRR": 40, "CRB": 0, "REC": 1, "MP": 0}, # Pumpkinhead
|
||||
{"STR": 6, "CRR": 20, "CRB": 0, "REC": 30, "MP": 1}, # Crabclaw
|
||||
{"STR": 13, "CRR": 40, "CRB": 0, "REC": 60, "MP": 0}, # Divine Rose
|
||||
{"STR": 4, "CRR": 20, "CRB": 0, "REC": 30, "MP": 2}, # Spellbinder
|
||||
{"STR": 10, "CRR": 20, "CRB": 2, "REC": 90, "MP": 0}, # Olympia
|
||||
{"STR": 10, "CRR": 20, "CRB": 0, "REC": 30, "MP": 1}, # Lionheart
|
||||
{"STR": 9, "CRR": 2, "CRB": 0, "REC": 90, "MP": -1}, # Metal Chocobo
|
||||
{"STR": 9, "CRR": 40, "CRB": 0, "REC": 1, "MP": 1}, # Oathkeeper
|
||||
{"STR": 11, "CRR": 20, "CRB": 2, "REC": 30, "MP": -1}, # Oblivion
|
||||
{"STR": 7, "CRR": 20, "CRB": 0, "REC": 1, "MP": 2}, # Lady Luck
|
||||
{"STR": 5, "CRR": 200, "CRB": 2, "REC": 1, "MP": 0}, # Wishing Star
|
||||
{"STR": 14, "CRR": 40, "CRB": 2, "REC": 90, "MP": 2}, # Ultima Weapon
|
||||
{"STR": 3, "CRR": 20, "CRB": 0, "REC": 1, "MP": 3}, # Diamond Dust
|
||||
{"STR": 8, "CRR": 10, "CRB": 16, "REC": 90, "MP": -2}, # One-Winged Angel
|
||||
]
|
||||
VANILLA_PUPPY_LOCATIONS = [
|
||||
"Traverse Town Mystical House Glide Chest",
|
||||
"Traverse Town Alleyway Behind Crates Chest",
|
||||
"Traverse Town Item Workshop Left Chest",
|
||||
"Traverse Town Secret Waterway Near Stairs Chest",
|
||||
"Wonderland Queen's Castle Hedge Right Blue Chest",
|
||||
"Wonderland Lotus Forest Nut Chest",
|
||||
"Wonderland Tea Party Garden Above Lotus Forest Entrance 1st Chest",
|
||||
"Olympus Coliseum Coliseum Gates Right Blue Trinity Chest",
|
||||
"Deep Jungle Hippo's Lagoon Center Chest",
|
||||
"Deep Jungle Vines 2 Chest",
|
||||
"Deep Jungle Waterfall Cavern Middle Chest",
|
||||
"Deep Jungle Camp Blue Trinity Chest",
|
||||
"Agrabah Cave of Wonders Treasure Room Across Platforms Chest",
|
||||
"Halloween Town Oogie's Manor Hollow Chest",
|
||||
"Neverland Pirate Ship Deck White Trinity Chest",
|
||||
"Agrabah Cave of Wonders Hidden Room Left Chest",
|
||||
"Agrabah Cave of Wonders Entrance Tall Tower Chest",
|
||||
"Agrabah Palace Gates High Opposite Palace Chest",
|
||||
"Monstro Chamber 3 Platform Above Chamber 2 Entrance Chest",
|
||||
"Wonderland Lotus Forest Through the Painting Thunder Plant Chest",
|
||||
"Hollow Bastion Grand Hall Left of Gate Chest",
|
||||
"Halloween Town Cemetery By Cat Shape Chest",
|
||||
"Halloween Town Moonlight Hill White Trinity Chest",
|
||||
"Halloween Town Guillotine Square Pumpkin Structure Right Chest",
|
||||
"Monstro Mouth High Platform Across from Boat Chest",
|
||||
"Monstro Chamber 6 Low Chest",
|
||||
"Monstro Chamber 5 Atop Barrel Chest",
|
||||
"Neverland Hold Flight 1st Chest",
|
||||
"Neverland Hold Yellow Trinity Green Chest",
|
||||
"Neverland Captain's Cabin Chest",
|
||||
"Hollow Bastion Rising Falls Floating Platform Near Save Chest",
|
||||
"Hollow Bastion Castle Gates Gravity Chest",
|
||||
"Hollow Bastion Lift Stop Outside Library Gravity Chest"
|
||||
]
|
||||
CHAR_TO_KH = {
|
||||
" ": 0x01,
|
||||
"0": 0x21,
|
||||
"1": 0x22,
|
||||
"2": 0x23,
|
||||
"3": 0x24,
|
||||
"4": 0x25,
|
||||
"5": 0x26,
|
||||
"6": 0x27,
|
||||
"7": 0x28,
|
||||
"8": 0x29,
|
||||
"9": 0x2A,
|
||||
"A": 0x2B,
|
||||
"B": 0x2C,
|
||||
"C": 0x2D,
|
||||
"D": 0x2E,
|
||||
"E": 0x2F,
|
||||
"F": 0x30,
|
||||
"G": 0x31,
|
||||
"H": 0x32,
|
||||
"I": 0x33,
|
||||
"J": 0x34,
|
||||
"K": 0x35,
|
||||
"L": 0x36,
|
||||
"M": 0x37,
|
||||
"N": 0x38,
|
||||
"O": 0x39,
|
||||
"P": 0x3A,
|
||||
"Q": 0x3B,
|
||||
"R": 0x3C,
|
||||
"S": 0x3D,
|
||||
"T": 0x3E,
|
||||
"U": 0x3F,
|
||||
"V": 0x40,
|
||||
"W": 0x41,
|
||||
"X": 0x42,
|
||||
"Y": 0x43,
|
||||
"Z": 0x44,
|
||||
"a": 0x45,
|
||||
"b": 0x46,
|
||||
"c": 0x47,
|
||||
"d": 0x48,
|
||||
"e": 0x49,
|
||||
"f": 0x4A,
|
||||
"g": 0x4B,
|
||||
"h": 0x4C,
|
||||
"i": 0x4D,
|
||||
"j": 0x4E,
|
||||
"k": 0x4F,
|
||||
"l": 0x50,
|
||||
"m": 0x51,
|
||||
"n": 0x52,
|
||||
"o": 0x53,
|
||||
"p": 0x54,
|
||||
"q": 0x55,
|
||||
"r": 0x56,
|
||||
"s": 0x57,
|
||||
"t": 0x58,
|
||||
"u": 0x59,
|
||||
"v": 0x5A,
|
||||
"w": 0x5B,
|
||||
"x": 0x5C,
|
||||
"y": 0x5D,
|
||||
"z": 0x5E
|
||||
}
|
||||
VANILLA_ABILITY_AP_COSTS = [
|
||||
{"Ability Name": "Treasure Magnet", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Combo Plus", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Air Combo Plus", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Critical Plus", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Second Wind", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Scan", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Sonic Blade", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Ars Arcanum", "AP Cost": 4, "Randomize": True},
|
||||
{"Ability Name": "Strike Raid", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Ragnarok", "AP Cost": 4, "Randomize": True},
|
||||
{"Ability Name": "Trinity Limit", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Cheer", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Vortex", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Aerial Sweep", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Counter Attack", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Blitz", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Guard", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Dodge Roll", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "MP Haste", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "MP Rage", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Second Chance", "AP Cost": 5, "Randomize": True},
|
||||
{"Ability Name": "Berserk", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Jackpot", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Lucky Strike", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Charge", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Rocket", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Tornado", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "MP Gift", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Raging Boar", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Asp's Bite", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Healing Herb", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Wind Armor", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Crescent", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Sandstorm", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Applause!", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Blazing Fury", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Icy Terror", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Bolts of Sorrow", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Ghostly Scream", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Hummingbird", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Time-Out", "AP Cost": 4, "Randomize": True},
|
||||
{"Ability Name": "Storm´s Eye", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Ferocious Lunge", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Furious Bellow", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Spiral Wave", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Thunder Potion", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Cure Potion", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Aero Potion", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Slapshot", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Sliding Dash", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Hurricane Blast", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Ripple Drive", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "Stun Impact", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Gravity Break", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Zantetsuken", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Tech Boost", "AP Cost": 2, "Randomize": True},
|
||||
{"Ability Name": "Encounter Plus", "AP Cost": 1, "Randomize": True},
|
||||
{"Ability Name": "Leaf Bracer", "AP Cost": 5, "Randomize": True},
|
||||
{"Ability Name": "Evolution", "AP Cost": 3, "Randomize": True},
|
||||
{"Ability Name": "EXP Zero", "AP Cost": 0, "Randomize": True},
|
||||
{"Ability Name": "Combo Master", "AP Cost": 3, "Randomize": True}
|
||||
]
|
||||
|
||||
WORLD_KEY_ITEMS = {
|
||||
"Footprints": "Wonderland",
|
||||
"Entry Pass": "Olympus Coliseum",
|
||||
"Slides": "Deep Jungle",
|
||||
"Crystal Trident": "Atlantica",
|
||||
"Forget-Me-Not": "Halloween Town",
|
||||
"Jack-In-The-Box": "Halloween Town",
|
||||
"Theon Vol. 6": "Hollow Bastion"
|
||||
}
|
||||
|
||||
LOGIC_BEGINNER = 0
|
||||
LOGIC_NORMAL = 5
|
||||
LOGIC_PROUD = 10
|
||||
LOGIC_MINIMAL = 15
|
||||
@@ -1,67 +0,0 @@
|
||||
import logging
|
||||
|
||||
import yaml
|
||||
import os
|
||||
import io
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, cast
|
||||
import Utils
|
||||
import zipfile
|
||||
import json
|
||||
|
||||
from .Locations import KH1Location, location_table
|
||||
|
||||
from worlds.Files import APPlayerContainer
|
||||
|
||||
|
||||
|
||||
class KH1Container(APPlayerContainer):
|
||||
game: str = 'Kingdom Hearts'
|
||||
patch_file_ending = ".zip"
|
||||
|
||||
def __init__(self, patch_data: Dict[str, str] | io.BytesIO, base_path: str = "", output_directory: str = "",
|
||||
player: Optional[int] = None, player_name: str = "", server: str = ""):
|
||||
self.patch_data = patch_data
|
||||
self.file_path = base_path
|
||||
container_path = os.path.join(output_directory, base_path + ".zip")
|
||||
super().__init__(container_path, player, player_name, server)
|
||||
|
||||
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||
for filename, text in self.patch_data.items():
|
||||
opened_zipfile.writestr(filename, text)
|
||||
super().write_contents(opened_zipfile)
|
||||
|
||||
|
||||
def generate_json(world, output_directory):
|
||||
mod_name = f"AP-{world.multiworld.seed_name}-P{world.player}-{world.multiworld.get_file_safe_player_name(world.player)}"
|
||||
mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__)
|
||||
|
||||
item_location_map = get_item_location_map(world)
|
||||
settings = get_settings(world)
|
||||
|
||||
files = {
|
||||
"item_location_map.json": json.dumps(item_location_map),
|
||||
"keyblade_stats.json": json.dumps(world.get_keyblade_stats()),
|
||||
"settings.json": json.dumps(settings),
|
||||
"ap_costs.json": json.dumps(world.get_ap_costs())
|
||||
}
|
||||
|
||||
mod = KH1Container(files, mod_dir, output_directory, world.player,
|
||||
world.multiworld.get_file_safe_player_name(world.player))
|
||||
mod.write()
|
||||
|
||||
def get_item_location_map(world):
|
||||
location_item_map = {}
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
if location.name != "Final Ansem":
|
||||
if world.player != location.item.player or (world.player == location.item.player and world.options.remote_items.current_key == "full" and (location_table[location.name].code < 2656800 or location_table[location.name].code > 2656814)):
|
||||
item_id = 2641230
|
||||
else:
|
||||
item_id = location.item.code
|
||||
location_data = location_table[location.name]
|
||||
location_id = location_data.code
|
||||
location_item_map[location_id] = item_id
|
||||
return location_item_map
|
||||
|
||||
def get_settings(world):
|
||||
settings = world.fill_slot_data()
|
||||
return settings
|
||||
+502
-325
@@ -10,341 +10,518 @@ class KH1Item(Item):
|
||||
class KH1ItemData(NamedTuple):
|
||||
category: str
|
||||
code: int
|
||||
type: str
|
||||
classification: ItemClassification = ItemClassification.filler
|
||||
max_quantity: int = 1
|
||||
weight: int = 1
|
||||
|
||||
|
||||
def get_items_by_category(category: str) -> Dict[str, KH1ItemData]:
|
||||
return {name: data for name, data in item_table.items() if data.category == category}
|
||||
item_dict: Dict[str, KH1ItemData] = {}
|
||||
for name, data in item_table.items():
|
||||
if data.category == category:
|
||||
item_dict.setdefault(name, data)
|
||||
|
||||
return item_dict
|
||||
|
||||
|
||||
item_table: Dict[str, KH1ItemData] = {
|
||||
"Potion": KH1ItemData("Item", code = 264_1001, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Hi-Potion": KH1ItemData("Item", code = 264_1002, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Ether": KH1ItemData("Item", code = 264_1003, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Elixir": KH1ItemData("Item", code = 264_1004, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"B05": KH1ItemData("Item", code = 264_1005, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Mega-Potion": KH1ItemData("Item", code = 264_1006, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Mega-Ether": KH1ItemData("Item", code = 264_1007, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Megalixir": KH1ItemData("Item", code = 264_1008, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Torn Page": KH1ItemData("Torn Pages", code = 264_1009, classification = ItemClassification.progression, type = "Item", max_quantity = 5 ),
|
||||
"Final Door Key": KH1ItemData("Key", code = 264_1010, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Destiny Islands": KH1ItemData("Worlds", code = 264_1011, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Raft Materials": KH1ItemData("Key", code = 264_1012, classification = ItemClassification.progression, type = "Item", max_quantity = 2 ),
|
||||
#"Frost Stone": KH1ItemData("Synthesis", code = 264_1013, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Lightning Stone": KH1ItemData("Synthesis", code = 264_1014, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Dazzling Stone": KH1ItemData("Synthesis", code = 264_1015, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Stormy Stone": KH1ItemData("Synthesis", code = 264_1016, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Protect Chain": KH1ItemData("Accessory", code = 264_1017, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Protera Chain": KH1ItemData("Accessory", code = 264_1018, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Protega Chain": KH1ItemData("Accessory", code = 264_1019, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Fire Ring": KH1ItemData("Accessory", code = 264_1020, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Fira Ring": KH1ItemData("Accessory", code = 264_1021, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Firaga Ring": KH1ItemData("Accessory", code = 264_1022, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Blizzard Ring": KH1ItemData("Accessory", code = 264_1023, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Blizzara Ring": KH1ItemData("Accessory", code = 264_1024, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Blizzaga Ring": KH1ItemData("Accessory", code = 264_1025, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Thunder Ring": KH1ItemData("Accessory", code = 264_1026, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Thundara Ring": KH1ItemData("Accessory", code = 264_1027, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Thundaga Ring": KH1ItemData("Accessory", code = 264_1028, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Ability Stud": KH1ItemData("Accessory", code = 264_1029, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Guard Earring": KH1ItemData("Accessory", code = 264_1030, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Master Earring": KH1ItemData("Accessory", code = 264_1031, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Chaos Ring": KH1ItemData("Accessory", code = 264_1032, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Dark Ring": KH1ItemData("Accessory", code = 264_1033, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Element Ring": KH1ItemData("Accessory", code = 264_1034, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Three Stars": KH1ItemData("Accessory", code = 264_1035, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Power Chain": KH1ItemData("Accessory", code = 264_1036, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Golem Chain": KH1ItemData("Accessory", code = 264_1037, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Titan Chain": KH1ItemData("Accessory", code = 264_1038, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Energy Bangle": KH1ItemData("Accessory", code = 264_1039, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Angel Bangle": KH1ItemData("Accessory", code = 264_1040, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Gaia Bangle": KH1ItemData("Accessory", code = 264_1041, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Magic Armlet": KH1ItemData("Accessory", code = 264_1042, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Rune Armlet": KH1ItemData("Accessory", code = 264_1043, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Atlas Armlet": KH1ItemData("Accessory", code = 264_1044, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Heartguard": KH1ItemData("Accessory", code = 264_1045, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Ribbon": KH1ItemData("Accessory", code = 264_1046, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Crystal Crown": KH1ItemData("Accessory", code = 264_1047, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Brave Warrior": KH1ItemData("Accessory", code = 264_1048, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Ifrit's Horn": KH1ItemData("Accessory", code = 264_1049, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Inferno Band": KH1ItemData("Accessory", code = 264_1050, classification = ItemClassification.useful, type = "Item", ),
|
||||
"White Fang": KH1ItemData("Accessory", code = 264_1051, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Ray of Light": KH1ItemData("Accessory", code = 264_1052, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Holy Circlet": KH1ItemData("Accessory", code = 264_1053, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Raven's Claw": KH1ItemData("Accessory", code = 264_1054, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Omega Arts": KH1ItemData("Accessory", code = 264_1055, classification = ItemClassification.useful, type = "Item", ),
|
||||
"EXP Earring": KH1ItemData("Accessory", code = 264_1056, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"A41": KH1ItemData("Accessory", code = 264_1057, classification = ItemClassification.useful, type = "Item", ),
|
||||
"EXP Ring": KH1ItemData("Accessory", code = 264_1058, classification = ItemClassification.useful, type = "Item", ),
|
||||
"EXP Bracelet": KH1ItemData("Accessory", code = 264_1059, classification = ItemClassification.useful, type = "Item", ),
|
||||
"EXP Necklace": KH1ItemData("Accessory", code = 264_1060, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Firagun Band": KH1ItemData("Accessory", code = 264_1061, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Blizzagun Band": KH1ItemData("Accessory", code = 264_1062, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Thundagun Band": KH1ItemData("Accessory", code = 264_1063, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Ifrit Belt": KH1ItemData("Accessory", code = 264_1064, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Shiva Belt": KH1ItemData("Accessory", code = 264_1065, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Ramuh Belt": KH1ItemData("Accessory", code = 264_1066, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Moogle Badge": KH1ItemData("Accessory", code = 264_1067, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Cosmic Arts": KH1ItemData("Accessory", code = 264_1068, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Royal Crown": KH1ItemData("Accessory", code = 264_1069, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Prime Cap": KH1ItemData("Accessory", code = 264_1070, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Obsidian Ring": KH1ItemData("Accessory", code = 264_1071, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"A56": KH1ItemData("Accessory", code = 264_1072, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A57": KH1ItemData("Accessory", code = 264_1073, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A58": KH1ItemData("Accessory", code = 264_1074, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A59": KH1ItemData("Accessory", code = 264_1075, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A60": KH1ItemData("Accessory", code = 264_1076, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A61": KH1ItemData("Accessory", code = 264_1077, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A62": KH1ItemData("Accessory", code = 264_1078, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A63": KH1ItemData("Accessory", code = 264_1079, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"A64": KH1ItemData("Accessory", code = 264_1080, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Kingdom Key": KH1ItemData("Keyblades", code = 264_1081, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Dream Sword": KH1ItemData("Keyblades", code = 264_1082, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Dream Shield": KH1ItemData("Keyblades", code = 264_1083, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Dream Rod": KH1ItemData("Keyblades", code = 264_1084, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Wooden Sword": KH1ItemData("Keyblades", code = 264_1085, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Jungle King": KH1ItemData("Keyblades", code = 264_1086, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Three Wishes": KH1ItemData("Keyblades", code = 264_1087, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Fairy Harp": KH1ItemData("Keyblades", code = 264_1088, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Pumpkinhead": KH1ItemData("Keyblades", code = 264_1089, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Crabclaw": KH1ItemData("Keyblades", code = 264_1090, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Divine Rose": KH1ItemData("Keyblades", code = 264_1091, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Spellbinder": KH1ItemData("Keyblades", code = 264_1092, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Olympia": KH1ItemData("Keyblades", code = 264_1093, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Lionheart": KH1ItemData("Keyblades", code = 264_1094, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Metal Chocobo": KH1ItemData("Keyblades", code = 264_1095, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Oathkeeper": KH1ItemData("Keyblades", code = 264_1096, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Oblivion": KH1ItemData("Keyblades", code = 264_1097, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Lady Luck": KH1ItemData("Keyblades", code = 264_1098, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Wishing Star": KH1ItemData("Keyblades", code = 264_1099, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Ultima Weapon": KH1ItemData("Keyblades", code = 264_1100, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Diamond Dust": KH1ItemData("Keyblades", code = 264_1101, classification = ItemClassification.useful, type = "Item", ),
|
||||
"One-Winged Angel": KH1ItemData("Keyblades", code = 264_1102, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Mage's Staff": KH1ItemData("Weapons", code = 264_1103, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Morning Star": KH1ItemData("Weapons", code = 264_1104, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Shooting Star": KH1ItemData("Weapons", code = 264_1105, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Magus Staff": KH1ItemData("Weapons", code = 264_1106, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Wisdom Staff": KH1ItemData("Weapons", code = 264_1107, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Warhammer": KH1ItemData("Weapons", code = 264_1108, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Silver Mallet": KH1ItemData("Weapons", code = 264_1109, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Grand Mallet": KH1ItemData("Weapons", code = 264_1110, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Lord Fortune": KH1ItemData("Weapons", code = 264_1111, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Violetta": KH1ItemData("Weapons", code = 264_1112, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Dream Rod (Donald)": KH1ItemData("Weapons", code = 264_1113, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Save the Queen": KH1ItemData("Weapons", code = 264_1114, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Wizard's Relic": KH1ItemData("Weapons", code = 264_1115, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Meteor Strike": KH1ItemData("Weapons", code = 264_1116, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Fantasista": KH1ItemData("Weapons", code = 264_1117, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Unused (Donald)": KH1ItemData("Weapons", code = 264_1118, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Knight's Shield": KH1ItemData("Weapons", code = 264_1119, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Mythril Shield": KH1ItemData("Weapons", code = 264_1120, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Onyx Shield": KH1ItemData("Weapons", code = 264_1121, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Stout Shield": KH1ItemData("Weapons", code = 264_1122, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Golem Shield": KH1ItemData("Weapons", code = 264_1123, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Adamant Shield": KH1ItemData("Weapons", code = 264_1124, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Smasher": KH1ItemData("Weapons", code = 264_1125, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Gigas Fist": KH1ItemData("Weapons", code = 264_1126, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Genji Shield": KH1ItemData("Weapons", code = 264_1127, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Herc's Shield": KH1ItemData("Weapons", code = 264_1128, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Dream Shield (Goofy)": KH1ItemData("Weapons", code = 264_1129, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Save the King": KH1ItemData("Weapons", code = 264_1130, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Defender": KH1ItemData("Weapons", code = 264_1131, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Mighty Shield": KH1ItemData("Weapons", code = 264_1132, classification = ItemClassification.useful, type = "Item", ),
|
||||
"Seven Elements": KH1ItemData("Weapons", code = 264_1133, classification = ItemClassification.useful, type = "Item", ),
|
||||
#"Unused (Goofy)": KH1ItemData("Weapons", code = 264_1134, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Spear": KH1ItemData("Weapons", code = 264_1135, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"No Weapon": KH1ItemData("Weapons", code = 264_1136, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Genie": KH1ItemData("Weapons", code = 264_1137, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"No Weapon": KH1ItemData("Weapons", code = 264_1138, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"No Weapon": KH1ItemData("Weapons", code = 264_1139, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Dagger": KH1ItemData("Weapons", code = 264_1140, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Claws": KH1ItemData("Weapons", code = 264_1141, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Tent": KH1ItemData("Camping", code = 264_1142, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Camping Set": KH1ItemData("Camping", code = 264_1143, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Cottage": KH1ItemData("Camping", code = 264_1144, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"C04": KH1ItemData("Camping", code = 264_1145, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"C05": KH1ItemData("Camping", code = 264_1146, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"C06": KH1ItemData("Camping", code = 264_1147, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"C07": KH1ItemData("Camping", code = 264_1148, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Wonderland": KH1ItemData("Worlds", code = 264_1149, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Olympus Coliseum": KH1ItemData("Worlds", code = 264_1150, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Deep Jungle": KH1ItemData("Worlds", code = 264_1151, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Power Up": KH1ItemData("Stat Ups", code = 264_1152, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Defense Up": KH1ItemData("Stat Ups", code = 264_1153, classification = ItemClassification.filler, type = "Item", ),
|
||||
"AP Up": KH1ItemData("Stat Ups", code = 264_1154, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Agrabah": KH1ItemData("Worlds", code = 264_1155, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Monstro": KH1ItemData("Worlds", code = 264_1156, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Atlantica": KH1ItemData("Worlds", code = 264_1157, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Fire Arts": KH1ItemData("Key", code = 264_1158, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Blizzard Arts": KH1ItemData("Key", code = 264_1159, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Thunder Arts": KH1ItemData("Key", code = 264_1160, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Cure Arts": KH1ItemData("Key", code = 264_1161, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Gravity Arts": KH1ItemData("Key", code = 264_1162, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Stop Arts": KH1ItemData("Key", code = 264_1163, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Aero Arts": KH1ItemData("Key", code = 264_1164, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Neverland": KH1ItemData("Worlds", code = 264_1165, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Halloween Town": KH1ItemData("Worlds", code = 264_1166, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Puppy": KH1ItemData("Key", code = 264_1167, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Hollow Bastion": KH1ItemData("Worlds", code = 264_1168, classification = ItemClassification.progression, type = "Item", ),
|
||||
"End of the World": KH1ItemData("Worlds", code = 264_1169, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Blue Trinity": KH1ItemData("Trinities", code = 264_1170, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Red Trinity": KH1ItemData("Trinities", code = 264_1171, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Green Trinity": KH1ItemData("Trinities", code = 264_1172, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Yellow Trinity": KH1ItemData("Trinities", code = 264_1173, classification = ItemClassification.progression, type = "Item", ),
|
||||
"White Trinity": KH1ItemData("Trinities", code = 264_1174, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Progressive Fire": KH1ItemData("Magic", code = 264_1175, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Progressive Blizzard": KH1ItemData("Magic", code = 264_1176, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Progressive Thunder": KH1ItemData("Magic", code = 264_1177, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Progressive Cure": KH1ItemData("Magic", code = 264_1178, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Progressive Gravity": KH1ItemData("Magic", code = 264_1179, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Progressive Stop": KH1ItemData("Magic", code = 264_1180, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Progressive Aero": KH1ItemData("Magic", code = 264_1181, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
"Phil Cup": KH1ItemData("Cups", code = 264_1182, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Theon Vol. 6": KH1ItemData("Key", code = 264_1183, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Pegasus Cup": KH1ItemData("Cups", code = 264_1184, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Hercules Cup": KH1ItemData("Cups", code = 264_1185, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Empty Bottle": KH1ItemData("Key", code = 264_1186, classification = ItemClassification.progression, type = "Item", max_quantity = 6 ),
|
||||
#"Old Book": KH1ItemData("Key", code = 264_1187, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Emblem Piece (Flame)": KH1ItemData("Key", code = 264_1188, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Emblem Piece (Chest)": KH1ItemData("Key", code = 264_1189, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Emblem Piece (Statue)": KH1ItemData("Key", code = 264_1190, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Emblem Piece (Fountain)": KH1ItemData("Key", code = 264_1191, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Log": KH1ItemData("DI", code = 264_1192, classification = ItemClassification.progression, type = "Item", max_quantity = 2 ),
|
||||
#"Cloth": KH1ItemData("DI", code = 264_1193, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Rope": KH1ItemData("DI", code = 264_1194, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Seagull Egg": KH1ItemData("DI", code = 264_1195, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Fish": KH1ItemData("DI", code = 264_1196, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
#"Mushroom": KH1ItemData("DI", code = 264_1197, classification = ItemClassification.progression, type = "Item", max_quantity = 3 ),
|
||||
#"Coconut": KH1ItemData("DI", code = 264_1198, classification = ItemClassification.progression, type = "Item", max_quantity = 2 ),
|
||||
#"Drinking Water": KH1ItemData("DI", code = 264_1199, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Navi-G Piece 1": KH1ItemData("Key", code = 264_1200, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Navi-G Piece 2": KH1ItemData("Key", code = 264_1201, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Navi-Gummi Unused": KH1ItemData("Key", code = 264_1202, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Navi-G Piece 3": KH1ItemData("Key", code = 264_1203, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Navi-G Piece 4": KH1ItemData("Key", code = 264_1204, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Navi-Gummi": KH1ItemData("Key", code = 264_1205, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Watergleam": KH1ItemData("Key", code = 264_1206, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Naturespark": KH1ItemData("Key", code = 264_1207, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Fireglow": KH1ItemData("Key", code = 264_1208, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Earthshine": KH1ItemData("Key", code = 264_1209, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Crystal Trident": KH1ItemData("Key", code = 264_1210, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Postcard": KH1ItemData("Key", code = 264_1211, classification = ItemClassification.progression, type = "Item", max_quantity = 10),
|
||||
#"Torn Page 1": KH1ItemData("Torn Pages", code = 264_1212, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Torn Page 2": KH1ItemData("Torn Pages", code = 264_1213, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Torn Page 3": KH1ItemData("Torn Pages", code = 264_1214, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Torn Page 4": KH1ItemData("Torn Pages", code = 264_1215, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Torn Page 5": KH1ItemData("Torn Pages", code = 264_1216, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Slides": KH1ItemData("Key", code = 264_1217, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Slide 2": KH1ItemData("Key", code = 264_1218, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Slide 3": KH1ItemData("Key", code = 264_1219, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Slide 4": KH1ItemData("Key", code = 264_1220, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Slide 5": KH1ItemData("Key", code = 264_1221, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Slide 6": KH1ItemData("Key", code = 264_1222, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Footprints": KH1ItemData("Key", code = 264_1223, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Claw Marks": KH1ItemData("Key", code = 264_1224, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Stench": KH1ItemData("Key", code = 264_1225, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"Antenna": KH1ItemData("Key", code = 264_1226, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Forget-Me-Not": KH1ItemData("Key", code = 264_1227, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Jack-In-The-Box": KH1ItemData("Key", code = 264_1228, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Entry Pass": KH1ItemData("Key", code = 264_1229, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"AP Item": KH1ItemData("Key", code = 264_1230, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Dumbo": KH1ItemData("Summons", code = 264_1231, classification = ItemClassification.progression, type = "Item", ),
|
||||
#"N41": KH1ItemData("Synthesis", code = 264_1232, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Bambi": KH1ItemData("Summons", code = 264_1233, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Genie": KH1ItemData("Summons", code = 264_1234, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Tinker Bell": KH1ItemData("Summons", code = 264_1235, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Mushu": KH1ItemData("Summons", code = 264_1236, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Simba": KH1ItemData("Summons", code = 264_1237, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Lucky Emblem": KH1ItemData("Key", code = 264_1238, classification = ItemClassification.progression, type = "Item", ),
|
||||
"Max HP Increase": KH1ItemData("Level Up", code = 264_1239, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
"Max MP Increase": KH1ItemData("Level Up", code = 264_1240, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
"Max AP Increase": KH1ItemData("Level Up", code = 264_1241, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
"Strength Increase": KH1ItemData("Level Up", code = 264_1242, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
"Defense Increase": KH1ItemData("Level Up", code = 264_1243, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
"Item Slot Increase": KH1ItemData("Limited Level Up", code = 264_1244, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
"Accessory Slot Increase": KH1ItemData("Limited Level Up", code = 264_1245, classification = ItemClassification.useful, type = "Item", max_quantity = 15),
|
||||
#"Thunder Gem": KH1ItemData("Synthesis", code = 264_1246, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Shiny Crystal": KH1ItemData("Synthesis", code = 264_1247, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Bright Shard": KH1ItemData("Synthesis", code = 264_1248, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Bright Gem": KH1ItemData("Synthesis", code = 264_1249, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Bright Crystal": KH1ItemData("Synthesis", code = 264_1250, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Mystery Goo": KH1ItemData("Synthesis", code = 264_1251, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Gale": KH1ItemData("Synthesis", code = 264_1252, classification = ItemClassification.filler, type = "Item", ),
|
||||
#"Mythril Shard": KH1ItemData("Synthesis", code = 264_1253, classification = ItemClassification.filler, type = "Item", ),
|
||||
"Mythril": KH1ItemData("Key", code = 264_1254, classification = ItemClassification.progression, type = "Item", max_quantity = 16),
|
||||
"Orichalcum": KH1ItemData("Key", code = 264_1255, classification = ItemClassification.progression, type = "Item", max_quantity = 17),
|
||||
"High Jump": KH1ItemData("Shared Abilities", code = 264_2001, classification = ItemClassification.progression, type = "Shared Ability", ),
|
||||
"Mermaid Kick": KH1ItemData("Shared Abilities", code = 264_2002, classification = ItemClassification.progression, type = "Shared Ability", ),
|
||||
"Progressive Glide": KH1ItemData("Shared Abilities", code = 264_2003, classification = ItemClassification.progression, type = "Shared Ability", max_quantity = 2 ),
|
||||
#"Superglide": KH1ItemData("Shared Abilities", code = 264_2004, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Treasure Magnet": KH1ItemData("Abilities", code = 264_3005, classification = ItemClassification.useful, type = "Ability", max_quantity = 2 ),
|
||||
"Combo Plus": KH1ItemData("Abilities", code = 264_3006, classification = ItemClassification.useful, type = "Ability", max_quantity = 4 ),
|
||||
"Air Combo Plus": KH1ItemData("Abilities", code = 264_3007, classification = ItemClassification.progression, type = "Ability", max_quantity = 2 ),
|
||||
"Critical Plus": KH1ItemData("Abilities", code = 264_3008, classification = ItemClassification.useful, type = "Ability", max_quantity = 3 ),
|
||||
#"Second Wind": KH1ItemData("Abilities", code = 264_3009, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Scan": KH1ItemData("Abilities", code = 264_3010, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Sonic Blade": KH1ItemData("Abilities", code = 264_3011, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Ars Arcanum": KH1ItemData("Abilities", code = 264_3012, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Strike Raid": KH1ItemData("Abilities", code = 264_3013, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Ragnarok": KH1ItemData("Abilities", code = 264_3014, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Trinity Limit": KH1ItemData("Abilities", code = 264_3015, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Cheer": KH1ItemData("Abilities", code = 264_3016, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Vortex": KH1ItemData("Abilities", code = 264_3017, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Aerial Sweep": KH1ItemData("Abilities", code = 264_3018, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Counterattack": KH1ItemData("Abilities", code = 264_3019, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Blitz": KH1ItemData("Abilities", code = 264_3020, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Guard": KH1ItemData("Abilities", code = 264_3021, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Dodge Roll": KH1ItemData("Abilities", code = 264_3022, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"MP Haste": KH1ItemData("Abilities", code = 264_3023, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"MP Rage": KH1ItemData("Abilities", code = 264_3024, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Second Chance": KH1ItemData("Abilities", code = 264_3025, classification = ItemClassification.progression, type = "Ability", ),
|
||||
"Berserk": KH1ItemData("Abilities", code = 264_3026, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Jackpot": KH1ItemData("Abilities", code = 264_3027, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Lucky Strike": KH1ItemData("Abilities", code = 264_3028, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Charge": KH1ItemData("Abilities", code = 264_3029, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Rocket": KH1ItemData("Abilities", code = 264_3030, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Tornado": KH1ItemData("Abilities", code = 264_3031, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"MP Gift": KH1ItemData("Abilities", code = 264_3032, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Raging Boar": KH1ItemData("Abilities", code = 264_3033, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Asp's Bite": KH1ItemData("Abilities", code = 264_3034, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Healing Herb": KH1ItemData("Abilities", code = 264_3035, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Wind Armor": KH1ItemData("Abilities", code = 264_3036, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Crescent": KH1ItemData("Abilities", code = 264_3037, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Sandstorm": KH1ItemData("Abilities", code = 264_3038, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Applause!": KH1ItemData("Abilities", code = 264_3039, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Blazing Fury": KH1ItemData("Abilities", code = 264_3040, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Icy Terror": KH1ItemData("Abilities", code = 264_3041, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Bolts of Sorrow": KH1ItemData("Abilities", code = 264_3042, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Ghostly Scream": KH1ItemData("Abilities", code = 264_3043, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Humming Bird": KH1ItemData("Abilities", code = 264_3044, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Time-Out": KH1ItemData("Abilities", code = 264_3045, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Storm's Eye": KH1ItemData("Abilities", code = 264_3046, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Ferocious Lunge": KH1ItemData("Abilities", code = 264_3047, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Furious Bellow": KH1ItemData("Abilities", code = 264_3048, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Spiral Wave": KH1ItemData("Abilities", code = 264_3049, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Thunder Potion": KH1ItemData("Abilities", code = 264_3050, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Cure Potion": KH1ItemData("Abilities", code = 264_3051, classification = ItemClassification.useful, type = "Ability", ),
|
||||
#"Aero Potion": KH1ItemData("Abilities", code = 264_3052, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Slapshot": KH1ItemData("Abilities", code = 264_3053, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Sliding Dash": KH1ItemData("Abilities", code = 264_3054, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Hurricane Blast": KH1ItemData("Abilities", code = 264_3055, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Ripple Drive": KH1ItemData("Abilities", code = 264_3056, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Stun Impact": KH1ItemData("Abilities", code = 264_3057, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Gravity Break": KH1ItemData("Abilities", code = 264_3058, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Zantetsuken": KH1ItemData("Abilities", code = 264_3059, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Tech Boost": KH1ItemData("Abilities", code = 264_3060, classification = ItemClassification.useful, type = "Ability", max_quantity = 4 ),
|
||||
"Encounter Plus": KH1ItemData("Abilities", code = 264_3061, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Leaf Bracer": KH1ItemData("Abilities", code = 264_3062, classification = ItemClassification.progression, type = "Ability", ),
|
||||
#"Evolution": KH1ItemData("Abilities", code = 264_3063, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"EXP Zero": KH1ItemData("Abilities", code = 264_3064, classification = ItemClassification.useful, type = "Ability", ),
|
||||
"Combo Master": KH1ItemData("Abilities", code = 264_3065, classification = ItemClassification.progression, type = "Ability", )
|
||||
"Victory": KH1ItemData("VIC", code = 264_0000, classification = ItemClassification.progression, ),
|
||||
"Potion": KH1ItemData("Item", code = 264_1001, classification = ItemClassification.filler, ),
|
||||
"Hi-Potion": KH1ItemData("Item", code = 264_1002, classification = ItemClassification.filler, ),
|
||||
"Ether": KH1ItemData("Item", code = 264_1003, classification = ItemClassification.filler, ),
|
||||
"Elixir": KH1ItemData("Item", code = 264_1004, classification = ItemClassification.filler, ),
|
||||
#"B05": KH1ItemData("Item", code = 264_1005, classification = ItemClassification.filler, ),
|
||||
"Mega-Potion": KH1ItemData("Item", code = 264_1006, classification = ItemClassification.filler, ),
|
||||
"Mega-Ether": KH1ItemData("Item", code = 264_1007, classification = ItemClassification.filler, ),
|
||||
"Megalixir": KH1ItemData("Item", code = 264_1008, classification = ItemClassification.filler, ),
|
||||
#"Fury Stone": KH1ItemData("Synthesis", code = 264_1009, classification = ItemClassification.filler, ),
|
||||
#"Power Stone": KH1ItemData("Synthesis", code = 264_1010, classification = ItemClassification.filler, ),
|
||||
#"Energy Stone": KH1ItemData("Synthesis", code = 264_1011, classification = ItemClassification.filler, ),
|
||||
#"Blazing Stone": KH1ItemData("Synthesis", code = 264_1012, classification = ItemClassification.filler, ),
|
||||
#"Frost Stone": KH1ItemData("Synthesis", code = 264_1013, classification = ItemClassification.filler, ),
|
||||
#"Lightning Stone": KH1ItemData("Synthesis", code = 264_1014, classification = ItemClassification.filler, ),
|
||||
#"Dazzling Stone": KH1ItemData("Synthesis", code = 264_1015, classification = ItemClassification.filler, ),
|
||||
#"Stormy Stone": KH1ItemData("Synthesis", code = 264_1016, classification = ItemClassification.filler, ),
|
||||
"Protect Chain": KH1ItemData("Accessory", code = 264_1017, classification = ItemClassification.useful, ),
|
||||
"Protera Chain": KH1ItemData("Accessory", code = 264_1018, classification = ItemClassification.useful, ),
|
||||
"Protega Chain": KH1ItemData("Accessory", code = 264_1019, classification = ItemClassification.useful, ),
|
||||
"Fire Ring": KH1ItemData("Accessory", code = 264_1020, classification = ItemClassification.useful, ),
|
||||
"Fira Ring": KH1ItemData("Accessory", code = 264_1021, classification = ItemClassification.useful, ),
|
||||
"Firaga Ring": KH1ItemData("Accessory", code = 264_1022, classification = ItemClassification.useful, ),
|
||||
"Blizzard Ring": KH1ItemData("Accessory", code = 264_1023, classification = ItemClassification.useful, ),
|
||||
"Blizzara Ring": KH1ItemData("Accessory", code = 264_1024, classification = ItemClassification.useful, ),
|
||||
"Blizzaga Ring": KH1ItemData("Accessory", code = 264_1025, classification = ItemClassification.useful, ),
|
||||
"Thunder Ring": KH1ItemData("Accessory", code = 264_1026, classification = ItemClassification.useful, ),
|
||||
"Thundara Ring": KH1ItemData("Accessory", code = 264_1027, classification = ItemClassification.useful, ),
|
||||
"Thundaga Ring": KH1ItemData("Accessory", code = 264_1028, classification = ItemClassification.useful, ),
|
||||
"Ability Stud": KH1ItemData("Accessory", code = 264_1029, classification = ItemClassification.useful, ),
|
||||
"Guard Earring": KH1ItemData("Accessory", code = 264_1030, classification = ItemClassification.useful, ),
|
||||
"Master Earring": KH1ItemData("Accessory", code = 264_1031, classification = ItemClassification.useful, ),
|
||||
"Chaos Ring": KH1ItemData("Accessory", code = 264_1032, classification = ItemClassification.useful, ),
|
||||
"Dark Ring": KH1ItemData("Accessory", code = 264_1033, classification = ItemClassification.useful, ),
|
||||
"Element Ring": KH1ItemData("Accessory", code = 264_1034, classification = ItemClassification.useful, ),
|
||||
"Three Stars": KH1ItemData("Accessory", code = 264_1035, classification = ItemClassification.useful, ),
|
||||
"Power Chain": KH1ItemData("Accessory", code = 264_1036, classification = ItemClassification.useful, ),
|
||||
"Golem Chain": KH1ItemData("Accessory", code = 264_1037, classification = ItemClassification.useful, ),
|
||||
"Titan Chain": KH1ItemData("Accessory", code = 264_1038, classification = ItemClassification.useful, ),
|
||||
"Energy Bangle": KH1ItemData("Accessory", code = 264_1039, classification = ItemClassification.useful, ),
|
||||
"Angel Bangle": KH1ItemData("Accessory", code = 264_1040, classification = ItemClassification.useful, ),
|
||||
"Gaia Bangle": KH1ItemData("Accessory", code = 264_1041, classification = ItemClassification.useful, ),
|
||||
"Magic Armlet": KH1ItemData("Accessory", code = 264_1042, classification = ItemClassification.useful, ),
|
||||
"Rune Armlet": KH1ItemData("Accessory", code = 264_1043, classification = ItemClassification.useful, ),
|
||||
"Atlas Armlet": KH1ItemData("Accessory", code = 264_1044, classification = ItemClassification.useful, ),
|
||||
"Heartguard": KH1ItemData("Accessory", code = 264_1045, classification = ItemClassification.useful, ),
|
||||
"Ribbon": KH1ItemData("Accessory", code = 264_1046, classification = ItemClassification.useful, ),
|
||||
"Crystal Crown": KH1ItemData("Accessory", code = 264_1047, classification = ItemClassification.useful, ),
|
||||
"Brave Warrior": KH1ItemData("Accessory", code = 264_1048, classification = ItemClassification.useful, ),
|
||||
"Ifrit's Horn": KH1ItemData("Accessory", code = 264_1049, classification = ItemClassification.useful, ),
|
||||
"Inferno Band": KH1ItemData("Accessory", code = 264_1050, classification = ItemClassification.useful, ),
|
||||
"White Fang": KH1ItemData("Accessory", code = 264_1051, classification = ItemClassification.useful, ),
|
||||
"Ray of Light": KH1ItemData("Accessory", code = 264_1052, classification = ItemClassification.useful, ),
|
||||
"Holy Circlet": KH1ItemData("Accessory", code = 264_1053, classification = ItemClassification.useful, ),
|
||||
"Raven's Claw": KH1ItemData("Accessory", code = 264_1054, classification = ItemClassification.useful, ),
|
||||
"Omega Arts": KH1ItemData("Accessory", code = 264_1055, classification = ItemClassification.useful, ),
|
||||
"EXP Earring": KH1ItemData("Accessory", code = 264_1056, classification = ItemClassification.useful, ),
|
||||
#"A41": KH1ItemData("Accessory", code = 264_1057, classification = ItemClassification.useful, ),
|
||||
"EXP Ring": KH1ItemData("Accessory", code = 264_1058, classification = ItemClassification.useful, ),
|
||||
"EXP Bracelet": KH1ItemData("Accessory", code = 264_1059, classification = ItemClassification.useful, ),
|
||||
"EXP Necklace": KH1ItemData("Accessory", code = 264_1060, classification = ItemClassification.useful, ),
|
||||
"Firagun Band": KH1ItemData("Accessory", code = 264_1061, classification = ItemClassification.useful, ),
|
||||
"Blizzagun Band": KH1ItemData("Accessory", code = 264_1062, classification = ItemClassification.useful, ),
|
||||
"Thundagun Band": KH1ItemData("Accessory", code = 264_1063, classification = ItemClassification.useful, ),
|
||||
"Ifrit Belt": KH1ItemData("Accessory", code = 264_1064, classification = ItemClassification.useful, ),
|
||||
"Shiva Belt": KH1ItemData("Accessory", code = 264_1065, classification = ItemClassification.useful, ),
|
||||
"Ramuh Belt": KH1ItemData("Accessory", code = 264_1066, classification = ItemClassification.useful, ),
|
||||
"Moogle Badge": KH1ItemData("Accessory", code = 264_1067, classification = ItemClassification.useful, ),
|
||||
"Cosmic Arts": KH1ItemData("Accessory", code = 264_1068, classification = ItemClassification.useful, ),
|
||||
"Royal Crown": KH1ItemData("Accessory", code = 264_1069, classification = ItemClassification.useful, ),
|
||||
"Prime Cap": KH1ItemData("Accessory", code = 264_1070, classification = ItemClassification.useful, ),
|
||||
"Obsidian Ring": KH1ItemData("Accessory", code = 264_1071, classification = ItemClassification.useful, ),
|
||||
#"A56": KH1ItemData("Accessory", code = 264_1072, classification = ItemClassification.filler, ),
|
||||
#"A57": KH1ItemData("Accessory", code = 264_1073, classification = ItemClassification.filler, ),
|
||||
#"A58": KH1ItemData("Accessory", code = 264_1074, classification = ItemClassification.filler, ),
|
||||
#"A59": KH1ItemData("Accessory", code = 264_1075, classification = ItemClassification.filler, ),
|
||||
#"A60": KH1ItemData("Accessory", code = 264_1076, classification = ItemClassification.filler, ),
|
||||
#"A61": KH1ItemData("Accessory", code = 264_1077, classification = ItemClassification.filler, ),
|
||||
#"A62": KH1ItemData("Accessory", code = 264_1078, classification = ItemClassification.filler, ),
|
||||
#"A63": KH1ItemData("Accessory", code = 264_1079, classification = ItemClassification.filler, ),
|
||||
#"A64": KH1ItemData("Accessory", code = 264_1080, classification = ItemClassification.filler, ),
|
||||
#"Kingdom Key": KH1ItemData("Keyblades", code = 264_1081, classification = ItemClassification.useful, ),
|
||||
#"Dream Sword": KH1ItemData("Keyblades", code = 264_1082, classification = ItemClassification.useful, ),
|
||||
#"Dream Shield": KH1ItemData("Keyblades", code = 264_1083, classification = ItemClassification.useful, ),
|
||||
#"Dream Rod": KH1ItemData("Keyblades", code = 264_1084, classification = ItemClassification.useful, ),
|
||||
"Wooden Sword": KH1ItemData("Keyblades", code = 264_1085, classification = ItemClassification.useful, ),
|
||||
"Jungle King": KH1ItemData("Keyblades", code = 264_1086, classification = ItemClassification.progression, ),
|
||||
"Three Wishes": KH1ItemData("Keyblades", code = 264_1087, classification = ItemClassification.progression, ),
|
||||
"Fairy Harp": KH1ItemData("Keyblades", code = 264_1088, classification = ItemClassification.progression, ),
|
||||
"Pumpkinhead": KH1ItemData("Keyblades", code = 264_1089, classification = ItemClassification.progression, ),
|
||||
"Crabclaw": KH1ItemData("Keyblades", code = 264_1090, classification = ItemClassification.useful, ),
|
||||
"Divine Rose": KH1ItemData("Keyblades", code = 264_1091, classification = ItemClassification.progression, ),
|
||||
"Spellbinder": KH1ItemData("Keyblades", code = 264_1092, classification = ItemClassification.useful, ),
|
||||
"Olympia": KH1ItemData("Keyblades", code = 264_1093, classification = ItemClassification.progression, ),
|
||||
"Lionheart": KH1ItemData("Keyblades", code = 264_1094, classification = ItemClassification.progression, ),
|
||||
"Metal Chocobo": KH1ItemData("Keyblades", code = 264_1095, classification = ItemClassification.useful, ),
|
||||
"Oathkeeper": KH1ItemData("Keyblades", code = 264_1096, classification = ItemClassification.progression, ),
|
||||
"Oblivion": KH1ItemData("Keyblades", code = 264_1097, classification = ItemClassification.progression, ),
|
||||
"Lady Luck": KH1ItemData("Keyblades", code = 264_1098, classification = ItemClassification.progression, ),
|
||||
"Wishing Star": KH1ItemData("Keyblades", code = 264_1099, classification = ItemClassification.progression, ),
|
||||
"Ultima Weapon": KH1ItemData("Keyblades", code = 264_1100, classification = ItemClassification.useful, ),
|
||||
"Diamond Dust": KH1ItemData("Keyblades", code = 264_1101, classification = ItemClassification.useful, ),
|
||||
"One-Winged Angel": KH1ItemData("Keyblades", code = 264_1102, classification = ItemClassification.useful, ),
|
||||
#"Mage's Staff": KH1ItemData("Weapons", code = 264_1103, classification = ItemClassification.filler, ),
|
||||
"Morning Star": KH1ItemData("Weapons", code = 264_1104, classification = ItemClassification.useful, ),
|
||||
"Shooting Star": KH1ItemData("Weapons", code = 264_1105, classification = ItemClassification.useful, ),
|
||||
"Magus Staff": KH1ItemData("Weapons", code = 264_1106, classification = ItemClassification.useful, ),
|
||||
"Wisdom Staff": KH1ItemData("Weapons", code = 264_1107, classification = ItemClassification.useful, ),
|
||||
"Warhammer": KH1ItemData("Weapons", code = 264_1108, classification = ItemClassification.useful, ),
|
||||
"Silver Mallet": KH1ItemData("Weapons", code = 264_1109, classification = ItemClassification.useful, ),
|
||||
"Grand Mallet": KH1ItemData("Weapons", code = 264_1110, classification = ItemClassification.useful, ),
|
||||
"Lord Fortune": KH1ItemData("Weapons", code = 264_1111, classification = ItemClassification.useful, ),
|
||||
"Violetta": KH1ItemData("Weapons", code = 264_1112, classification = ItemClassification.useful, ),
|
||||
"Dream Rod (Donald)": KH1ItemData("Weapons", code = 264_1113, classification = ItemClassification.useful, ),
|
||||
"Save the Queen": KH1ItemData("Weapons", code = 264_1114, classification = ItemClassification.useful, ),
|
||||
"Wizard's Relic": KH1ItemData("Weapons", code = 264_1115, classification = ItemClassification.useful, ),
|
||||
"Meteor Strike": KH1ItemData("Weapons", code = 264_1116, classification = ItemClassification.useful, ),
|
||||
"Fantasista": KH1ItemData("Weapons", code = 264_1117, classification = ItemClassification.useful, ),
|
||||
#"Unused (Donald)": KH1ItemData("Weapons", code = 264_1118, classification = ItemClassification.filler, ),
|
||||
#"Knight's Shield": KH1ItemData("Weapons", code = 264_1119, classification = ItemClassification.filler, ),
|
||||
"Mythril Shield": KH1ItemData("Weapons", code = 264_1120, classification = ItemClassification.useful, ),
|
||||
"Onyx Shield": KH1ItemData("Weapons", code = 264_1121, classification = ItemClassification.useful, ),
|
||||
"Stout Shield": KH1ItemData("Weapons", code = 264_1122, classification = ItemClassification.useful, ),
|
||||
"Golem Shield": KH1ItemData("Weapons", code = 264_1123, classification = ItemClassification.useful, ),
|
||||
"Adamant Shield": KH1ItemData("Weapons", code = 264_1124, classification = ItemClassification.useful, ),
|
||||
"Smasher": KH1ItemData("Weapons", code = 264_1125, classification = ItemClassification.useful, ),
|
||||
"Gigas Fist": KH1ItemData("Weapons", code = 264_1126, classification = ItemClassification.useful, ),
|
||||
"Genji Shield": KH1ItemData("Weapons", code = 264_1127, classification = ItemClassification.useful, ),
|
||||
"Herc's Shield": KH1ItemData("Weapons", code = 264_1128, classification = ItemClassification.useful, ),
|
||||
"Dream Shield (Goofy)": KH1ItemData("Weapons", code = 264_1129, classification = ItemClassification.useful, ),
|
||||
"Save the King": KH1ItemData("Weapons", code = 264_1130, classification = ItemClassification.useful, ),
|
||||
"Defender": KH1ItemData("Weapons", code = 264_1131, classification = ItemClassification.useful, ),
|
||||
"Mighty Shield": KH1ItemData("Weapons", code = 264_1132, classification = ItemClassification.useful, ),
|
||||
"Seven Elements": KH1ItemData("Weapons", code = 264_1133, classification = ItemClassification.useful, ),
|
||||
#"Unused (Goofy)": KH1ItemData("Weapons", code = 264_1134, classification = ItemClassification.filler, ),
|
||||
#"Spear": KH1ItemData("Weapons", code = 264_1135, classification = ItemClassification.filler, ),
|
||||
#"No Weapon": KH1ItemData("Weapons", code = 264_1136, classification = ItemClassification.filler, ),
|
||||
#"Genie": KH1ItemData("Weapons", code = 264_1137, classification = ItemClassification.filler, ),
|
||||
#"No Weapon": KH1ItemData("Weapons", code = 264_1138, classification = ItemClassification.filler, ),
|
||||
#"No Weapon": KH1ItemData("Weapons", code = 264_1139, classification = ItemClassification.filler, ),
|
||||
#"Tinker Bell": KH1ItemData("Weapons", code = 264_1140, classification = ItemClassification.filler, ),
|
||||
#"Claws": KH1ItemData("Weapons", code = 264_1141, classification = ItemClassification.filler, ),
|
||||
"Tent": KH1ItemData("Camping", code = 264_1142, classification = ItemClassification.filler, ),
|
||||
"Camping Set": KH1ItemData("Camping", code = 264_1143, classification = ItemClassification.filler, ),
|
||||
"Cottage": KH1ItemData("Camping", code = 264_1144, classification = ItemClassification.filler, ),
|
||||
#"C04": KH1ItemData("Camping", code = 264_1145, classification = ItemClassification.filler, ),
|
||||
#"C05": KH1ItemData("Camping", code = 264_1146, classification = ItemClassification.filler, ),
|
||||
#"C06": KH1ItemData("Camping", code = 264_1147, classification = ItemClassification.filler, ),
|
||||
#"C07": KH1ItemData("Camping", code = 264_1148, classification = ItemClassification.filler, ),
|
||||
"Ansem's Report 11": KH1ItemData("Reports", code = 264_1149, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 12": KH1ItemData("Reports", code = 264_1150, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 13": KH1ItemData("Reports", code = 264_1151, classification = ItemClassification.progression, ),
|
||||
"Power Up": KH1ItemData("Stat Ups", code = 264_1152, classification = ItemClassification.filler, ),
|
||||
"Defense Up": KH1ItemData("Stat Ups", code = 264_1153, classification = ItemClassification.filler, ),
|
||||
"AP Up": KH1ItemData("Stat Ups", code = 264_1154, classification = ItemClassification.filler, ),
|
||||
#"Serenity Power": KH1ItemData("Synthesis", code = 264_1155, classification = ItemClassification.filler, ),
|
||||
#"Dark Matter": KH1ItemData("Synthesis", code = 264_1156, classification = ItemClassification.filler, ),
|
||||
#"Mythril Stone": KH1ItemData("Synthesis", code = 264_1157, classification = ItemClassification.filler, ),
|
||||
"Fire Arts": KH1ItemData("Key", code = 264_1158, classification = ItemClassification.progression, ),
|
||||
"Blizzard Arts": KH1ItemData("Key", code = 264_1159, classification = ItemClassification.progression, ),
|
||||
"Thunder Arts": KH1ItemData("Key", code = 264_1160, classification = ItemClassification.progression, ),
|
||||
"Cure Arts": KH1ItemData("Key", code = 264_1161, classification = ItemClassification.progression, ),
|
||||
"Gravity Arts": KH1ItemData("Key", code = 264_1162, classification = ItemClassification.progression, ),
|
||||
"Stop Arts": KH1ItemData("Key", code = 264_1163, classification = ItemClassification.progression, ),
|
||||
"Aero Arts": KH1ItemData("Key", code = 264_1164, classification = ItemClassification.progression, ),
|
||||
#"Shiitank Rank": KH1ItemData("Synthesis", code = 264_1165, classification = ItemClassification.filler, ),
|
||||
#"Matsutake Rank": KH1ItemData("Synthesis", code = 264_1166, classification = ItemClassification.filler, ),
|
||||
#"Mystery Mold": KH1ItemData("Synthesis", code = 264_1167, classification = ItemClassification.filler, ),
|
||||
"Ansem's Report 1": KH1ItemData("Reports", code = 264_1168, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 2": KH1ItemData("Reports", code = 264_1169, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 3": KH1ItemData("Reports", code = 264_1170, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 4": KH1ItemData("Reports", code = 264_1171, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 5": KH1ItemData("Reports", code = 264_1172, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 6": KH1ItemData("Reports", code = 264_1173, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 7": KH1ItemData("Reports", code = 264_1174, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 8": KH1ItemData("Reports", code = 264_1175, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 9": KH1ItemData("Reports", code = 264_1176, classification = ItemClassification.progression, ),
|
||||
"Ansem's Report 10": KH1ItemData("Reports", code = 264_1177, classification = ItemClassification.progression, ),
|
||||
#"Khama Vol. 8": KH1ItemData("Key", code = 264_1178, classification = ItemClassification.progression, ),
|
||||
#"Salegg Vol. 6": KH1ItemData("Key", code = 264_1179, classification = ItemClassification.progression, ),
|
||||
#"Azal Vol. 3": KH1ItemData("Key", code = 264_1180, classification = ItemClassification.progression, ),
|
||||
#"Mava Vol. 3": KH1ItemData("Key", code = 264_1181, classification = ItemClassification.progression, ),
|
||||
#"Mava Vol. 6": KH1ItemData("Key", code = 264_1182, classification = ItemClassification.progression, ),
|
||||
"Theon Vol. 6": KH1ItemData("Key", code = 264_1183, classification = ItemClassification.progression, ),
|
||||
#"Nahara Vol. 5": KH1ItemData("Key", code = 264_1184, classification = ItemClassification.progression, ),
|
||||
#"Hafet Vol. 4": KH1ItemData("Key", code = 264_1185, classification = ItemClassification.progression, ),
|
||||
"Empty Bottle": KH1ItemData("Key", code = 264_1186, classification = ItemClassification.progression, max_quantity = 6 ),
|
||||
#"Old Book": KH1ItemData("Key", code = 264_1187, classification = ItemClassification.progression, ),
|
||||
"Emblem Piece (Flame)": KH1ItemData("Key", code = 264_1188, classification = ItemClassification.progression, ),
|
||||
"Emblem Piece (Chest)": KH1ItemData("Key", code = 264_1189, classification = ItemClassification.progression, ),
|
||||
"Emblem Piece (Statue)": KH1ItemData("Key", code = 264_1190, classification = ItemClassification.progression, ),
|
||||
"Emblem Piece (Fountain)": KH1ItemData("Key", code = 264_1191, classification = ItemClassification.progression, ),
|
||||
#"Log": KH1ItemData("Key", code = 264_1192, classification = ItemClassification.progression, ),
|
||||
#"Cloth": KH1ItemData("Key", code = 264_1193, classification = ItemClassification.progression, ),
|
||||
#"Rope": KH1ItemData("Key", code = 264_1194, classification = ItemClassification.progression, ),
|
||||
#"Seagull Egg": KH1ItemData("Key", code = 264_1195, classification = ItemClassification.progression, ),
|
||||
#"Fish": KH1ItemData("Key", code = 264_1196, classification = ItemClassification.progression, ),
|
||||
#"Mushroom": KH1ItemData("Key", code = 264_1197, classification = ItemClassification.progression, ),
|
||||
#"Coconut": KH1ItemData("Key", code = 264_1198, classification = ItemClassification.progression, ),
|
||||
#"Drinking Water": KH1ItemData("Key", code = 264_1199, classification = ItemClassification.progression, ),
|
||||
#"Navi-G Piece 1": KH1ItemData("Key", code = 264_1200, classification = ItemClassification.progression, ),
|
||||
#"Navi-G Piece 2": KH1ItemData("Key", code = 264_1201, classification = ItemClassification.progression, ),
|
||||
#"Navi-Gummi Unused": KH1ItemData("Key", code = 264_1202, classification = ItemClassification.progression, ),
|
||||
#"Navi-G Piece 3": KH1ItemData("Key", code = 264_1203, classification = ItemClassification.progression, ),
|
||||
#"Navi-G Piece 4": KH1ItemData("Key", code = 264_1204, classification = ItemClassification.progression, ),
|
||||
#"Navi-Gummi": KH1ItemData("Key", code = 264_1205, classification = ItemClassification.progression, ),
|
||||
#"Watergleam": KH1ItemData("Key", code = 264_1206, classification = ItemClassification.progression, ),
|
||||
#"Naturespark": KH1ItemData("Key", code = 264_1207, classification = ItemClassification.progression, ),
|
||||
#"Fireglow": KH1ItemData("Key", code = 264_1208, classification = ItemClassification.progression, ),
|
||||
#"Earthshine": KH1ItemData("Key", code = 264_1209, classification = ItemClassification.progression, ),
|
||||
"Crystal Trident": KH1ItemData("Key", code = 264_1210, classification = ItemClassification.progression, ),
|
||||
"Postcard": KH1ItemData("Key", code = 264_1211, classification = ItemClassification.progression, max_quantity = 10),
|
||||
"Torn Page 1": KH1ItemData("Torn Pages", code = 264_1212, classification = ItemClassification.progression, ),
|
||||
"Torn Page 2": KH1ItemData("Torn Pages", code = 264_1213, classification = ItemClassification.progression, ),
|
||||
"Torn Page 3": KH1ItemData("Torn Pages", code = 264_1214, classification = ItemClassification.progression, ),
|
||||
"Torn Page 4": KH1ItemData("Torn Pages", code = 264_1215, classification = ItemClassification.progression, ),
|
||||
"Torn Page 5": KH1ItemData("Torn Pages", code = 264_1216, classification = ItemClassification.progression, ),
|
||||
"Slides": KH1ItemData("Key", code = 264_1217, classification = ItemClassification.progression, ),
|
||||
#"Slide 2": KH1ItemData("Key", code = 264_1218, classification = ItemClassification.progression, ),
|
||||
#"Slide 3": KH1ItemData("Key", code = 264_1219, classification = ItemClassification.progression, ),
|
||||
#"Slide 4": KH1ItemData("Key", code = 264_1220, classification = ItemClassification.progression, ),
|
||||
#"Slide 5": KH1ItemData("Key", code = 264_1221, classification = ItemClassification.progression, ),
|
||||
#"Slide 6": KH1ItemData("Key", code = 264_1222, classification = ItemClassification.progression, ),
|
||||
"Footprints": KH1ItemData("Key", code = 264_1223, classification = ItemClassification.progression, ),
|
||||
#"Claw Marks": KH1ItemData("Key", code = 264_1224, classification = ItemClassification.progression, ),
|
||||
#"Stench": KH1ItemData("Key", code = 264_1225, classification = ItemClassification.progression, ),
|
||||
#"Antenna": KH1ItemData("Key", code = 264_1226, classification = ItemClassification.progression, ),
|
||||
"Forget-Me-Not": KH1ItemData("Key", code = 264_1227, classification = ItemClassification.progression, ),
|
||||
"Jack-In-The-Box": KH1ItemData("Key", code = 264_1228, classification = ItemClassification.progression, ),
|
||||
"Entry Pass": KH1ItemData("Key", code = 264_1229, classification = ItemClassification.progression, ),
|
||||
#"Hero License": KH1ItemData("Key", code = 264_1230, classification = ItemClassification.progression, ),
|
||||
#"Pretty Stone": KH1ItemData("Synthesis", code = 264_1231, classification = ItemClassification.filler, ),
|
||||
#"N41": KH1ItemData("Synthesis", code = 264_1232, classification = ItemClassification.filler, ),
|
||||
#"Lucid Shard": KH1ItemData("Synthesis", code = 264_1233, classification = ItemClassification.filler, ),
|
||||
#"Lucid Gem": KH1ItemData("Synthesis", code = 264_1234, classification = ItemClassification.filler, ),
|
||||
#"Lucid Crystal": KH1ItemData("Synthesis", code = 264_1235, classification = ItemClassification.filler, ),
|
||||
#"Spirit Shard": KH1ItemData("Synthesis", code = 264_1236, classification = ItemClassification.filler, ),
|
||||
#"Spirit Gem": KH1ItemData("Synthesis", code = 264_1237, classification = ItemClassification.filler, ),
|
||||
#"Power Shard": KH1ItemData("Synthesis", code = 264_1238, classification = ItemClassification.filler, ),
|
||||
#"Power Gem": KH1ItemData("Synthesis", code = 264_1239, classification = ItemClassification.filler, ),
|
||||
#"Power Crystal": KH1ItemData("Synthesis", code = 264_1240, classification = ItemClassification.filler, ),
|
||||
#"Blaze Shard": KH1ItemData("Synthesis", code = 264_1241, classification = ItemClassification.filler, ),
|
||||
#"Blaze Gem": KH1ItemData("Synthesis", code = 264_1242, classification = ItemClassification.filler, ),
|
||||
#"Frost Shard": KH1ItemData("Synthesis", code = 264_1243, classification = ItemClassification.filler, ),
|
||||
#"Frost Gem": KH1ItemData("Synthesis", code = 264_1244, classification = ItemClassification.filler, ),
|
||||
#"Thunder Shard": KH1ItemData("Synthesis", code = 264_1245, classification = ItemClassification.filler, ),
|
||||
#"Thunder Gem": KH1ItemData("Synthesis", code = 264_1246, classification = ItemClassification.filler, ),
|
||||
#"Shiny Crystal": KH1ItemData("Synthesis", code = 264_1247, classification = ItemClassification.filler, ),
|
||||
#"Bright Shard": KH1ItemData("Synthesis", code = 264_1248, classification = ItemClassification.filler, ),
|
||||
#"Bright Gem": KH1ItemData("Synthesis", code = 264_1249, classification = ItemClassification.filler, ),
|
||||
#"Bright Crystal": KH1ItemData("Synthesis", code = 264_1250, classification = ItemClassification.filler, ),
|
||||
#"Mystery Goo": KH1ItemData("Synthesis", code = 264_1251, classification = ItemClassification.filler, ),
|
||||
#"Gale": KH1ItemData("Synthesis", code = 264_1252, classification = ItemClassification.filler, ),
|
||||
#"Mythril Shard": KH1ItemData("Synthesis", code = 264_1253, classification = ItemClassification.filler, ),
|
||||
#"Mythril": KH1ItemData("Synthesis", code = 264_1254, classification = ItemClassification.filler, ),
|
||||
#"Orichalcum": KH1ItemData("Synthesis", code = 264_1255, classification = ItemClassification.filler, ),
|
||||
"High Jump": KH1ItemData("Shared Abilities", code = 264_2001, classification = ItemClassification.progression, ),
|
||||
"Mermaid Kick": KH1ItemData("Shared Abilities", code = 264_2002, classification = ItemClassification.progression, ),
|
||||
"Progressive Glide": KH1ItemData("Shared Abilities", code = 264_2003, classification = ItemClassification.progression, max_quantity = 2 ),
|
||||
#"Superglide": KH1ItemData("Shared Abilities", code = 264_2004, classification = ItemClassification.progression, ),
|
||||
"Puppy 01": KH1ItemData("Puppies", code = 264_2101, classification = ItemClassification.progression, ),
|
||||
"Puppy 02": KH1ItemData("Puppies", code = 264_2102, classification = ItemClassification.progression, ),
|
||||
"Puppy 03": KH1ItemData("Puppies", code = 264_2103, classification = ItemClassification.progression, ),
|
||||
"Puppy 04": KH1ItemData("Puppies", code = 264_2104, classification = ItemClassification.progression, ),
|
||||
"Puppy 05": KH1ItemData("Puppies", code = 264_2105, classification = ItemClassification.progression, ),
|
||||
"Puppy 06": KH1ItemData("Puppies", code = 264_2106, classification = ItemClassification.progression, ),
|
||||
"Puppy 07": KH1ItemData("Puppies", code = 264_2107, classification = ItemClassification.progression, ),
|
||||
"Puppy 08": KH1ItemData("Puppies", code = 264_2108, classification = ItemClassification.progression, ),
|
||||
"Puppy 09": KH1ItemData("Puppies", code = 264_2109, classification = ItemClassification.progression, ),
|
||||
"Puppy 10": KH1ItemData("Puppies", code = 264_2110, classification = ItemClassification.progression, ),
|
||||
"Puppy 11": KH1ItemData("Puppies", code = 264_2111, classification = ItemClassification.progression, ),
|
||||
"Puppy 12": KH1ItemData("Puppies", code = 264_2112, classification = ItemClassification.progression, ),
|
||||
"Puppy 13": KH1ItemData("Puppies", code = 264_2113, classification = ItemClassification.progression, ),
|
||||
"Puppy 14": KH1ItemData("Puppies", code = 264_2114, classification = ItemClassification.progression, ),
|
||||
"Puppy 15": KH1ItemData("Puppies", code = 264_2115, classification = ItemClassification.progression, ),
|
||||
"Puppy 16": KH1ItemData("Puppies", code = 264_2116, classification = ItemClassification.progression, ),
|
||||
"Puppy 17": KH1ItemData("Puppies", code = 264_2117, classification = ItemClassification.progression, ),
|
||||
"Puppy 18": KH1ItemData("Puppies", code = 264_2118, classification = ItemClassification.progression, ),
|
||||
"Puppy 19": KH1ItemData("Puppies", code = 264_2119, classification = ItemClassification.progression, ),
|
||||
"Puppy 20": KH1ItemData("Puppies", code = 264_2120, classification = ItemClassification.progression, ),
|
||||
"Puppy 21": KH1ItemData("Puppies", code = 264_2121, classification = ItemClassification.progression, ),
|
||||
"Puppy 22": KH1ItemData("Puppies", code = 264_2122, classification = ItemClassification.progression, ),
|
||||
"Puppy 23": KH1ItemData("Puppies", code = 264_2123, classification = ItemClassification.progression, ),
|
||||
"Puppy 24": KH1ItemData("Puppies", code = 264_2124, classification = ItemClassification.progression, ),
|
||||
"Puppy 25": KH1ItemData("Puppies", code = 264_2125, classification = ItemClassification.progression, ),
|
||||
"Puppy 26": KH1ItemData("Puppies", code = 264_2126, classification = ItemClassification.progression, ),
|
||||
"Puppy 27": KH1ItemData("Puppies", code = 264_2127, classification = ItemClassification.progression, ),
|
||||
"Puppy 28": KH1ItemData("Puppies", code = 264_2128, classification = ItemClassification.progression, ),
|
||||
"Puppy 29": KH1ItemData("Puppies", code = 264_2129, classification = ItemClassification.progression, ),
|
||||
"Puppy 30": KH1ItemData("Puppies", code = 264_2130, classification = ItemClassification.progression, ),
|
||||
"Puppy 31": KH1ItemData("Puppies", code = 264_2131, classification = ItemClassification.progression, ),
|
||||
"Puppy 32": KH1ItemData("Puppies", code = 264_2132, classification = ItemClassification.progression, ),
|
||||
"Puppy 33": KH1ItemData("Puppies", code = 264_2133, classification = ItemClassification.progression, ),
|
||||
"Puppy 34": KH1ItemData("Puppies", code = 264_2134, classification = ItemClassification.progression, ),
|
||||
"Puppy 35": KH1ItemData("Puppies", code = 264_2135, classification = ItemClassification.progression, ),
|
||||
"Puppy 36": KH1ItemData("Puppies", code = 264_2136, classification = ItemClassification.progression, ),
|
||||
"Puppy 37": KH1ItemData("Puppies", code = 264_2137, classification = ItemClassification.progression, ),
|
||||
"Puppy 38": KH1ItemData("Puppies", code = 264_2138, classification = ItemClassification.progression, ),
|
||||
"Puppy 39": KH1ItemData("Puppies", code = 264_2139, classification = ItemClassification.progression, ),
|
||||
"Puppy 40": KH1ItemData("Puppies", code = 264_2140, classification = ItemClassification.progression, ),
|
||||
"Puppy 41": KH1ItemData("Puppies", code = 264_2141, classification = ItemClassification.progression, ),
|
||||
"Puppy 42": KH1ItemData("Puppies", code = 264_2142, classification = ItemClassification.progression, ),
|
||||
"Puppy 43": KH1ItemData("Puppies", code = 264_2143, classification = ItemClassification.progression, ),
|
||||
"Puppy 44": KH1ItemData("Puppies", code = 264_2144, classification = ItemClassification.progression, ),
|
||||
"Puppy 45": KH1ItemData("Puppies", code = 264_2145, classification = ItemClassification.progression, ),
|
||||
"Puppy 46": KH1ItemData("Puppies", code = 264_2146, classification = ItemClassification.progression, ),
|
||||
"Puppy 47": KH1ItemData("Puppies", code = 264_2147, classification = ItemClassification.progression, ),
|
||||
"Puppy 48": KH1ItemData("Puppies", code = 264_2148, classification = ItemClassification.progression, ),
|
||||
"Puppy 49": KH1ItemData("Puppies", code = 264_2149, classification = ItemClassification.progression, ),
|
||||
"Puppy 50": KH1ItemData("Puppies", code = 264_2150, classification = ItemClassification.progression, ),
|
||||
"Puppy 51": KH1ItemData("Puppies", code = 264_2151, classification = ItemClassification.progression, ),
|
||||
"Puppy 52": KH1ItemData("Puppies", code = 264_2152, classification = ItemClassification.progression, ),
|
||||
"Puppy 53": KH1ItemData("Puppies", code = 264_2153, classification = ItemClassification.progression, ),
|
||||
"Puppy 54": KH1ItemData("Puppies", code = 264_2154, classification = ItemClassification.progression, ),
|
||||
"Puppy 55": KH1ItemData("Puppies", code = 264_2155, classification = ItemClassification.progression, ),
|
||||
"Puppy 56": KH1ItemData("Puppies", code = 264_2156, classification = ItemClassification.progression, ),
|
||||
"Puppy 57": KH1ItemData("Puppies", code = 264_2157, classification = ItemClassification.progression, ),
|
||||
"Puppy 58": KH1ItemData("Puppies", code = 264_2158, classification = ItemClassification.progression, ),
|
||||
"Puppy 59": KH1ItemData("Puppies", code = 264_2159, classification = ItemClassification.progression, ),
|
||||
"Puppy 60": KH1ItemData("Puppies", code = 264_2160, classification = ItemClassification.progression, ),
|
||||
"Puppy 61": KH1ItemData("Puppies", code = 264_2161, classification = ItemClassification.progression, ),
|
||||
"Puppy 62": KH1ItemData("Puppies", code = 264_2162, classification = ItemClassification.progression, ),
|
||||
"Puppy 63": KH1ItemData("Puppies", code = 264_2163, classification = ItemClassification.progression, ),
|
||||
"Puppy 64": KH1ItemData("Puppies", code = 264_2164, classification = ItemClassification.progression, ),
|
||||
"Puppy 65": KH1ItemData("Puppies", code = 264_2165, classification = ItemClassification.progression, ),
|
||||
"Puppy 66": KH1ItemData("Puppies", code = 264_2166, classification = ItemClassification.progression, ),
|
||||
"Puppy 67": KH1ItemData("Puppies", code = 264_2167, classification = ItemClassification.progression, ),
|
||||
"Puppy 68": KH1ItemData("Puppies", code = 264_2168, classification = ItemClassification.progression, ),
|
||||
"Puppy 69": KH1ItemData("Puppies", code = 264_2169, classification = ItemClassification.progression, ),
|
||||
"Puppy 70": KH1ItemData("Puppies", code = 264_2170, classification = ItemClassification.progression, ),
|
||||
"Puppy 71": KH1ItemData("Puppies", code = 264_2171, classification = ItemClassification.progression, ),
|
||||
"Puppy 72": KH1ItemData("Puppies", code = 264_2172, classification = ItemClassification.progression, ),
|
||||
"Puppy 73": KH1ItemData("Puppies", code = 264_2173, classification = ItemClassification.progression, ),
|
||||
"Puppy 74": KH1ItemData("Puppies", code = 264_2174, classification = ItemClassification.progression, ),
|
||||
"Puppy 75": KH1ItemData("Puppies", code = 264_2175, classification = ItemClassification.progression, ),
|
||||
"Puppy 76": KH1ItemData("Puppies", code = 264_2176, classification = ItemClassification.progression, ),
|
||||
"Puppy 77": KH1ItemData("Puppies", code = 264_2177, classification = ItemClassification.progression, ),
|
||||
"Puppy 78": KH1ItemData("Puppies", code = 264_2178, classification = ItemClassification.progression, ),
|
||||
"Puppy 79": KH1ItemData("Puppies", code = 264_2179, classification = ItemClassification.progression, ),
|
||||
"Puppy 80": KH1ItemData("Puppies", code = 264_2180, classification = ItemClassification.progression, ),
|
||||
"Puppy 81": KH1ItemData("Puppies", code = 264_2181, classification = ItemClassification.progression, ),
|
||||
"Puppy 82": KH1ItemData("Puppies", code = 264_2182, classification = ItemClassification.progression, ),
|
||||
"Puppy 83": KH1ItemData("Puppies", code = 264_2183, classification = ItemClassification.progression, ),
|
||||
"Puppy 84": KH1ItemData("Puppies", code = 264_2184, classification = ItemClassification.progression, ),
|
||||
"Puppy 85": KH1ItemData("Puppies", code = 264_2185, classification = ItemClassification.progression, ),
|
||||
"Puppy 86": KH1ItemData("Puppies", code = 264_2186, classification = ItemClassification.progression, ),
|
||||
"Puppy 87": KH1ItemData("Puppies", code = 264_2187, classification = ItemClassification.progression, ),
|
||||
"Puppy 88": KH1ItemData("Puppies", code = 264_2188, classification = ItemClassification.progression, ),
|
||||
"Puppy 89": KH1ItemData("Puppies", code = 264_2189, classification = ItemClassification.progression, ),
|
||||
"Puppy 90": KH1ItemData("Puppies", code = 264_2190, classification = ItemClassification.progression, ),
|
||||
"Puppy 91": KH1ItemData("Puppies", code = 264_2191, classification = ItemClassification.progression, ),
|
||||
"Puppy 92": KH1ItemData("Puppies", code = 264_2192, classification = ItemClassification.progression, ),
|
||||
"Puppy 93": KH1ItemData("Puppies", code = 264_2193, classification = ItemClassification.progression, ),
|
||||
"Puppy 94": KH1ItemData("Puppies", code = 264_2194, classification = ItemClassification.progression, ),
|
||||
"Puppy 95": KH1ItemData("Puppies", code = 264_2195, classification = ItemClassification.progression, ),
|
||||
"Puppy 96": KH1ItemData("Puppies", code = 264_2196, classification = ItemClassification.progression, ),
|
||||
"Puppy 97": KH1ItemData("Puppies", code = 264_2197, classification = ItemClassification.progression, ),
|
||||
"Puppy 98": KH1ItemData("Puppies", code = 264_2198, classification = ItemClassification.progression, ),
|
||||
"Puppy 99": KH1ItemData("Puppies", code = 264_2199, classification = ItemClassification.progression, ),
|
||||
"Puppies 01-03": KH1ItemData("Puppies", code = 264_2201, classification = ItemClassification.progression, ),
|
||||
"Puppies 04-06": KH1ItemData("Puppies", code = 264_2202, classification = ItemClassification.progression, ),
|
||||
"Puppies 07-09": KH1ItemData("Puppies", code = 264_2203, classification = ItemClassification.progression, ),
|
||||
"Puppies 10-12": KH1ItemData("Puppies", code = 264_2204, classification = ItemClassification.progression, ),
|
||||
"Puppies 13-15": KH1ItemData("Puppies", code = 264_2205, classification = ItemClassification.progression, ),
|
||||
"Puppies 16-18": KH1ItemData("Puppies", code = 264_2206, classification = ItemClassification.progression, ),
|
||||
"Puppies 19-21": KH1ItemData("Puppies", code = 264_2207, classification = ItemClassification.progression, ),
|
||||
"Puppies 22-24": KH1ItemData("Puppies", code = 264_2208, classification = ItemClassification.progression, ),
|
||||
"Puppies 25-27": KH1ItemData("Puppies", code = 264_2209, classification = ItemClassification.progression, ),
|
||||
"Puppies 28-30": KH1ItemData("Puppies", code = 264_2210, classification = ItemClassification.progression, ),
|
||||
"Puppies 31-33": KH1ItemData("Puppies", code = 264_2211, classification = ItemClassification.progression, ),
|
||||
"Puppies 34-36": KH1ItemData("Puppies", code = 264_2212, classification = ItemClassification.progression, ),
|
||||
"Puppies 37-39": KH1ItemData("Puppies", code = 264_2213, classification = ItemClassification.progression, ),
|
||||
"Puppies 40-42": KH1ItemData("Puppies", code = 264_2214, classification = ItemClassification.progression, ),
|
||||
"Puppies 43-45": KH1ItemData("Puppies", code = 264_2215, classification = ItemClassification.progression, ),
|
||||
"Puppies 46-48": KH1ItemData("Puppies", code = 264_2216, classification = ItemClassification.progression, ),
|
||||
"Puppies 49-51": KH1ItemData("Puppies", code = 264_2217, classification = ItemClassification.progression, ),
|
||||
"Puppies 52-54": KH1ItemData("Puppies", code = 264_2218, classification = ItemClassification.progression, ),
|
||||
"Puppies 55-57": KH1ItemData("Puppies", code = 264_2219, classification = ItemClassification.progression, ),
|
||||
"Puppies 58-60": KH1ItemData("Puppies", code = 264_2220, classification = ItemClassification.progression, ),
|
||||
"Puppies 61-63": KH1ItemData("Puppies", code = 264_2221, classification = ItemClassification.progression, ),
|
||||
"Puppies 64-66": KH1ItemData("Puppies", code = 264_2222, classification = ItemClassification.progression, ),
|
||||
"Puppies 67-69": KH1ItemData("Puppies", code = 264_2223, classification = ItemClassification.progression, ),
|
||||
"Puppies 70-72": KH1ItemData("Puppies", code = 264_2224, classification = ItemClassification.progression, ),
|
||||
"Puppies 73-75": KH1ItemData("Puppies", code = 264_2225, classification = ItemClassification.progression, ),
|
||||
"Puppies 76-78": KH1ItemData("Puppies", code = 264_2226, classification = ItemClassification.progression, ),
|
||||
"Puppies 79-81": KH1ItemData("Puppies", code = 264_2227, classification = ItemClassification.progression, ),
|
||||
"Puppies 82-84": KH1ItemData("Puppies", code = 264_2228, classification = ItemClassification.progression, ),
|
||||
"Puppies 85-87": KH1ItemData("Puppies", code = 264_2229, classification = ItemClassification.progression, ),
|
||||
"Puppies 88-90": KH1ItemData("Puppies", code = 264_2230, classification = ItemClassification.progression, ),
|
||||
"Puppies 91-93": KH1ItemData("Puppies", code = 264_2231, classification = ItemClassification.progression, ),
|
||||
"Puppies 94-96": KH1ItemData("Puppies", code = 264_2232, classification = ItemClassification.progression, ),
|
||||
"Puppies 97-99": KH1ItemData("Puppies", code = 264_2233, classification = ItemClassification.progression, ),
|
||||
"All Puppies": KH1ItemData("Puppies", code = 264_2240, classification = ItemClassification.progression, ),
|
||||
"Treasure Magnet": KH1ItemData("Abilities", code = 264_3005, classification = ItemClassification.useful, max_quantity = 2 ),
|
||||
"Combo Plus": KH1ItemData("Abilities", code = 264_3006, classification = ItemClassification.useful, max_quantity = 4 ),
|
||||
"Air Combo Plus": KH1ItemData("Abilities", code = 264_3007, classification = ItemClassification.useful, max_quantity = 2 ),
|
||||
"Critical Plus": KH1ItemData("Abilities", code = 264_3008, classification = ItemClassification.useful, max_quantity = 3 ),
|
||||
#"Second Wind": KH1ItemData("Abilities", code = 264_3009, classification = ItemClassification.useful, ),
|
||||
"Scan": KH1ItemData("Abilities", code = 264_3010, classification = ItemClassification.useful, ),
|
||||
"Sonic Blade": KH1ItemData("Abilities", code = 264_3011, classification = ItemClassification.useful, ),
|
||||
"Ars Arcanum": KH1ItemData("Abilities", code = 264_3012, classification = ItemClassification.useful, ),
|
||||
"Strike Raid": KH1ItemData("Abilities", code = 264_3013, classification = ItemClassification.useful, ),
|
||||
"Ragnarok": KH1ItemData("Abilities", code = 264_3014, classification = ItemClassification.useful, ),
|
||||
"Trinity Limit": KH1ItemData("Abilities", code = 264_3015, classification = ItemClassification.useful, ),
|
||||
"Cheer": KH1ItemData("Abilities", code = 264_3016, classification = ItemClassification.useful, ),
|
||||
"Vortex": KH1ItemData("Abilities", code = 264_3017, classification = ItemClassification.useful, ),
|
||||
"Aerial Sweep": KH1ItemData("Abilities", code = 264_3018, classification = ItemClassification.useful, ),
|
||||
"Counterattack": KH1ItemData("Abilities", code = 264_3019, classification = ItemClassification.useful, ),
|
||||
"Blitz": KH1ItemData("Abilities", code = 264_3020, classification = ItemClassification.useful, ),
|
||||
"Guard": KH1ItemData("Abilities", code = 264_3021, classification = ItemClassification.progression, ),
|
||||
"Dodge Roll": KH1ItemData("Abilities", code = 264_3022, classification = ItemClassification.progression, ),
|
||||
"MP Haste": KH1ItemData("Abilities", code = 264_3023, classification = ItemClassification.useful, ),
|
||||
"MP Rage": KH1ItemData("Abilities", code = 264_3024, classification = ItemClassification.progression, ),
|
||||
"Second Chance": KH1ItemData("Abilities", code = 264_3025, classification = ItemClassification.progression, ),
|
||||
"Berserk": KH1ItemData("Abilities", code = 264_3026, classification = ItemClassification.useful, ),
|
||||
"Jackpot": KH1ItemData("Abilities", code = 264_3027, classification = ItemClassification.useful, ),
|
||||
"Lucky Strike": KH1ItemData("Abilities", code = 264_3028, classification = ItemClassification.useful, ),
|
||||
#"Charge": KH1ItemData("Abilities", code = 264_3029, classification = ItemClassification.useful, ),
|
||||
#"Rocket": KH1ItemData("Abilities", code = 264_3030, classification = ItemClassification.useful, ),
|
||||
#"Tornado": KH1ItemData("Abilities", code = 264_3031, classification = ItemClassification.useful, ),
|
||||
#"MP Gift": KH1ItemData("Abilities", code = 264_3032, classification = ItemClassification.useful, ),
|
||||
#"Raging Boar": KH1ItemData("Abilities", code = 264_3033, classification = ItemClassification.useful, ),
|
||||
#"Asp's Bite": KH1ItemData("Abilities", code = 264_3034, classification = ItemClassification.useful, ),
|
||||
#"Healing Herb": KH1ItemData("Abilities", code = 264_3035, classification = ItemClassification.useful, ),
|
||||
#"Wind Armor": KH1ItemData("Abilities", code = 264_3036, classification = ItemClassification.useful, ),
|
||||
#"Crescent": KH1ItemData("Abilities", code = 264_3037, classification = ItemClassification.useful, ),
|
||||
#"Sandstorm": KH1ItemData("Abilities", code = 264_3038, classification = ItemClassification.useful, ),
|
||||
#"Applause!": KH1ItemData("Abilities", code = 264_3039, classification = ItemClassification.useful, ),
|
||||
#"Blazing Fury": KH1ItemData("Abilities", code = 264_3040, classification = ItemClassification.useful, ),
|
||||
#"Icy Terror": KH1ItemData("Abilities", code = 264_3041, classification = ItemClassification.useful, ),
|
||||
#"Bolts of Sorrow": KH1ItemData("Abilities", code = 264_3042, classification = ItemClassification.useful, ),
|
||||
#"Ghostly Scream": KH1ItemData("Abilities", code = 264_3043, classification = ItemClassification.useful, ),
|
||||
#"Humming Bird": KH1ItemData("Abilities", code = 264_3044, classification = ItemClassification.useful, ),
|
||||
#"Time-Out": KH1ItemData("Abilities", code = 264_3045, classification = ItemClassification.useful, ),
|
||||
#"Storm's Eye": KH1ItemData("Abilities", code = 264_3046, classification = ItemClassification.useful, ),
|
||||
#"Ferocious Lunge": KH1ItemData("Abilities", code = 264_3047, classification = ItemClassification.useful, ),
|
||||
#"Furious Bellow": KH1ItemData("Abilities", code = 264_3048, classification = ItemClassification.useful, ),
|
||||
#"Spiral Wave": KH1ItemData("Abilities", code = 264_3049, classification = ItemClassification.useful, ),
|
||||
#"Thunder Potion": KH1ItemData("Abilities", code = 264_3050, classification = ItemClassification.useful, ),
|
||||
#"Cure Potion": KH1ItemData("Abilities", code = 264_3051, classification = ItemClassification.useful, ),
|
||||
#"Aero Potion": KH1ItemData("Abilities", code = 264_3052, classification = ItemClassification.useful, ),
|
||||
"Slapshot": KH1ItemData("Abilities", code = 264_3053, classification = ItemClassification.useful, ),
|
||||
"Sliding Dash": KH1ItemData("Abilities", code = 264_3054, classification = ItemClassification.useful, ),
|
||||
"Hurricane Blast": KH1ItemData("Abilities", code = 264_3055, classification = ItemClassification.useful, ),
|
||||
"Ripple Drive": KH1ItemData("Abilities", code = 264_3056, classification = ItemClassification.useful, ),
|
||||
"Stun Impact": KH1ItemData("Abilities", code = 264_3057, classification = ItemClassification.useful, ),
|
||||
"Gravity Break": KH1ItemData("Abilities", code = 264_3058, classification = ItemClassification.useful, ),
|
||||
"Zantetsuken": KH1ItemData("Abilities", code = 264_3059, classification = ItemClassification.useful, ),
|
||||
"Tech Boost": KH1ItemData("Abilities", code = 264_3060, classification = ItemClassification.useful, max_quantity = 4 ),
|
||||
"Encounter Plus": KH1ItemData("Abilities", code = 264_3061, classification = ItemClassification.useful, ),
|
||||
"Leaf Bracer": KH1ItemData("Abilities", code = 264_3062, classification = ItemClassification.progression, ),
|
||||
#"Evolution": KH1ItemData("Abilities", code = 264_3063, classification = ItemClassification.useful, ),
|
||||
"EXP Zero": KH1ItemData("Abilities", code = 264_3064, classification = ItemClassification.useful, ),
|
||||
"Combo Master": KH1ItemData("Abilities", code = 264_3065, classification = ItemClassification.progression, ),
|
||||
"Max HP Increase": KH1ItemData("Level Up", code = 264_4001, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Max MP Increase": KH1ItemData("Level Up", code = 264_4002, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Max AP Increase": KH1ItemData("Level Up", code = 264_4003, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Strength Increase": KH1ItemData("Level Up", code = 264_4004, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Defense Increase": KH1ItemData("Level Up", code = 264_4005, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Accessory Slot Increase": KH1ItemData("Limited Level Up", code = 264_4006, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Item Slot Increase": KH1ItemData("Limited Level Up", code = 264_4007, classification = ItemClassification.useful, max_quantity = 15),
|
||||
"Dumbo": KH1ItemData("Summons", code = 264_5000, classification = ItemClassification.progression, ),
|
||||
"Bambi": KH1ItemData("Summons", code = 264_5001, classification = ItemClassification.progression, ),
|
||||
"Genie": KH1ItemData("Summons", code = 264_5002, classification = ItemClassification.progression, ),
|
||||
"Tinker Bell": KH1ItemData("Summons", code = 264_5003, classification = ItemClassification.progression, ),
|
||||
"Mushu": KH1ItemData("Summons", code = 264_5004, classification = ItemClassification.progression, ),
|
||||
"Simba": KH1ItemData("Summons", code = 264_5005, classification = ItemClassification.progression, ),
|
||||
"Progressive Fire": KH1ItemData("Magic", code = 264_6001, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
"Progressive Blizzard": KH1ItemData("Magic", code = 264_6002, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
"Progressive Thunder": KH1ItemData("Magic", code = 264_6003, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
"Progressive Cure": KH1ItemData("Magic", code = 264_6004, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
"Progressive Gravity": KH1ItemData("Magic", code = 264_6005, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
"Progressive Stop": KH1ItemData("Magic", code = 264_6006, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
"Progressive Aero": KH1ItemData("Magic", code = 264_6007, classification = ItemClassification.progression, max_quantity = 3 ),
|
||||
#"Traverse Town": KH1ItemData("Worlds", code = 264_7001, classification = ItemClassification.progression, ),
|
||||
"Wonderland": KH1ItemData("Worlds", code = 264_7002, classification = ItemClassification.progression, ),
|
||||
"Olympus Coliseum": KH1ItemData("Worlds", code = 264_7003, classification = ItemClassification.progression, ),
|
||||
"Deep Jungle": KH1ItemData("Worlds", code = 264_7004, classification = ItemClassification.progression, ),
|
||||
"Agrabah": KH1ItemData("Worlds", code = 264_7005, classification = ItemClassification.progression, ),
|
||||
"Halloween Town": KH1ItemData("Worlds", code = 264_7006, classification = ItemClassification.progression, ),
|
||||
"Atlantica": KH1ItemData("Worlds", code = 264_7007, classification = ItemClassification.progression, ),
|
||||
"Neverland": KH1ItemData("Worlds", code = 264_7008, classification = ItemClassification.progression, ),
|
||||
"Hollow Bastion": KH1ItemData("Worlds", code = 264_7009, classification = ItemClassification.progression, ),
|
||||
"End of the World": KH1ItemData("Worlds", code = 264_7010, classification = ItemClassification.progression, ),
|
||||
"Monstro": KH1ItemData("Worlds", code = 264_7011, classification = ItemClassification.progression, ),
|
||||
"Blue Trinity": KH1ItemData("Trinities", code = 264_8001, classification = ItemClassification.progression, ),
|
||||
"Red Trinity": KH1ItemData("Trinities", code = 264_8002, classification = ItemClassification.progression, ),
|
||||
"Green Trinity": KH1ItemData("Trinities", code = 264_8003, classification = ItemClassification.progression, ),
|
||||
"Yellow Trinity": KH1ItemData("Trinities", code = 264_8004, classification = ItemClassification.progression, ),
|
||||
"White Trinity": KH1ItemData("Trinities", code = 264_8005, classification = ItemClassification.progression, ),
|
||||
"Phil Cup": KH1ItemData("Cups", code = 264_9001, classification = ItemClassification.progression, ),
|
||||
"Pegasus Cup": KH1ItemData("Cups", code = 264_9002, classification = ItemClassification.progression, ),
|
||||
"Hercules Cup": KH1ItemData("Cups", code = 264_9003, classification = ItemClassification.progression, ),
|
||||
#"Hades Cup": KH1ItemData("Cups", code = 264_9004, classification = ItemClassification.progression, ),
|
||||
}
|
||||
|
||||
event_item_table: Dict[str, KH1ItemData] = {
|
||||
"Victory": KH1ItemData("Event", code = None, classification = ItemClassification.progression, type = "Event")
|
||||
}
|
||||
event_item_table: Dict[str, KH1ItemData] = {}
|
||||
|
||||
#Make item categories
|
||||
item_name_groups: Dict[str, Set[str]] = {}
|
||||
|
||||
+552
-738
File diff suppressed because it is too large
Load Diff
+113
-643
@@ -6,9 +6,8 @@ class StrengthIncrease(Range):
|
||||
"""
|
||||
Determines the number of Strength Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "STR Increases"
|
||||
range_start = 0
|
||||
@@ -19,9 +18,8 @@ class DefenseIncrease(Range):
|
||||
"""
|
||||
Determines the number of Defense Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "DEF Increases"
|
||||
range_start = 0
|
||||
@@ -32,9 +30,8 @@ class HPIncrease(Range):
|
||||
"""
|
||||
Determines the number of HP Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "HP Increases"
|
||||
range_start = 0
|
||||
@@ -45,9 +42,8 @@ class APIncrease(Range):
|
||||
"""
|
||||
Determines the number of AP Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "AP Increases"
|
||||
range_start = 0
|
||||
@@ -58,9 +54,8 @@ class MPIncrease(Range):
|
||||
"""
|
||||
Determines the number of MP Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "MP Increases"
|
||||
range_start = 0
|
||||
@@ -71,9 +66,8 @@ class AccessorySlotIncrease(Range):
|
||||
"""
|
||||
Determines the number of Accessory Slot Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "Accessory Slot Increases"
|
||||
range_start = 0
|
||||
@@ -84,9 +78,8 @@ class ItemSlotIncrease(Range):
|
||||
"""
|
||||
Determines the number of Item Slot Increases to add to the multiworld.
|
||||
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 99 to add to the multiworld.
|
||||
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 99 total) are chosen at random.
|
||||
The randomizer will add all stat ups defined here into a pool and choose up to 100 to add to the multiworld.
|
||||
Accessory Slot Increases and Item Slot Increases are prioritized first, then the remaining items (up to 100 total) are chosen at random.
|
||||
"""
|
||||
display_name = "Item Slot Increases"
|
||||
range_start = 0
|
||||
@@ -111,45 +104,29 @@ class SuperBosses(Toggle):
|
||||
"""
|
||||
display_name = "Super Bosses"
|
||||
|
||||
class Cups(Choice):
|
||||
class Cups(Toggle):
|
||||
"""
|
||||
Determines which cups have their locations added to the multiworld.
|
||||
|
||||
Please note that the cup items will still appear in the multiworld even if set to off, as they are required to challenge Sephiroth.
|
||||
|
||||
Off: All cup locations are removed
|
||||
|
||||
Cups: Phil, Pegasus, and Hercules cups are included
|
||||
|
||||
Hades Cup: Hades Cup is included in addition to Phil, Pegasus, and Hercules cups. If Super Bosses are enabled, then Ice Titan is included
|
||||
Toggle whether to include checks behind completing Phil, Pegasus, Hercules, or Hades cups.
|
||||
Please note that the cup items will still appear in the multiworld even if toggled off, as they are required to challenge Sephiroth.
|
||||
"""
|
||||
display_name = "Cups"
|
||||
option_off = 0
|
||||
option_cups = 1
|
||||
option_hades_cup = 2
|
||||
default = 0
|
||||
|
||||
class FinalRestDoorKey(Choice):
|
||||
class Goal(Choice):
|
||||
"""
|
||||
Determines what grants the player the Final Rest Door Key.
|
||||
Determines when victory is achieved in your playthrough.
|
||||
|
||||
Sephiroth: Defeat Sephiroth
|
||||
|
||||
Unknown: Defeat Unknown
|
||||
|
||||
Postcards: Turn in an amount of postcards in Traverse Town
|
||||
|
||||
Postcards: Turn in all 10 postcards in Traverse Town
|
||||
Final Ansem: Enter End of the World and defeat Ansem as normal
|
||||
|
||||
Puppies: Rescue and return an amount of puppies in Traverse Town
|
||||
|
||||
Puppies: Rescue and return all 99 puppies in Traverse Town
|
||||
Final Rest: Open the chest in End of the World Final Rest
|
||||
"""
|
||||
display_name = "Final Rest Door Key"
|
||||
display_name = "Goal"
|
||||
option_sephiroth = 0
|
||||
option_unknown = 1
|
||||
option_postcards = 2
|
||||
option_lucky_emblems = 3
|
||||
option_final_ansem = 3
|
||||
option_puppies = 4
|
||||
option_final_rest = 5
|
||||
default = 3
|
||||
@@ -158,115 +135,89 @@ class EndoftheWorldUnlock(Choice):
|
||||
"""Determines how End of the World is unlocked.
|
||||
|
||||
Item: You can receive an item called "End of the World" which unlocks the world
|
||||
|
||||
Lucky Emblems: A certain amount of lucky emblems are required to unlock End of the World, which is defined in your options"""
|
||||
Reports: A certain amount of reports are required to unlock End of the World, which is defined in your options"""
|
||||
display_name = "End of the World Unlock"
|
||||
option_item = 0
|
||||
option_lucky_emblems = 1
|
||||
option_reports = 1
|
||||
default = 1
|
||||
|
||||
class RequiredPostcards(Range):
|
||||
"""
|
||||
If "Final Rest Door Key" is set to "Postcards", defines how many postcards are required.
|
||||
"""
|
||||
display_name = "Required Postcards"
|
||||
default = 8
|
||||
range_start = 1
|
||||
range_end = 10
|
||||
|
||||
class RequiredPuppies(Choice):
|
||||
"""
|
||||
If "Final Rest Door Key" is set to "Puppies", defines how many puppies are required.
|
||||
"""
|
||||
display_name = "Required Puppies"
|
||||
default = 80
|
||||
option_10 = 10
|
||||
option_20 = 20
|
||||
option_30 = 30
|
||||
option_40 = 40
|
||||
option_50 = 50
|
||||
option_60 = 60
|
||||
option_70 = 70
|
||||
option_80 = 80
|
||||
option_90 = 90
|
||||
option_99 = 99
|
||||
|
||||
class PuppyValue(Range):
|
||||
"""
|
||||
Determines how many dalmatian puppies are given when a puppy item is found.
|
||||
"""
|
||||
display_name = "Puppy Value"
|
||||
default = 3
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
|
||||
class RandomizePuppies(DefaultOnToggle):
|
||||
"""
|
||||
If OFF, the "Puppy" item is worth 3 puppies and puppies are placed in vanilla locations.
|
||||
class FinalRestDoor(Choice):
|
||||
"""Determines what conditions need to be met to manifest the door in Final Rest, allowing the player to challenge Ansem.
|
||||
|
||||
If ON, the "Puppy" item is worth an amount of puppies defined by "Puppy Value", and are shuffled randomly.
|
||||
Reports: A certain number of Ansem's Reports are required, determined by the "Reports to Open Final Rest Door" option
|
||||
Puppies: Having all 99 puppies is required
|
||||
Postcards: Turning in all 10 postcards is required
|
||||
Superbosses: Defeating Sephiroth, Unknown, Kurt Zisa, and Phantom are required
|
||||
"""
|
||||
display_name = "Randomize Puppies"
|
||||
display_name = "Final Rest Door"
|
||||
option_reports = 0
|
||||
option_puppies = 1
|
||||
option_postcards = 2
|
||||
option_superbosses = 3
|
||||
|
||||
class Puppies(Choice):
|
||||
"""
|
||||
Determines how dalmatian puppies are shuffled into the pool.
|
||||
Full: All puppies are in one location
|
||||
Triplets: Puppies are found in triplets just as they are in the base game
|
||||
Individual: One puppy can be found per location
|
||||
"""
|
||||
display_name = "Puppies"
|
||||
option_full = 0
|
||||
option_triplets = 1
|
||||
option_individual = 2
|
||||
default = 1
|
||||
|
||||
class EXPMultiplier(NamedRange):
|
||||
"""
|
||||
Determines the multiplier to apply to EXP gained.
|
||||
"""
|
||||
display_name = "EXP Multiplier"
|
||||
default = 16 * 4
|
||||
range_start = 16 // 4
|
||||
default = 16
|
||||
range_start = default // 4
|
||||
range_end = 128
|
||||
special_range_names = {
|
||||
"0.25x": int(16 // 4),
|
||||
"0.5x": int(16 // 2),
|
||||
"1x": 16,
|
||||
"2x": 16 * 2,
|
||||
"3x": 16 * 3,
|
||||
"4x": 16 * 4,
|
||||
"8x": 16 * 8,
|
||||
"0.25x": int(default // 4),
|
||||
"0.5x": int(default // 2),
|
||||
"1x": default,
|
||||
"2x": default * 2,
|
||||
"3x": default * 3,
|
||||
"4x": default * 4,
|
||||
"8x": default * 8,
|
||||
}
|
||||
|
||||
class RequiredLuckyEmblemsEotW(Range):
|
||||
class RequiredReportsEotW(Range):
|
||||
"""
|
||||
If End of the World Unlock is set to "Lucky Emblems", determines the number of Lucky Emblems required.
|
||||
If End of the World Unlock is set to "Reports", determines the number of Ansem's Reports required to open End of the World.
|
||||
"""
|
||||
display_name = "Lucky Emblems to Open End of the World"
|
||||
default = 7
|
||||
display_name = "Reports to Open End of the World"
|
||||
default = 4
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
range_end = 13
|
||||
|
||||
class RequiredLuckyEmblemsDoor(Range):
|
||||
class RequiredReportsDoor(Range):
|
||||
"""
|
||||
If Final Rest Door Key is set to "Lucky Emblems", determines the number of Lucky Emblems required.
|
||||
If Final Rest Door is set to "Reports", determines the number of Ansem's Reports required to manifest the door in Final Rest to challenge Ansem.
|
||||
"""
|
||||
display_name = "Lucky Emblems to Open Final Rest Door"
|
||||
default = 10
|
||||
display_name = "Reports to Open Final Rest Door"
|
||||
default = 4
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
range_end = 13
|
||||
|
||||
class LuckyEmblemsInPool(Range):
|
||||
class ReportsInPool(Range):
|
||||
"""
|
||||
Determines the number of Lucky Emblems in the item pool.
|
||||
Determines the number of Ansem's Reports in the item pool.
|
||||
"""
|
||||
display_name = "Lucky Emblems in Pool"
|
||||
default = 13
|
||||
display_name = "Reports in Pool"
|
||||
default = 4
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
range_end = 13
|
||||
|
||||
class KeybladeStats(Choice):
|
||||
class RandomizeKeybladeStats(DefaultOnToggle):
|
||||
"""
|
||||
Determines whether Keyblade stats should be randomized.
|
||||
|
||||
Randomize: Randomly generates stats for each keyblade between the defined minimums and maximums.
|
||||
|
||||
Shuffle: Shuffles the stats of the vanilla keyblades amongst each other.
|
||||
|
||||
Vanilla: Keyblade stats are unchanged.
|
||||
"""
|
||||
display_name = "Keyblade Stats"
|
||||
option_randomize = 0
|
||||
option_shuffle = 1
|
||||
option_vanilla = 2
|
||||
display_name = "Randomize Keyblade Stats"
|
||||
|
||||
class KeybladeMinStrength(Range):
|
||||
"""
|
||||
@@ -286,60 +237,6 @@ class KeybladeMaxStrength(Range):
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
|
||||
class KeybladeMinCritRateBonus(Range):
|
||||
"""
|
||||
Determines the minimum Crit Rate bonus a keyblade can have.
|
||||
"""
|
||||
display_name = "Keyblade Minimum Crit Rate Bonus"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 200
|
||||
|
||||
class KeybladeMaxCritRateBonus(Range):
|
||||
"""
|
||||
Determines the maximum Crit Rate bonus a keyblade can have.
|
||||
"""
|
||||
display_name = "Keyblade Maximum Crit Rate Bonus"
|
||||
default = 200
|
||||
range_start = 0
|
||||
range_end = 200
|
||||
|
||||
class KeybladeMinCritSTRBonus(Range):
|
||||
"""
|
||||
Determines the minimum Crit STR bonus a keyblade can have.
|
||||
"""
|
||||
display_name = "Keyblade Minimum Crit Rate Bonus"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 16
|
||||
|
||||
class KeybladeMaxCritSTRBonus(Range):
|
||||
"""
|
||||
Determines the maximum Crit STR bonus a keyblade can have.
|
||||
"""
|
||||
display_name = "Keyblade Maximum Crit Rate Bonus"
|
||||
default = 16
|
||||
range_start = 0
|
||||
range_end = 16
|
||||
|
||||
class KeybladeMinRecoil(Range):
|
||||
"""
|
||||
Determines the minimum recoil a keyblade can have.
|
||||
"""
|
||||
display_name = "Keyblade Minimum Recoil"
|
||||
default = 1
|
||||
range_start = 1
|
||||
range_end = 90
|
||||
|
||||
class KeybladeMaxRecoil(Range):
|
||||
"""
|
||||
Determines the maximum recoil a keyblade can have.
|
||||
"""
|
||||
display_name = "Keyblade Maximum Recoil"
|
||||
default = 90
|
||||
range_start = 1
|
||||
range_end = 90
|
||||
|
||||
class KeybladeMinMP(Range):
|
||||
"""
|
||||
Determines the minimum MP bonus a keyblade can have.
|
||||
@@ -363,43 +260,31 @@ class LevelChecks(Range):
|
||||
Determines the maximum level for which checks can be obtained.
|
||||
"""
|
||||
display_name = "Level Checks"
|
||||
default = 99
|
||||
default = 100
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
range_end = 100
|
||||
|
||||
class ForceStatsOnLevels(NamedRange):
|
||||
"""
|
||||
If this value is less than the value for Level Checks, this determines the minimum level from which only stat ups are obtained at level up locations.
|
||||
|
||||
For example, if you want to be able to find any multiworld item from levels 2-50, then just stat ups for levels 51-100, set this value to 51.
|
||||
For example, if you want to be able to find any multiworld item from levels 1-50, then just stat ups for levels 51-100, set this value to 51.
|
||||
"""
|
||||
display_name = "Force Stats on Level Starting From"
|
||||
default = 2
|
||||
range_start = 2
|
||||
default = 1
|
||||
range_start = 1
|
||||
range_end = 101
|
||||
special_range_names = {
|
||||
"none": 101,
|
||||
"multiworld-to-level-50": 51,
|
||||
"all": 2
|
||||
"all": 1
|
||||
}
|
||||
|
||||
class BadStartingWeapons(Toggle):
|
||||
"""
|
||||
Forces Kingdom Key, Dream Sword, Dream Shield, and Dream Staff to have vanilla stats.
|
||||
Forces Kingdom Key, Dream Sword, Dream Shield, and Dream Staff to have bad stats.
|
||||
"""
|
||||
display_name = "Bad Starting Weapons"
|
||||
|
||||
class DeathLink(Choice):
|
||||
"""
|
||||
If Sora is KO'ed, the other players with "Death Link" on will also be KO'ed.
|
||||
The opposite is also true.
|
||||
"""
|
||||
display_name = "Death Link"
|
||||
option_off = 0
|
||||
option_toggle = 1
|
||||
option_on = 2
|
||||
default = 0
|
||||
|
||||
class DonaldDeathLink(Toggle):
|
||||
"""
|
||||
If Donald is KO'ed, so is Sora. If Death Link is toggled on in your client, this will send a death to everyone who enabled death link.
|
||||
@@ -415,61 +300,35 @@ class GoofyDeathLink(Toggle):
|
||||
class KeybladesUnlockChests(Toggle):
|
||||
"""
|
||||
If toggled on, the player is required to have a certain keyblade to open chests in certain worlds.
|
||||
|
||||
TT - Lionheart
|
||||
|
||||
WL - Lady Luck
|
||||
|
||||
OC - Olympia
|
||||
|
||||
DJ - Jungle King
|
||||
|
||||
AG - Three Wishes
|
||||
|
||||
MS - Wishing Star
|
||||
|
||||
HT - Pumpkinhead
|
||||
|
||||
NL - Fairy Harp
|
||||
|
||||
HB - Divine Rose
|
||||
|
||||
EotW - Oblivion
|
||||
|
||||
HAW - Spellbinder
|
||||
|
||||
DI - Oathkeeper
|
||||
HAW - Oathkeeper
|
||||
|
||||
Note: Does not apply to Atlantica, the emblem and carousel chests in Hollow Bastion, or the Aero chest in Neverland currently.
|
||||
"""
|
||||
display_name = "Keyblades Unlock Chests"
|
||||
|
||||
class InteractInBattle(DefaultOnToggle):
|
||||
class InteractInBattle(Toggle):
|
||||
"""
|
||||
Allow Sora to talk to people, examine objects, and open chests in battle.
|
||||
"""
|
||||
display_name = "Interact in Battle"
|
||||
|
||||
class LogicDifficulty(Choice):
|
||||
class AdvancedLogic(Toggle):
|
||||
"""
|
||||
Determines what the randomizer logic may expect you to do to reach certain locations.
|
||||
|
||||
Beginner: Logic only expects what would be the natural solution in vanilla gameplay or similar, as well as a guarantee of tools for boss fights.
|
||||
|
||||
Normal: Logic expects some clever use of abilities, exploration of options, and competent combat ability; generally does not require advanced knowledge.
|
||||
|
||||
Proud: Logic expects advanced knowledge of tricks and obscure interactions, such as using Combo Master, Dumbo, and other unusual methods to reach locations.
|
||||
|
||||
Minimal: Logic expects the bare minimum to get to locations; may require extensive grinding, beating fights with no tools, and performing very difficult or tedious tricks.
|
||||
If on, logic may expect you to do advanced skips like using Combo Master, Dumbo, and other unusual methods to reach locations.
|
||||
"""
|
||||
display_name = "Logic Difficulty"
|
||||
option_beginner = 0
|
||||
option_normal = 5
|
||||
option_proud = 10
|
||||
option_minimal = 15
|
||||
default = 5
|
||||
display_name = "Advanced Logic"
|
||||
|
||||
class ExtraSharedAbilities(DefaultOnToggle):
|
||||
class ExtraSharedAbilities(Toggle):
|
||||
"""
|
||||
If on, adds extra shared abilities to the pool. These can stack, so multiple high jumps make you jump higher and multiple glides make you superglide faster.
|
||||
"""
|
||||
@@ -481,361 +340,51 @@ class EXPZeroInPool(Toggle):
|
||||
"""
|
||||
display_name = "EXP Zero in Pool"
|
||||
|
||||
class RandomizeEmblemPieces(Toggle):
|
||||
class VanillaEmblemPieces(DefaultOnToggle):
|
||||
"""
|
||||
If off, the Hollow Bastion emblem pieces are in their vanilla locations.
|
||||
If on, the Hollow Bastion emblem pieces are in their vanilla locations.
|
||||
"""
|
||||
display_name = "Randomize Emblem Pieces"
|
||||
|
||||
class RandomizePostcards(Choice):
|
||||
"""
|
||||
Determines how Postcards are randomized
|
||||
|
||||
All: All Postcards are randomized
|
||||
|
||||
Chests: Only the 3 Postcards in chests are randomized
|
||||
|
||||
Vanilla: Postcards are in their original location
|
||||
"""
|
||||
display_name = "Randomize Postcards"
|
||||
option_all = 0
|
||||
option_chests = 1
|
||||
option_vanilla = 2
|
||||
|
||||
class JungleSlider(Toggle):
|
||||
"""
|
||||
Determines whether checks are behind the Jungle Slider minigame.
|
||||
"""
|
||||
display_name = "Jungle Slider"
|
||||
display_name = "Vanilla Emblem Pieces"
|
||||
|
||||
class StartingWorlds(Range):
|
||||
"""
|
||||
Number of random worlds to start with in addition to Traverse Town, which is always available.
|
||||
|
||||
Will only consider Atlantica if toggled, and will only consider End of the World if its unlock is set to "Item".
|
||||
|
||||
These are given by the server, and are received after connection.
|
||||
Number of random worlds to start with in addition to Traverse Town, which is always available. Will only consider Atlantica if toggled, and will only consider End of the World if its unlock is set to "Item".
|
||||
"""
|
||||
display_name = "Starting Worlds"
|
||||
default = 4
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
|
||||
class StartingTools(DefaultOnToggle):
|
||||
"""
|
||||
Determines whether you start with Scan and Dodge Roll.
|
||||
|
||||
These are given by the server, and are received after connection.
|
||||
"""
|
||||
display_name = "Starting Tools"
|
||||
|
||||
class RemoteItems(Choice):
|
||||
"""
|
||||
Determines if items can be placed on locations in your own world in such a way that will force them to be remote items.
|
||||
|
||||
Off: When your items are placed in your world, they can only be placed in locations that they can be acquired without server connection (stats on levels, items in chests, etc).
|
||||
|
||||
Allow: When your items are placed in your world, items that normally can't be placed in a location in-game are simply made remote (abilities on static events, etc).
|
||||
|
||||
Full: All items are remote. Use this when doing something like a co-op seed.
|
||||
"""
|
||||
display_name = "Remote Items"
|
||||
option_off = 0
|
||||
option_allow = 1
|
||||
option_full = 2
|
||||
default = 0
|
||||
|
||||
class Slot2LevelChecks(Range):
|
||||
"""
|
||||
Determines how many levels have an additional item.
|
||||
|
||||
If Remote Items is OFF, these checks will only contain abilities or items for other players.
|
||||
"""
|
||||
display_name = "Slot 2 Level Checks"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 33
|
||||
|
||||
class ShortenGoMode(DefaultOnToggle):
|
||||
"""
|
||||
If on, the player warps to the final cutscene after defeating Ansem 1 > Darkside > Ansem 2, skipping World of Chaos.
|
||||
"""
|
||||
display_name = "Shorten Go Mode"
|
||||
|
||||
class DestinyIslands(Toggle):
|
||||
"""
|
||||
If on, Adds a Destiny Islands item and a number of Raft Materials items to the pool.
|
||||
|
||||
When "Destiny Islands" is found, Traverse Town will have an additional place to land - Seashore.
|
||||
|
||||
"Raft Materials" allow progress into Day 2 and to Homecoming. The amount is defined in Day 2 Materials and Homecoming Materials.
|
||||
"""
|
||||
display_name = "Destiny Islands"
|
||||
|
||||
class MythrilInPool(Range):
|
||||
"""
|
||||
Determines how much Mythril, one of the two synthesis items, is in the item pool.
|
||||
|
||||
You need 16 to synth every recipe that requires it.
|
||||
"""
|
||||
display_name = "Mythril In Pool"
|
||||
default = 20
|
||||
range_start = 16
|
||||
range_end = 30
|
||||
|
||||
class OrichalcumInPool(Range):
|
||||
"""
|
||||
Determines how much Orichalcum, one of the two synthesis items, is in the item pool.
|
||||
|
||||
You need 17 to synth every recipe that requires it.
|
||||
"""
|
||||
display_name = "Mythril In Pool"
|
||||
default = 20
|
||||
range_start = 17
|
||||
range_end = 30
|
||||
|
||||
class MythrilPrice(Range):
|
||||
"""
|
||||
Determines the cost of Mythril in each shop.
|
||||
"""
|
||||
display_name = "Mythril Price"
|
||||
default = 500
|
||||
range_start = 100
|
||||
range_end = 5000
|
||||
|
||||
class OrichalcumPrice(Range):
|
||||
"""
|
||||
Determines the cost of Orichalcum in each shop.
|
||||
"""
|
||||
display_name = "Orichalcum Price"
|
||||
default = 500
|
||||
range_start = 100
|
||||
range_end = 5000
|
||||
|
||||
class OneHP(Toggle):
|
||||
"""
|
||||
If on, forces Sora's max HP to 1 and removes the low health warning sound.
|
||||
"""
|
||||
display_name = "One HP"
|
||||
|
||||
class FourByThree(Toggle):
|
||||
"""
|
||||
If on, changes the aspect ratio to 4 by 3.
|
||||
"""
|
||||
display_name = "4 by 3"
|
||||
|
||||
class AutoAttack(Toggle):
|
||||
"""
|
||||
If on, you can combo by holding confirm.
|
||||
"""
|
||||
display_name = "Auto Attack"
|
||||
|
||||
class BeepHack(Toggle):
|
||||
"""
|
||||
If on, removes low health warning sound. Works up to max health of 41.
|
||||
"""
|
||||
display_name = "Beep Hack"
|
||||
|
||||
class ConsistentFinishers(Toggle):
|
||||
"""
|
||||
If on, 30% chance finishers are now 100% chance.
|
||||
"""
|
||||
display_name = "Consistent Finishers"
|
||||
|
||||
class EarlySkip(DefaultOnToggle):
|
||||
"""
|
||||
If on, allows skipping cutscenes immediately that normally take time to be able to skip.
|
||||
"""
|
||||
display_name = "Early Skip"
|
||||
|
||||
class FastCamera(Toggle):
|
||||
"""
|
||||
If on, speeds up camera movement and camera centering.
|
||||
"""
|
||||
display_name = "Fast Camera"
|
||||
|
||||
class FasterAnimations(DefaultOnToggle):
|
||||
"""
|
||||
If on, speeds up animations during which you can't play.
|
||||
"""
|
||||
display_name = "Faster Animations"
|
||||
|
||||
class Unlock0Volume(Toggle):
|
||||
"""
|
||||
If on, volume 1 mutes the audio channel.
|
||||
"""
|
||||
display_name = "Unlock 0 Volume"
|
||||
|
||||
class Unskippable(DefaultOnToggle):
|
||||
"""
|
||||
If on, makes unskippable cutscenes skippable.
|
||||
"""
|
||||
display_name = "Unskippable"
|
||||
|
||||
class AutoSave(DefaultOnToggle):
|
||||
"""
|
||||
If on, enables auto saving.
|
||||
|
||||
Press L1+L2+R1+R2+D-Pad Left to instantly load continue state.
|
||||
|
||||
Press L1+L2+R1+R2+D-Pad Right to instantly load autosave.
|
||||
"""
|
||||
display_name = "AutoSave"
|
||||
|
||||
class WarpAnywhere(Toggle):
|
||||
"""
|
||||
If on, enables the player to warp at any time, even when not at a save point.
|
||||
|
||||
Press L1+L2+R2+Select to open the Save/Warp menu at any time.
|
||||
"""
|
||||
display_name = "WarpAnywhere"
|
||||
|
||||
class RandomizePartyMemberStartingAccessories(DefaultOnToggle):
|
||||
"""
|
||||
If on, the 10 accessories that some party members (Aladdin, Ariel, Jack, Peter Pan, Beast) start with are randomized.
|
||||
|
||||
10 random accessories will be distributed amongst any party member aside from Sora in their starting equipment.
|
||||
"""
|
||||
display_name = "Randomize Party Member Starting Accessories"
|
||||
|
||||
class MaxLevelForSlot2LevelChecks(Range):
|
||||
"""
|
||||
Determines the max level for slot 2 level checks.
|
||||
"""
|
||||
display_name = "Max Level for Slot 2 Level Checks"
|
||||
default = 50
|
||||
range_start = 2
|
||||
range_end = 100
|
||||
|
||||
class RandomizeAPCosts(Choice):
|
||||
"""
|
||||
Off: No randomization
|
||||
Shuffle: Ability AP Costs will be shuffled amongst themselves.
|
||||
|
||||
Randomize: Ability AP Costs will be randomized to the specified max and min.
|
||||
|
||||
Distribute: Ability AP Costs will totalled and re-distributed randomly between the specified max and min.
|
||||
"""
|
||||
display_name = "Randomize AP Costs"
|
||||
option_off = 0
|
||||
option_shuffle = 1
|
||||
option_randomize = 2
|
||||
option_distribute = 3
|
||||
default = 0
|
||||
|
||||
class MaxAPCost(Range):
|
||||
"""
|
||||
If Randomize AP Costs is set to Randomize or Distribute, this defined the max AP cost an ability can have.
|
||||
"""
|
||||
display_name = "Max AP Cost"
|
||||
default = 5
|
||||
range_start = 4
|
||||
range_end = 9
|
||||
|
||||
class MinAPCost(Range):
|
||||
"""
|
||||
If Randomize AP Costs is set to Randomize or Distribute, this defined the min AP cost an ability can have.
|
||||
"""
|
||||
display_name = "Min AP Cost"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 2
|
||||
|
||||
class Day2Materials(Range):
|
||||
"""
|
||||
The amount of Raft Materials required to access Day 2.
|
||||
"""
|
||||
display_name = "Day 2 Materials"
|
||||
default = 4
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
|
||||
class HomecomingMaterials(Range):
|
||||
"""
|
||||
The amount of Raft Materials required to access Homecoming.
|
||||
"""
|
||||
display_name = "Homecoming Materials"
|
||||
default = 10
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
|
||||
class MaterialsInPool(Range):
|
||||
"""
|
||||
The amount of Raft Materials required to access Homecoming.
|
||||
"""
|
||||
display_name = "Materials in Pool"
|
||||
default = 16
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
|
||||
class StackingWorldItems(DefaultOnToggle):
|
||||
"""
|
||||
Multiple world items give you the world's associated key item.
|
||||
|
||||
WL - Footprints
|
||||
|
||||
OC - Entry Pass
|
||||
|
||||
DJ - Slides
|
||||
|
||||
HT - Forget-Me-Not and Jack-In-The-Box
|
||||
|
||||
HB - Theon Vol. 6
|
||||
|
||||
Adds an extra world to the pool for each that has a key item (WL, OC, DJ, HT, HB).
|
||||
|
||||
Forces Halloween Town Key Item Bundle ON.
|
||||
"""
|
||||
display_name = "Stacking World Items"
|
||||
|
||||
class HalloweenTownKeyItemBundle(DefaultOnToggle):
|
||||
"""
|
||||
Obtaining the Forget-Me-Not automatically gives Jack-in-the-Box as well.
|
||||
|
||||
Removes Jack-in-the-Box from the pool.
|
||||
"""
|
||||
display_name = "Halloween Town Key Item Bundle"
|
||||
|
||||
@dataclass
|
||||
class KH1Options(PerGameCommonOptions):
|
||||
final_rest_door_key: FinalRestDoorKey
|
||||
goal: Goal
|
||||
end_of_the_world_unlock: EndoftheWorldUnlock
|
||||
required_lucky_emblems_eotw: RequiredLuckyEmblemsEotW
|
||||
required_lucky_emblems_door: RequiredLuckyEmblemsDoor
|
||||
lucky_emblems_in_pool: LuckyEmblemsInPool
|
||||
required_postcards: RequiredPostcards
|
||||
required_puppies: RequiredPuppies
|
||||
final_rest_door: FinalRestDoor
|
||||
required_reports_eotw: RequiredReportsEotW
|
||||
required_reports_door: RequiredReportsDoor
|
||||
reports_in_pool: ReportsInPool
|
||||
super_bosses: SuperBosses
|
||||
atlantica: Atlantica
|
||||
hundred_acre_wood: HundredAcreWood
|
||||
cups: Cups
|
||||
randomize_puppies: RandomizePuppies
|
||||
puppy_value: PuppyValue
|
||||
puppies: Puppies
|
||||
starting_worlds: StartingWorlds
|
||||
keyblades_unlock_chests: KeybladesUnlockChests
|
||||
interact_in_battle: InteractInBattle
|
||||
exp_multiplier: EXPMultiplier
|
||||
logic_difficulty: LogicDifficulty
|
||||
advanced_logic: AdvancedLogic
|
||||
extra_shared_abilities: ExtraSharedAbilities
|
||||
exp_zero_in_pool: EXPZeroInPool
|
||||
randomize_emblem_pieces: RandomizeEmblemPieces
|
||||
randomize_postcards: RandomizePostcards
|
||||
vanilla_emblem_pieces: VanillaEmblemPieces
|
||||
donald_death_link: DonaldDeathLink
|
||||
goofy_death_link: GoofyDeathLink
|
||||
keyblade_stats: KeybladeStats
|
||||
randomize_keyblade_stats: RandomizeKeybladeStats
|
||||
bad_starting_weapons: BadStartingWeapons
|
||||
keyblade_min_str: KeybladeMinStrength
|
||||
keyblade_max_str: KeybladeMaxStrength
|
||||
keyblade_min_crit_rate: KeybladeMinCritRateBonus
|
||||
keyblade_max_crit_rate: KeybladeMaxCritRateBonus
|
||||
keyblade_min_crit_str: KeybladeMinCritSTRBonus
|
||||
keyblade_max_crit_str: KeybladeMaxCritSTRBonus
|
||||
keyblade_min_recoil: KeybladeMinRecoil
|
||||
keyblade_max_recoil: KeybladeMaxRecoil
|
||||
keyblade_min_mp: KeybladeMinMP
|
||||
keyblade_max_mp: KeybladeMaxMP
|
||||
level_checks: LevelChecks
|
||||
slot_2_level_checks: Slot2LevelChecks
|
||||
force_stats_on_levels: ForceStatsOnLevels
|
||||
strength_increase: StrengthIncrease
|
||||
defense_increase: DefenseIncrease
|
||||
@@ -845,68 +394,26 @@ class KH1Options(PerGameCommonOptions):
|
||||
accessory_slot_increase: AccessorySlotIncrease
|
||||
item_slot_increase: ItemSlotIncrease
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
jungle_slider: JungleSlider
|
||||
starting_tools: StartingTools
|
||||
remote_items: RemoteItems
|
||||
shorten_go_mode: ShortenGoMode
|
||||
death_link: DeathLink
|
||||
destiny_islands: DestinyIslands
|
||||
orichalcum_in_pool: OrichalcumInPool
|
||||
orichalcum_price: OrichalcumPrice
|
||||
mythril_in_pool: MythrilInPool
|
||||
mythril_price: MythrilPrice
|
||||
one_hp: OneHP
|
||||
four_by_three: FourByThree
|
||||
auto_attack: AutoAttack
|
||||
beep_hack: BeepHack
|
||||
consistent_finishers: ConsistentFinishers
|
||||
early_skip: EarlySkip
|
||||
fast_camera: FastCamera
|
||||
faster_animations: FasterAnimations
|
||||
unlock_0_volume: Unlock0Volume
|
||||
unskippable: Unskippable
|
||||
auto_save: AutoSave
|
||||
warp_anywhere: WarpAnywhere
|
||||
randomize_party_member_starting_accessories: RandomizePartyMemberStartingAccessories
|
||||
max_level_for_slot_2_level_checks: MaxLevelForSlot2LevelChecks
|
||||
randomize_ap_costs: RandomizeAPCosts
|
||||
max_ap_cost: MaxAPCost
|
||||
min_ap_cost: MinAPCost
|
||||
day_2_materials: Day2Materials
|
||||
homecoming_materials: HomecomingMaterials
|
||||
materials_in_pool: MaterialsInPool
|
||||
stacking_world_items: StackingWorldItems
|
||||
halloween_town_key_item_bundle: HalloweenTownKeyItemBundle
|
||||
|
||||
|
||||
kh1_option_groups = [
|
||||
OptionGroup("Goal", [
|
||||
FinalRestDoorKey,
|
||||
Goal,
|
||||
EndoftheWorldUnlock,
|
||||
RequiredLuckyEmblemsDoor,
|
||||
RequiredLuckyEmblemsEotW,
|
||||
LuckyEmblemsInPool,
|
||||
RequiredPostcards,
|
||||
RequiredPuppies,
|
||||
DestinyIslands,
|
||||
Day2Materials,
|
||||
HomecomingMaterials,
|
||||
MaterialsInPool,
|
||||
FinalRestDoor,
|
||||
RequiredReportsDoor,
|
||||
RequiredReportsEotW,
|
||||
ReportsInPool,
|
||||
]),
|
||||
OptionGroup("Locations", [
|
||||
SuperBosses,
|
||||
Atlantica,
|
||||
Cups,
|
||||
HundredAcreWood,
|
||||
JungleSlider,
|
||||
RandomizeEmblemPieces,
|
||||
RandomizePostcards,
|
||||
VanillaEmblemPieces,
|
||||
]),
|
||||
OptionGroup("Levels", [
|
||||
EXPMultiplier,
|
||||
LevelChecks,
|
||||
Slot2LevelChecks,
|
||||
MaxLevelForSlot2LevelChecks,
|
||||
ForceStatsOnLevels,
|
||||
StrengthIncrease,
|
||||
DefenseIncrease,
|
||||
@@ -918,58 +425,21 @@ kh1_option_groups = [
|
||||
]),
|
||||
OptionGroup("Keyblades", [
|
||||
KeybladesUnlockChests,
|
||||
KeybladeStats,
|
||||
RandomizeKeybladeStats,
|
||||
BadStartingWeapons,
|
||||
KeybladeMinStrength,
|
||||
KeybladeMaxStrength,
|
||||
KeybladeMinCritRateBonus,
|
||||
KeybladeMaxCritRateBonus,
|
||||
KeybladeMinCritSTRBonus,
|
||||
KeybladeMaxCritSTRBonus,
|
||||
KeybladeMinRecoil,
|
||||
KeybladeMaxRecoil,
|
||||
KeybladeMinMP,
|
||||
KeybladeMinStrength,
|
||||
KeybladeMaxMP,
|
||||
]),
|
||||
OptionGroup("Synth", [
|
||||
OrichalcumInPool,
|
||||
OrichalcumPrice,
|
||||
MythrilInPool,
|
||||
MythrilPrice,
|
||||
]),
|
||||
OptionGroup("AP Costs", [
|
||||
RandomizeAPCosts,
|
||||
MaxAPCost,
|
||||
MinAPCost
|
||||
KeybladeMinMP,
|
||||
]),
|
||||
OptionGroup("Misc", [
|
||||
StartingWorlds,
|
||||
StartingTools,
|
||||
RandomizePuppies,
|
||||
PuppyValue,
|
||||
Puppies,
|
||||
InteractInBattle,
|
||||
LogicDifficulty,
|
||||
AdvancedLogic,
|
||||
ExtraSharedAbilities,
|
||||
StackingWorldItems,
|
||||
HalloweenTownKeyItemBundle,
|
||||
EXPZeroInPool,
|
||||
RandomizePartyMemberStartingAccessories,
|
||||
DeathLink,
|
||||
DonaldDeathLink,
|
||||
GoofyDeathLink,
|
||||
RemoteItems,
|
||||
ShortenGoMode,
|
||||
OneHP,
|
||||
FourByThree,
|
||||
AutoAttack,
|
||||
BeepHack,
|
||||
ConsistentFinishers,
|
||||
EarlySkip,
|
||||
FastCamera,
|
||||
FasterAnimations,
|
||||
Unlock0Volume,
|
||||
Unskippable,
|
||||
AutoSave,
|
||||
WarpAnywhere
|
||||
])
|
||||
]
|
||||
|
||||
+65
-237
@@ -3,33 +3,24 @@ from typing import Any, Dict
|
||||
from .Options import *
|
||||
|
||||
kh1_option_presets: Dict[str, Dict[str, Any]] = {
|
||||
# Standard playthrough where your goal is to defeat Ansem, reaching him by acquiring enough lucky emblems.
|
||||
# Standard playthrough where your goal is to defeat Ansem, reaching him by acquiring enough reports.
|
||||
"Final Ansem": {
|
||||
"final_rest_door_key": FinalRestDoorKey.option_lucky_emblems,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_lucky_emblems,
|
||||
"required_lucky_emblems_eotw": 7,
|
||||
"required_lucky_emblems_door": 10,
|
||||
"lucky_emblems_in_pool": 13,
|
||||
"required_postcards": 10,
|
||||
"required_puppies": 99,
|
||||
"destiny_islands": True,
|
||||
"day_2_materials": 4,
|
||||
"homecoming_materials": 10,
|
||||
"materials_in_pool": 13,
|
||||
"goal": Goal.option_final_ansem,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_reports,
|
||||
"final_rest_door": FinalRestDoor.option_reports,
|
||||
"required_reports_eotw": 7,
|
||||
"required_reports_door": 10,
|
||||
"reports_in_pool": 13,
|
||||
|
||||
"super_bosses": False,
|
||||
"atlantica": False,
|
||||
"hundred_acre_wood": False,
|
||||
"cups": Cups.option_off,
|
||||
"jungle_slider": False,
|
||||
"randomize_emblem_pieces": False,
|
||||
"randomize_postcards": RandomizePostcards.option_all,
|
||||
"cups": False,
|
||||
"vanilla_emblem_pieces": True,
|
||||
|
||||
"exp_multiplier": 64,
|
||||
"level_checks": 99,
|
||||
"slot_2_level_checks": 33,
|
||||
"max_level_for_slot_2_level_checks": 50,
|
||||
"force_stats_on_levels": 2,
|
||||
"exp_multiplier": 48,
|
||||
"level_checks": 100,
|
||||
"force_stats_on_levels": 1,
|
||||
"strength_increase": 24,
|
||||
"defense_increase": 24,
|
||||
"hp_increase": 23,
|
||||
@@ -39,83 +30,40 @@ kh1_option_presets: Dict[str, Dict[str, Any]] = {
|
||||
"item_slot_increase": 3,
|
||||
|
||||
"keyblades_unlock_chests": False,
|
||||
"keyblade_stats": KeybladeStats.option_shuffle,
|
||||
"randomize_keyblade_stats": True,
|
||||
"bad_starting_weapons": False,
|
||||
"keyblade_max_str": 14,
|
||||
"keyblade_min_str": 3,
|
||||
"keyblade_max_crit_rate": 200,
|
||||
"keyblade_min_crit_rate": 0,
|
||||
"keyblade_max_crit_str": 16,
|
||||
"keyblade_min_crit_str": 0,
|
||||
"keyblade_max_recoil": 90,
|
||||
"keyblade_min_recoil": 1,
|
||||
"keyblade_max_mp": 3,
|
||||
"keyblade_min_mp": -2,
|
||||
|
||||
"orichalcum_in_pool": 20,
|
||||
"orichalcum_price": 500,
|
||||
"mythril_in_pool": 20,
|
||||
"mythril_price": 500,
|
||||
|
||||
"randomize_ap_costs": RandomizeAPCosts.option_off,
|
||||
"max_ap_cost": 5,
|
||||
"min_ap_cost": 0,
|
||||
|
||||
"randomize_puppies": True,
|
||||
"puppy_value": 3,
|
||||
"starting_worlds": 4,
|
||||
"starting_tools": True,
|
||||
"interact_in_battle": True,
|
||||
"logic_difficulty": LogicDifficulty.option_normal,
|
||||
"extra_shared_abilities": True,
|
||||
"stacking_world_items": True,
|
||||
"halloween_town_key_item_bundle": True,
|
||||
"puppies": Puppies.option_triplets,
|
||||
"starting_worlds": 0,
|
||||
"interact_in_battle": False,
|
||||
"advanced_logic": False,
|
||||
"extra_shared_abilities": False,
|
||||
"exp_zero_in_pool": False,
|
||||
"randomize_party_member_starting_accessories": True,
|
||||
"death_link": False,
|
||||
"donald_death_link": False,
|
||||
"goofy_death_link": False,
|
||||
"remote_items": RemoteItems.option_off,
|
||||
"shorten_go_mode": True,
|
||||
"one_hp": False,
|
||||
"four_by_three": False,
|
||||
"beep_hack": False,
|
||||
"consistent_finishers": False,
|
||||
"early_skip": True,
|
||||
"fast_camera": False,
|
||||
"faster_animations": True,
|
||||
"unlock_0_volume": False,
|
||||
"unskippable": True,
|
||||
"auto_save": True,
|
||||
"warp_anywhere": False
|
||||
"goofy_death_link": False
|
||||
},
|
||||
# Puppies are found individually, and the goal is to return them all.
|
||||
"Puppy Hunt": {
|
||||
"final_rest_door_key": FinalRestDoorKey.option_puppies,
|
||||
"goal": Goal.option_puppies,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_item,
|
||||
"required_lucky_emblems_eotw": 13,
|
||||
"required_lucky_emblems_door": 13,
|
||||
"lucky_emblems_in_pool": 13,
|
||||
"required_postcards": 10,
|
||||
"required_puppies": 99,
|
||||
"destiny_islands": False,
|
||||
"day_2_materials": 4,
|
||||
"homecoming_materials": 10,
|
||||
"materials_in_pool": 13,
|
||||
"final_rest_door": FinalRestDoor.option_puppies,
|
||||
"required_reports_eotw": 13,
|
||||
"required_reports_door": 13,
|
||||
"reports_in_pool": 13,
|
||||
|
||||
"super_bosses": False,
|
||||
"atlantica": False,
|
||||
"hundred_acre_wood": False,
|
||||
"cups": Cups.option_off,
|
||||
"jungle_slider": False,
|
||||
"randomize_emblem_pieces": False,
|
||||
"randomize_postcards": RandomizePostcards.option_all,
|
||||
"cups": False,
|
||||
"vanilla_emblem_pieces": True,
|
||||
|
||||
"exp_multiplier": 64,
|
||||
"level_checks": 99,
|
||||
"slot_2_level_checks": 33,
|
||||
"max_level_for_slot_2_level_checks": 50,
|
||||
"force_stats_on_levels": 2,
|
||||
"exp_multiplier": 48,
|
||||
"level_checks": 100,
|
||||
"force_stats_on_levels": 1,
|
||||
"strength_increase": 24,
|
||||
"defense_increase": 24,
|
||||
"hp_increase": 23,
|
||||
@@ -125,83 +73,40 @@ kh1_option_presets: Dict[str, Dict[str, Any]] = {
|
||||
"item_slot_increase": 3,
|
||||
|
||||
"keyblades_unlock_chests": False,
|
||||
"keyblade_stats": KeybladeStats.option_shuffle,
|
||||
"randomize_keyblade_stats": True,
|
||||
"bad_starting_weapons": False,
|
||||
"keyblade_max_str": 14,
|
||||
"keyblade_min_str": 3,
|
||||
"keyblade_max_crit_rate": 200,
|
||||
"keyblade_min_crit_rate": 0,
|
||||
"keyblade_max_crit_str": 16,
|
||||
"keyblade_min_crit_str": 0,
|
||||
"keyblade_max_recoil": 90,
|
||||
"keyblade_min_recoil": 1,
|
||||
"keyblade_max_mp": 3,
|
||||
"keyblade_min_mp": -2,
|
||||
|
||||
"orichalcum_in_pool": 20,
|
||||
"orichalcum_price": 500,
|
||||
"mythril_in_pool": 20,
|
||||
"mythril_price": 500,
|
||||
|
||||
"randomize_ap_costs": RandomizeAPCosts.option_off,
|
||||
"max_ap_cost": 5,
|
||||
"min_ap_cost": 0,
|
||||
|
||||
"randomize_puppies": True,
|
||||
"puppy_value": 1,
|
||||
"puppies": Puppies.option_individual,
|
||||
"starting_worlds": 0,
|
||||
"starting_tools": True,
|
||||
"interact_in_battle": True,
|
||||
"logic_difficulty": LogicDifficulty.option_normal,
|
||||
"extra_shared_abilities": True,
|
||||
"stacking_world_items": True,
|
||||
"halloween_town_key_item_bundle": True,
|
||||
"interact_in_battle": False,
|
||||
"advanced_logic": False,
|
||||
"extra_shared_abilities": False,
|
||||
"exp_zero_in_pool": False,
|
||||
"randomize_party_member_starting_accessories": True,
|
||||
"death_link": False,
|
||||
"donald_death_link": False,
|
||||
"goofy_death_link": False,
|
||||
"remote_items": RemoteItems.option_off,
|
||||
"shorten_go_mode": True,
|
||||
"one_hp": False,
|
||||
"four_by_three": False,
|
||||
"beep_hack": False,
|
||||
"consistent_finishers": False,
|
||||
"early_skip": True,
|
||||
"fast_camera": False,
|
||||
"faster_animations": True,
|
||||
"unlock_0_volume": False,
|
||||
"unskippable": True,
|
||||
"auto_save": True,
|
||||
"warp_anywhere": False
|
||||
"goofy_death_link": False
|
||||
},
|
||||
# Advanced playthrough with most settings on.
|
||||
"Advanced": {
|
||||
"final_rest_door_key": FinalRestDoorKey.option_lucky_emblems,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_lucky_emblems,
|
||||
"required_lucky_emblems_eotw": 7,
|
||||
"required_lucky_emblems_door": 10,
|
||||
"lucky_emblems_in_pool": 13,
|
||||
"required_postcards": 10,
|
||||
"required_puppies": 99,
|
||||
"destiny_islands": True,
|
||||
"day_2_materials": 4,
|
||||
"homecoming_materials": 10,
|
||||
"materials_in_pool": 13,
|
||||
"goal": Goal.option_final_ansem,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_reports,
|
||||
"final_rest_door": FinalRestDoor.option_reports,
|
||||
"required_reports_eotw": 7,
|
||||
"required_reports_door": 10,
|
||||
"reports_in_pool": 13,
|
||||
|
||||
"super_bosses": True,
|
||||
"atlantica": True,
|
||||
"hundred_acre_wood": True,
|
||||
"cups": Cups.option_off,
|
||||
"jungle_slider": True,
|
||||
"randomize_emblem_pieces": True,
|
||||
"randomize_postcards": RandomizePostcards.option_all,
|
||||
"cups": True,
|
||||
"vanilla_emblem_pieces": False,
|
||||
|
||||
"exp_multiplier": 64,
|
||||
"level_checks": 99,
|
||||
"slot_2_level_checks": 33,
|
||||
"max_level_for_slot_2_level_checks": 50,
|
||||
"force_stats_on_levels": 2,
|
||||
"exp_multiplier": 48,
|
||||
"level_checks": 100,
|
||||
"force_stats_on_levels": 1,
|
||||
"strength_increase": 24,
|
||||
"defense_increase": 24,
|
||||
"hp_increase": 23,
|
||||
@@ -211,83 +116,40 @@ kh1_option_presets: Dict[str, Dict[str, Any]] = {
|
||||
"item_slot_increase": 3,
|
||||
|
||||
"keyblades_unlock_chests": True,
|
||||
"keyblade_stats": KeybladeStats.option_shuffle,
|
||||
"randomize_keyblade_stats": True,
|
||||
"bad_starting_weapons": True,
|
||||
"keyblade_max_str": 14,
|
||||
"keyblade_min_str": 3,
|
||||
"keyblade_max_crit_rate": 200,
|
||||
"keyblade_min_crit_rate": 0,
|
||||
"keyblade_max_crit_str": 16,
|
||||
"keyblade_min_crit_str": 0,
|
||||
"keyblade_max_recoil": 90,
|
||||
"keyblade_min_recoil": 1,
|
||||
"keyblade_max_mp": 3,
|
||||
"keyblade_min_mp": -2,
|
||||
|
||||
"orichalcum_in_pool": 20,
|
||||
"orichalcum_price": 500,
|
||||
"mythril_in_pool": 20,
|
||||
"mythril_price": 500,
|
||||
|
||||
"randomize_ap_costs": RandomizeAPCosts.option_off,
|
||||
"max_ap_cost": 5,
|
||||
"min_ap_cost": 0,
|
||||
|
||||
"randomize_puppies": True,
|
||||
"puppy_value": 3,
|
||||
"puppies": Puppies.option_triplets,
|
||||
"starting_worlds": 0,
|
||||
"starting_tools": True,
|
||||
"interact_in_battle": True,
|
||||
"logic_difficulty": LogicDifficulty.option_proud,
|
||||
"advanced_logic": True,
|
||||
"extra_shared_abilities": True,
|
||||
"stacking_world_items": True,
|
||||
"halloween_town_key_item_bundle": True,
|
||||
"exp_zero_in_pool": True,
|
||||
"randomize_party_member_starting_accessories": True,
|
||||
"death_link": False,
|
||||
"donald_death_link": False,
|
||||
"goofy_death_link": False,
|
||||
"remote_items": RemoteItems.option_off,
|
||||
"shorten_go_mode": True,
|
||||
"one_hp": False,
|
||||
"four_by_three": False,
|
||||
"beep_hack": False,
|
||||
"consistent_finishers": False,
|
||||
"early_skip": True,
|
||||
"fast_camera": False,
|
||||
"faster_animations": True,
|
||||
"unlock_0_volume": False,
|
||||
"unskippable": True,
|
||||
"auto_save": True,
|
||||
"warp_anywhere": False
|
||||
"goofy_death_link": False
|
||||
},
|
||||
# Playthrough meant to enhance the level 1 experience.
|
||||
"Level 1": {
|
||||
"final_rest_door_key": FinalRestDoorKey.option_lucky_emblems,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_lucky_emblems,
|
||||
"required_lucky_emblems_eotw": 7,
|
||||
"required_lucky_emblems_door": 10,
|
||||
"lucky_emblems_in_pool": 13,
|
||||
"required_postcards": 10,
|
||||
"required_puppies": 99,
|
||||
"destiny_islands": True,
|
||||
"day_2_materials": 4,
|
||||
"homecoming_materials": 10,
|
||||
"materials_in_pool": 13,
|
||||
"goal": Goal.option_final_ansem,
|
||||
"end_of_the_world_unlock": EndoftheWorldUnlock.option_reports,
|
||||
"final_rest_door": FinalRestDoor.option_reports,
|
||||
"required_reports_eotw": 7,
|
||||
"required_reports_door": 10,
|
||||
"reports_in_pool": 13,
|
||||
|
||||
"super_bosses": False,
|
||||
"atlantica": False,
|
||||
"hundred_acre_wood": False,
|
||||
"cups": Cups.option_off,
|
||||
"jungle_slider": False,
|
||||
"randomize_emblem_pieces": False,
|
||||
"randomize_postcards": RandomizePostcards.option_all,
|
||||
"cups": False,
|
||||
"vanilla_emblem_pieces": True,
|
||||
|
||||
"exp_multiplier": 16,
|
||||
"level_checks": 0,
|
||||
"slot_2_level_checks": 0,
|
||||
"max_level_for_slot_2_level_checks": 50,
|
||||
"force_stats_on_levels": 2,
|
||||
"force_stats_on_levels": 101,
|
||||
"strength_increase": 0,
|
||||
"defense_increase": 0,
|
||||
"hp_increase": 0,
|
||||
@@ -296,54 +158,20 @@ kh1_option_presets: Dict[str, Dict[str, Any]] = {
|
||||
"item_slot_increase": 5,
|
||||
|
||||
"keyblades_unlock_chests": False,
|
||||
"keyblade_stats": KeybladeStats.option_shuffle,
|
||||
"randomize_keyblade_stats": True,
|
||||
"bad_starting_weapons": False,
|
||||
"keyblade_max_str": 14,
|
||||
"keyblade_min_str": 3,
|
||||
"keyblade_max_crit_rate": 200,
|
||||
"keyblade_min_crit_rate": 0,
|
||||
"keyblade_max_crit_str": 16,
|
||||
"keyblade_min_crit_str": 0,
|
||||
"keyblade_max_recoil": 90,
|
||||
"keyblade_min_recoil": 1,
|
||||
"keyblade_max_mp": 3,
|
||||
"keyblade_min_mp": -2,
|
||||
|
||||
"orichalcum_in_pool": 20,
|
||||
"orichalcum_price": 500,
|
||||
"mythril_in_pool": 20,
|
||||
"mythril_price": 500,
|
||||
|
||||
"randomize_ap_costs": RandomizeAPCosts.option_off,
|
||||
"max_ap_cost": 5,
|
||||
"min_ap_cost": 0,
|
||||
|
||||
"randomize_puppies": True,
|
||||
"puppy_value": 3,
|
||||
"puppies": Puppies.option_triplets,
|
||||
"starting_worlds": 0,
|
||||
"starting_tools": True,
|
||||
"interact_in_battle": True,
|
||||
"logic_difficulty": LogicDifficulty.option_normal,
|
||||
"extra_shared_abilities": True,
|
||||
"stacking_world_items": True,
|
||||
"halloween_town_key_item_bundle": True,
|
||||
"interact_in_battle": False,
|
||||
"advanced_logic": False,
|
||||
"extra_shared_abilities": False,
|
||||
"exp_zero_in_pool": False,
|
||||
"randomize_party_member_starting_accessories": True,
|
||||
"death_link": False,
|
||||
"donald_death_link": False,
|
||||
"goofy_death_link": False,
|
||||
"remote_items": RemoteItems.option_off,
|
||||
"shorten_go_mode": True,
|
||||
"one_hp": False,
|
||||
"four_by_three": False,
|
||||
"beep_hack": False,
|
||||
"consistent_finishers": True,
|
||||
"early_skip": True,
|
||||
"fast_camera": False,
|
||||
"faster_animations": True,
|
||||
"unlock_0_volume": False,
|
||||
"unskippable": True,
|
||||
"auto_save": True,
|
||||
"warp_anywhere": False
|
||||
"goofy_death_link": False
|
||||
}
|
||||
}
|
||||
|
||||
+56
-153
@@ -9,16 +9,12 @@ class KH1RegionData(NamedTuple):
|
||||
region_exits: Optional[List[str]]
|
||||
|
||||
|
||||
def create_regions(kh1world):
|
||||
multiworld = kh1world.multiworld
|
||||
player = kh1world.player
|
||||
options = kh1world.options
|
||||
|
||||
def create_regions(multiworld: MultiWorld, player: int, options):
|
||||
regions: Dict[str, KH1RegionData] = {
|
||||
"Menu": KH1RegionData([], ["Awakening", "Levels", "World Map"]),
|
||||
"Awakening": KH1RegionData([], []),
|
||||
"Destiny Islands": KH1RegionData([], []),
|
||||
"Traverse Town": KH1RegionData([], []),
|
||||
"Menu": KH1RegionData([], ["Awakening", "Levels"]),
|
||||
"Awakening": KH1RegionData([], ["Destiny Islands"]),
|
||||
"Destiny Islands": KH1RegionData([], ["Traverse Town"]),
|
||||
"Traverse Town": KH1RegionData([], ["World Map"]),
|
||||
"Wonderland": KH1RegionData([], []),
|
||||
"Olympus Coliseum": KH1RegionData([], []),
|
||||
"Deep Jungle": KH1RegionData([], []),
|
||||
@@ -31,27 +27,17 @@ def create_regions(kh1world):
|
||||
"End of the World": KH1RegionData([], []),
|
||||
"100 Acre Wood": KH1RegionData([], []),
|
||||
"Levels": KH1RegionData([], []),
|
||||
"Homecoming": KH1RegionData([], []),
|
||||
"World Map": KH1RegionData([], ["Destiny Islands", "Traverse Town",
|
||||
"Wonderland", "Olympus Coliseum", "Deep Jungle",
|
||||
"World Map": KH1RegionData([], ["Wonderland", "Olympus Coliseum", "Deep Jungle",
|
||||
"Agrabah", "Monstro", "Atlantica",
|
||||
"Halloween Town", "Neverland", "Hollow Bastion",
|
||||
"End of the World", "100 Acre Wood", "Homecoming"])
|
||||
"End of the World", "100 Acre Wood"])
|
||||
}
|
||||
|
||||
if not options.atlantica:
|
||||
del regions["Atlantica"]
|
||||
regions["World Map"].region_exits.remove("Atlantica")
|
||||
if not options.destiny_islands:
|
||||
del regions["Destiny Islands"]
|
||||
regions["World Map"].region_exits.remove("Destiny Islands")
|
||||
|
||||
# Set up locations
|
||||
regions["Agrabah"].locations.append("Agrabah Aladdin's House Main Street Entrance Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Aladdin's House Plaza Entrance Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Alley Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Bazaar Across Windows Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Bazaar Blue Trinity")
|
||||
regions["Agrabah"].locations.append("Agrabah Bazaar High Corner Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Bottomless Hall Across Chasm Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Bottomless Hall Pillar Chest")
|
||||
@@ -73,7 +59,6 @@ def create_regions(kh1world):
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Above Fire Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Across Platforms Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Large Treasure Pile Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Red Trinity")
|
||||
regions["Agrabah"].locations.append("Agrabah Cave of Wonders Treasure Room Small Treasure Pile Chest")
|
||||
regions["Agrabah"].locations.append("Agrabah Defeat Jafar Blizzard Event")
|
||||
regions["Agrabah"].locations.append("Agrabah Defeat Jafar Genie Ansem's Report 1")
|
||||
@@ -111,11 +96,15 @@ def create_regions(kh1world):
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Hippo's Lagoon Center Chest")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Hippo's Lagoon Left Chest")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Hippo's Lagoon Right Chest")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 10 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 20 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 30 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 40 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 50 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Seal Keyhole Jungle King Event")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Seal Keyhole Red Trinity Event")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Tent Chest")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Tent Protect-G Event")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Treetop Green Trinity")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Beneath Tree House Chest")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Rooftop Chest")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Tree House Save Gorillas")
|
||||
@@ -149,7 +138,7 @@ def create_regions(kh1world):
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Atlantica Chest")
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Deep Jungle Chest")
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Halloween Town Chest")
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Hollow Bastion Chest")
|
||||
#regions["End of the World"].locations.append("End of the World World Terminus Hollow Bastion Chest")
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Neverland Chest")
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Olympus Coliseum Chest")
|
||||
regions["End of the World"].locations.append("End of the World World Terminus Traverse Town Chest")
|
||||
@@ -192,7 +181,6 @@ def create_regions(kh1world):
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Maleficent Donald Cheer Event")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Riku I White Trinity Event")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Riku II Ragnarok Event")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Dungeon Blue Trinity")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Dungeon By Candles Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Dungeon Corner Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Entrance Hall Emblem Piece (Chest)")
|
||||
@@ -204,7 +192,6 @@ def create_regions(kh1world):
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Grand Hall Oblivion Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Grand Hall Steps Right Side Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Great Crest After Battle Platform Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Great Crest Blue Trinity")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Great Crest Lower Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion High Tower 1st Gravity Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion High Tower 2nd Gravity Chest")
|
||||
@@ -216,7 +203,6 @@ def create_regions(kh1world):
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Speak to Belle Divine Rose")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Top of Bookshelf Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Library Top of Bookshelf Turn the Carousel Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop from Waterway Examine Node")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Heartless Sigil Door Gravity Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Library Node After High Tower Switch Gravity Chest")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Lift Stop Library Node Gravity Chest")
|
||||
@@ -244,7 +230,6 @@ def create_regions(kh1world):
|
||||
regions["Monstro"].locations.append("Monstro Chamber 3 Platform Above Chamber 2 Entrance Chest")
|
||||
regions["Monstro"].locations.append("Monstro Chamber 3 Platform Near Chamber 6 Entrance Chest")
|
||||
regions["Monstro"].locations.append("Monstro Chamber 5 Atop Barrel Chest")
|
||||
regions["Monstro"].locations.append("Monstro Chamber 5 Blue Trinity")
|
||||
regions["Monstro"].locations.append("Monstro Chamber 5 Low 1st Chest")
|
||||
regions["Monstro"].locations.append("Monstro Chamber 5 Low 2nd Chest")
|
||||
regions["Monstro"].locations.append("Monstro Chamber 5 Platform Chest")
|
||||
@@ -255,28 +240,26 @@ def create_regions(kh1world):
|
||||
regions["Monstro"].locations.append("Monstro Chamber 6 White Trinity Chest")
|
||||
regions["Monstro"].locations.append("Monstro Defeat Parasite Cage I Goofy Cheer Event")
|
||||
regions["Monstro"].locations.append("Monstro Defeat Parasite Cage II Stop Event")
|
||||
regions["Monstro"].locations.append("Monstro Mouth Blue Trinity")
|
||||
regions["Monstro"].locations.append("Monstro Mouth Boat Deck Chest")
|
||||
regions["Monstro"].locations.append("Monstro Mouth Green Trinity Top of Boat Chest")
|
||||
regions["Monstro"].locations.append("Monstro Mouth High Platform Across from Boat Chest")
|
||||
regions["Monstro"].locations.append("Monstro Mouth High Platform Boat Side Chest")
|
||||
regions["Monstro"].locations.append("Monstro Mouth High Platform Near Teeth Chest")
|
||||
regions["Monstro"].locations.append("Monstro Mouth Near Ship Chest")
|
||||
regions["Monstro"].locations.append("Monstro Throat Blue Trinity")
|
||||
regions["Neverland"].locations.append("Neverland Cabin Chest")
|
||||
regions["Neverland"].locations.append("Neverland Captain's Cabin Chest")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 01:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 02:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 03:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 04:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 05:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 06:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 07:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 08:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 09:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 10:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 11:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower 12:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 01:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 02:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 03:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 04:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 05:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 06:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 07:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 08:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 09:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 10:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 11:00 Door")
|
||||
#regions["Neverland"].locations.append("Neverland Clock Tower 12:00 Door")
|
||||
regions["Neverland"].locations.append("Neverland Clock Tower Chest")
|
||||
regions["Neverland"].locations.append("Neverland Defeat Anti Sora Raven's Claw Event")
|
||||
regions["Neverland"].locations.append("Neverland Defeat Captain Hook Ars Arcanum Event")
|
||||
@@ -293,7 +276,6 @@ def create_regions(kh1world):
|
||||
regions["Neverland"].locations.append("Neverland Pirate Ship Deck White Trinity Chest")
|
||||
regions["Neverland"].locations.append("Neverland Seal Keyhole Fairy Harp Event")
|
||||
regions["Neverland"].locations.append("Neverland Seal Keyhole Glide Event")
|
||||
regions["Neverland"].locations.append("Neverland Seal Keyhole Navi-G Piece Event")
|
||||
regions["Neverland"].locations.append("Neverland Seal Keyhole Tinker Bell Event")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Clear Phil's Training Thunder Event")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Cloud Sonic Blade Event")
|
||||
@@ -310,16 +292,14 @@ def create_regions(kh1world):
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Accessory Shop Roof Chest")
|
||||
#regions["Traverse Town"].locations.append("Traverse Town 1st District Aerith Gift")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Blue Trinity Balcony Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Blue Trinity by Exit Door")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Candle Puzzle Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Leon Gift")
|
||||
#regions["Traverse Town"].locations.append("Traverse Town 1st District Leon Gift")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Safe Postcard")
|
||||
#regions["Traverse Town"].locations.append("Traverse Town 1st District Speak with Cid Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 1st District Speak with Cid Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 2nd District Boots and Shoes Awning Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 2nd District Gizmo Shop Facade Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 2nd District Rooftop Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 3rd District Balcony Postcard")
|
||||
regions["Traverse Town"].locations.append("Traverse Town 3rd District Blue Trinity")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Accessory Shop Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Alleyway Balcony Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Alleyway Behind Crates Chest")
|
||||
@@ -330,7 +310,6 @@ def create_regions(kh1world):
|
||||
regions["Traverse Town"].locations.append("Traverse Town Defeat Guard Armor Dodge Roll Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Defeat Guard Armor Fire Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Defeat Opposite Armor Aero Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Defeat Opposite Armor Navi-G Piece Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto All Summons Reward")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Geppetto's House Geppetto Reward 1")
|
||||
@@ -350,7 +329,6 @@ def create_regions(kh1world):
|
||||
regions["Traverse Town"].locations.append("Traverse Town Item Workshop Right Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Kairi Secret Waterway Oathkeeper Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Leon Secret Waterway Earthshine Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Blue Trinity")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Obtained All Arts Items")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Obtained All LV1 Magic")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Magician's Study Obtained All LV3 Magic")
|
||||
@@ -379,62 +357,26 @@ def create_regions(kh1world):
|
||||
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 99 Puppies Reward 1")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Piano Room Return 99 Puppies Reward 2")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Red Room Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Secret Waterway Navi Gummi Event")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Secret Waterway Near Stairs Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Secret Waterway White Trinity Chest")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth 15 Items")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 01")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 02")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 03")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 04")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 05")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 06")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 07")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 08")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 09")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 10")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 11")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 12")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 13")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 14")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 15")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 16")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 17")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 18")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 19")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 20")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 21")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 22")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 23")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 24")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 25")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 26")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 27")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 28")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 29")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 30")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 31")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 32")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Item 33")
|
||||
regions["Wonderland"].locations.append("Wonderland Bizarre Room Examine Flower Pot")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Cloth")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Fish")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Log")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Mushroom")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Rope")
|
||||
regions["Traverse Town"].locations.append("Traverse Town Synth Seagull Egg")
|
||||
regions["Wonderland"].locations.append("Wonderland Bizarre Room Green Trinity Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Bizarre Room Lamp Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Bizarre Room Navi-G Piece Event")
|
||||
regions["Wonderland"].locations.append("Wonderland Bizarre Room Read Book")
|
||||
regions["Wonderland"].locations.append("Wonderland Defeat Trickmaster Blizzard Event")
|
||||
regions["Wonderland"].locations.append("Wonderland Defeat Trickmaster Ifrit's Horn Event")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Blue Trinity in Alcove")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Blue Trinity by Moving Boulder")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Corner Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Glide Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Nut Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Red Flower Raise Lily Pads")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Red Flowers on the Main Path")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Through the Painting Thunder Plant Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Through the Painting White Trinity Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Thunder Plant Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Yellow Elixir Flower Through Painting")
|
||||
regions["Wonderland"].locations.append("Wonderland Lotus Forest Yellow Flowers in Middle Clearing and Through Painting")
|
||||
regions["Wonderland"].locations.append("Wonderland Queen's Castle Hedge Left Red Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Queen's Castle Hedge Right Blue Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Queen's Castle Hedge Right Red Chest")
|
||||
@@ -446,11 +388,6 @@ def create_regions(kh1world):
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Above Lotus Forest Entrance 2nd Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Across From Bizarre Room Entrance Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Bear and Clock Puzzle Chest")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Left Cushioned Chair")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Left Gray Chair")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Left Pink Chair")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Right Brown Chair")
|
||||
regions["Wonderland"].locations.append("Wonderland Tea Party Garden Right Yellow Chair")
|
||||
if options.hundred_acre_wood:
|
||||
regions["100 Acre Wood"].locations.append("100 Acre Wood Meadow Inside Log Chest")
|
||||
regions["100 Acre Wood"].locations.append("100 Acre Wood Bouncing Spot Left Cliff Chest")
|
||||
@@ -503,7 +440,7 @@ def create_regions(kh1world):
|
||||
regions["Atlantica"].locations.append("Atlantica Undersea Cave Clam")
|
||||
regions["Atlantica"].locations.append("Atlantica Sunken Ship Crystal Trident Event")
|
||||
regions["Atlantica"].locations.append("Atlantica Defeat Ursula II Ansem's Report 3")
|
||||
if options.cups.current_key != "off":
|
||||
if options.cups:
|
||||
regions["Olympus Coliseum"].locations.append("Complete Phil Cup")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Phil Cup Solo")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Phil Cup Time Trial")
|
||||
@@ -513,84 +450,50 @@ def create_regions(kh1world):
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hercules Cup")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hercules Cup Solo")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hercules Cup Time Trial")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hades Cup")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hades Cup Solo")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hades Cup Time Trial")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Cloud and Leon Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Yuffie Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Cerberus Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Behemoth Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Hades Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hercules Cup Defeat Cloud Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hercules Cup Yellow Trinity Event")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Hades Ansem's Report 8")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Olympia Chest")
|
||||
if options.cups.current_key == "hades_cup":
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hades Cup")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hades Cup Solo")
|
||||
regions["Olympus Coliseum"].locations.append("Complete Hades Cup Time Trial")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Cloud and Leon Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Yuffie Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Cerberus Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Behemoth Event")
|
||||
regions["Olympus Coliseum"].locations.append("Hades Cup Defeat Hades Event")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Hades Ansem's Report 8")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Gates Purple Jar After Defeating Hades")
|
||||
if options.cups.current_key == "hades_cup" and options.super_bosses:
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Ice Titan Diamond Dust Event")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Ice Titan Diamond Dust Event")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Gates Purple Jar After Defeating Hades")
|
||||
if options.super_bosses:
|
||||
regions["Neverland"].locations.append("Neverland Defeat Phantom Stop Event")
|
||||
regions["Agrabah"].locations.append("Agrabah Defeat Kurt Zisa Zantetsuken Event")
|
||||
regions["Agrabah"].locations.append("Agrabah Defeat Kurt Zisa Ansem's Report 11")
|
||||
if options.super_bosses or options.final_rest_door_key.current_key == "sephiroth":
|
||||
if options.super_bosses or options.goal.current_key == "sephiroth":
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Sephiroth Ansem's Report 12")
|
||||
regions["Olympus Coliseum"].locations.append("Olympus Coliseum Defeat Sephiroth One-Winged Angel Event")
|
||||
if options.super_bosses or options.final_rest_door_key.current_key == "unknown":
|
||||
if options.super_bosses or options.goal.current_key == "unknown":
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Unknown Ansem's Report 13")
|
||||
regions["Hollow Bastion"].locations.append("Hollow Bastion Defeat Unknown EXP Necklace Event")
|
||||
if options.jungle_slider:
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 10 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 20 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 30 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 40 Fruits")
|
||||
regions["Deep Jungle"].locations.append("Deep Jungle Jungle Slider 50 Fruits")
|
||||
for i in range(1,options.level_checks+1):
|
||||
regions["Levels"].locations.append("Level " + str(i+1).rjust(3, '0') + " (Slot 1)")
|
||||
if i+1 in kh1world.get_slot_2_levels():
|
||||
regions["Levels"].locations.append("Level " + str(i+1).rjust(3, '0') + " (Slot 2)")
|
||||
if options.destiny_islands:
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Capture Fish 1 (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Capture Fish 2 (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Capture Fish 3 (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Gather Seagull Egg (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Log on Riku's Island (Day 1)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Log under Bridge (Day 1)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Gather Cloth (Day 1)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Seashore Gather Rope (Day 1)")
|
||||
#regions["Destiny Islands"].locations.append("Destiny Islands Seashore Deliver Kairi Items (Day 1)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Secret Place Gather Mushroom (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Cove Gather Mushroom Near Zip Line (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Cove Gather Mushroom in Small Cave (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Cove Talk to Kairi (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Gather Drinking Water (Day 2)")
|
||||
#regions["Destiny Islands"].locations.append("Destiny Islands Cove Deliver Kairi Items (Day 2)")
|
||||
regions["Destiny Islands"].locations.append("Destiny Islands Chest")
|
||||
regions["Homecoming"].locations.append("Final Ansem")
|
||||
|
||||
for location in kh1world.get_starting_accessory_locations():
|
||||
regions[location_table[location].category].locations.append(location)
|
||||
for i in range(options.level_checks):
|
||||
regions["Levels"].locations.append("Level " + str(i+1).rjust(3, '0'))
|
||||
if options.goal.current_key == "final_ansem":
|
||||
regions["End of the World"].locations.append("Final Ansem")
|
||||
|
||||
# Set up the regions correctly.
|
||||
for name, data in regions.items():
|
||||
multiworld.regions.append(create_region(multiworld, player, name, data))
|
||||
|
||||
def connect_entrances(kh1world):
|
||||
multiworld = kh1world.multiworld
|
||||
player = kh1world.player
|
||||
options = kh1world.options
|
||||
|
||||
|
||||
def connect_entrances(multiworld: MultiWorld, player: int):
|
||||
multiworld.get_entrance("Awakening", player).connect(multiworld.get_region("Awakening", player))
|
||||
if options.destiny_islands:
|
||||
multiworld.get_entrance("Destiny Islands", player).connect(multiworld.get_region("Destiny Islands", player))
|
||||
multiworld.get_entrance("Destiny Islands", player).connect(multiworld.get_region("Destiny Islands", player))
|
||||
multiworld.get_entrance("Traverse Town", player).connect(multiworld.get_region("Traverse Town", player))
|
||||
multiworld.get_entrance("Wonderland", player).connect(multiworld.get_region("Wonderland", player))
|
||||
multiworld.get_entrance("Olympus Coliseum", player).connect(multiworld.get_region("Olympus Coliseum", player))
|
||||
multiworld.get_entrance("Deep Jungle", player).connect(multiworld.get_region("Deep Jungle", player))
|
||||
multiworld.get_entrance("Agrabah", player).connect(multiworld.get_region("Agrabah", player))
|
||||
multiworld.get_entrance("Monstro", player).connect(multiworld.get_region("Monstro", player))
|
||||
if options.atlantica:
|
||||
multiworld.get_entrance("Atlantica", player).connect(multiworld.get_region("Atlantica", player))
|
||||
multiworld.get_entrance("Atlantica", player).connect(multiworld.get_region("Atlantica", player))
|
||||
multiworld.get_entrance("Halloween Town", player).connect(multiworld.get_region("Halloween Town", player))
|
||||
multiworld.get_entrance("Neverland", player).connect(multiworld.get_region("Neverland", player))
|
||||
multiworld.get_entrance("Hollow Bastion", player).connect(multiworld.get_region("Hollow Bastion", player))
|
||||
@@ -598,7 +501,7 @@ def connect_entrances(kh1world):
|
||||
multiworld.get_entrance("100 Acre Wood", player).connect(multiworld.get_region("100 Acre Wood", player))
|
||||
multiworld.get_entrance("World Map", player).connect(multiworld.get_region("World Map", player))
|
||||
multiworld.get_entrance("Levels", player).connect(multiworld.get_region("Levels", player))
|
||||
multiworld.get_entrance("Homecoming", player).connect(multiworld.get_region("Homecoming", player))
|
||||
|
||||
|
||||
def create_region(multiworld: MultiWorld, player: int, name: str, data: KH1RegionData):
|
||||
region = Region(name, player, multiworld)
|
||||
|
||||
+1213
-1045
File diff suppressed because it is too large
Load Diff
+107
-436
@@ -1,29 +1,23 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import List
|
||||
from math import ceil
|
||||
|
||||
from BaseClasses import Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Items import KH1Item, KH1ItemData, event_item_table, get_items_by_category, item_table, item_name_groups
|
||||
from .Locations import KH1Location, location_table, get_locations_by_type, location_name_groups
|
||||
from .Locations import KH1Location, location_table, get_locations_by_category, location_name_groups
|
||||
from .Options import KH1Options, kh1_option_groups
|
||||
from .Regions import connect_entrances, create_regions
|
||||
from .Rules import set_rules
|
||||
from .Presets import kh1_option_presets
|
||||
from worlds.LauncherComponents import Component, components, Type, launch as launch_component, icon_paths
|
||||
from .GenerateJSON import generate_json
|
||||
from .Data import VANILLA_KEYBLADE_STATS, VANILLA_PUPPY_LOCATIONS, CHAR_TO_KH, VANILLA_ABILITY_AP_COSTS, WORLD_KEY_ITEMS
|
||||
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
|
||||
from worlds.LauncherComponents import Component, components, Type, launch as launch_component
|
||||
|
||||
|
||||
def launch_client():
|
||||
from .Client import launch
|
||||
launch_component(launch, name="KH1 Client")
|
||||
|
||||
|
||||
components.append(Component("KH1 Client", func=launch_client, component_type=Type.CLIENT, icon="kh1_heart"))
|
||||
|
||||
icon_paths["kh1_heart"] = f"ap:{__name__}/icons/kh1_heart.png"
|
||||
components.append(Component("KH1 Client", "KH1Client", func=launch_client, component_type=Type.CLIENT))
|
||||
|
||||
|
||||
class KH1Web(WebWorld):
|
||||
@@ -60,19 +54,6 @@ class KH1World(World):
|
||||
fillers.update(get_items_by_category("Item"))
|
||||
fillers.update(get_items_by_category("Camping"))
|
||||
fillers.update(get_items_by_category("Stat Ups"))
|
||||
slot_2_levels: list[int]
|
||||
keyblade_stats: list[dict[str, int]]
|
||||
starting_accessory_locations: list[str]
|
||||
starting_accessories: list[str]
|
||||
ap_costs: list[dict[str, str | int | bool]]
|
||||
|
||||
def __init__(self, multiworld, player):
|
||||
super(KH1World, self).__init__(multiworld, player)
|
||||
self.slot_2_levels = None
|
||||
self.keyblade_stats = None
|
||||
self.starting_accessory_locations = None
|
||||
self.starting_accessories = None
|
||||
self.ap_costs = None
|
||||
|
||||
def create_items(self):
|
||||
self.place_predetermined_items()
|
||||
@@ -82,29 +63,12 @@ class KH1World(World):
|
||||
possible_starting_worlds = ["Wonderland", "Olympus Coliseum", "Deep Jungle", "Agrabah", "Monstro", "Halloween Town", "Neverland", "Hollow Bastion"]
|
||||
if self.options.atlantica:
|
||||
possible_starting_worlds.append("Atlantica")
|
||||
if self.options.destiny_islands:
|
||||
possible_starting_worlds.append("Destiny Islands")
|
||||
if self.options.end_of_the_world_unlock == "item":
|
||||
possible_starting_worlds.append("End of the World")
|
||||
starting_worlds = self.random.sample(possible_starting_worlds, min(self.options.starting_worlds.value, len(possible_starting_worlds)))
|
||||
for starting_world in starting_worlds:
|
||||
self.multiworld.push_precollected(self.create_item(starting_world))
|
||||
|
||||
# Handle starting tools
|
||||
starting_tools = []
|
||||
if self.options.starting_tools:
|
||||
starting_tools = ["Scan", "Dodge Roll"]
|
||||
self.multiworld.push_precollected(self.create_item("Scan"))
|
||||
self.multiworld.push_precollected(self.create_item("Dodge Roll"))
|
||||
|
||||
# Handle starting party member accessories
|
||||
starting_party_member_accessories = []
|
||||
starting_party_member_locations = []
|
||||
starting_party_member_locations = self.get_starting_accessory_locations()
|
||||
starting_party_member_accessories = self.get_starting_accessories()
|
||||
for i in range(len(starting_party_member_locations)):
|
||||
self.get_location(self.starting_accessory_locations[i]).place_locked_item(self.create_item(self.starting_accessories[i]))
|
||||
|
||||
item_pool: List[KH1Item] = []
|
||||
possible_level_up_item_pool = []
|
||||
level_up_item_pool = []
|
||||
@@ -130,26 +94,19 @@ class KH1World(World):
|
||||
|
||||
# Fill remaining pool with items from other pool
|
||||
self.random.shuffle(possible_level_up_item_pool)
|
||||
level_up_item_pool = level_up_item_pool + possible_level_up_item_pool[:(99 - len(level_up_item_pool))]
|
||||
|
||||
level_up_locations = list(get_locations_by_type("Level Slot 1").keys())
|
||||
level_up_item_pool = level_up_item_pool + possible_level_up_item_pool[:(100 - len(level_up_item_pool))]
|
||||
|
||||
level_up_locations = list(get_locations_by_category("Levels").keys())
|
||||
self.random.shuffle(level_up_item_pool)
|
||||
current_level_index_for_placing_stats = self.options.force_stats_on_levels.value - 2 # Level 2 is index 0, Level 3 is index 1, etc
|
||||
if self.options.remote_items.current_key == "off" and self.options.force_stats_on_levels.value != 2:
|
||||
logging.info(f"{self.player_name}'s value {self.options.force_stats_on_levels.value} for force_stats_on_levels was changed\n"
|
||||
f"Set to 2 as remote_items if \"off\"")
|
||||
self.options.force_stats_on_levels.value = 2
|
||||
current_level_index_for_placing_stats = 0
|
||||
while len(level_up_item_pool) > 0 and current_level_index_for_placing_stats < self.options.level_checks: # With all levels in location pool, 99 level ups so need to go index 0-98
|
||||
self.get_location(level_up_locations[current_level_index_for_placing_stats]).place_locked_item(self.create_item(level_up_item_pool.pop()))
|
||||
current_level_index_for_placing_stats += 1
|
||||
|
||||
|
||||
current_level_for_placing_stats = self.options.force_stats_on_levels.value
|
||||
while len(level_up_item_pool) > 0 and current_level_for_placing_stats <= self.options.level_checks:
|
||||
self.get_location(level_up_locations[current_level_for_placing_stats - 1]).place_locked_item(self.create_item(level_up_item_pool.pop()))
|
||||
current_level_for_placing_stats += 1
|
||||
|
||||
# Calculate prefilled locations and items
|
||||
exclude_items = ["Final Door Key", "Lucky Emblem"]
|
||||
if not self.options.randomize_emblem_pieces:
|
||||
exclude_items = exclude_items + ["Emblem Piece (Flame)", "Emblem Piece (Chest)", "Emblem Piece (Fountain)", "Emblem Piece (Statue)"]
|
||||
prefilled_items = []
|
||||
if self.options.vanilla_emblem_pieces:
|
||||
prefilled_items = prefilled_items + ["Emblem Piece (Flame)", "Emblem Piece (Chest)", "Emblem Piece (Fountain)", "Emblem Piece (Statue)"]
|
||||
|
||||
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
|
||||
|
||||
@@ -160,29 +117,27 @@ class KH1World(World):
|
||||
quantity = data.max_quantity
|
||||
if data.category not in non_filler_item_categories:
|
||||
continue
|
||||
if name in starting_worlds or name in starting_tools or name in starting_party_member_accessories:
|
||||
if name in starting_worlds:
|
||||
continue
|
||||
if self.options.stacking_world_items and name in WORLD_KEY_ITEMS.keys() and name not in ("Crystal Trident", "Jack-In-The-Box"): # Handling these special cases separately
|
||||
item_pool += [self.create_item(WORLD_KEY_ITEMS[name]) for _ in range(0, 1)]
|
||||
elif self.options.halloween_town_key_item_bundle and name == "Jack-In-The-Box":
|
||||
continue
|
||||
elif name == "Puppy":
|
||||
if self.options.randomize_puppies:
|
||||
item_pool += [self.create_item(name) for _ in range(ceil(99/self.options.puppy_value.value))]
|
||||
if data.category == "Puppies":
|
||||
if self.options.puppies == "triplets" and "-" in name:
|
||||
item_pool += [self.create_item(name) for _ in range(quantity)]
|
||||
if self.options.puppies == "individual" and "Puppy" in name:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
if self.options.puppies == "full" and name == "All Puppies":
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Atlantica":
|
||||
if self.options.atlantica:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Mermaid Kick":
|
||||
if self.options.atlantica and self.options.extra_shared_abilities:
|
||||
item_pool += [self.create_item(name) for _ in range(0, 2)]
|
||||
else:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Crystal Trident":
|
||||
if self.options.atlantica:
|
||||
if self.options.stacking_world_items:
|
||||
item_pool += [self.create_item(WORLD_KEY_ITEMS[name]) for _ in range(0, 1)]
|
||||
if self.options.extra_shared_abilities:
|
||||
item_pool += [self.create_item(name) for _ in range(0, 2)]
|
||||
else:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Crystal Trident":
|
||||
if self.options.atlantica:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "High Jump":
|
||||
if self.options.extra_shared_abilities:
|
||||
item_pool += [self.create_item(name) for _ in range(0, 3)]
|
||||
@@ -199,26 +154,11 @@ class KH1World(World):
|
||||
elif name == "EXP Zero":
|
||||
if self.options.exp_zero_in_pool:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Postcard":
|
||||
if self.options.randomize_postcards.current_key == "chests":
|
||||
item_pool += [self.create_item(name) for _ in range(0, 3)]
|
||||
if self.options.randomize_postcards.current_key == "all":
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Orichalcum":
|
||||
item_pool += [self.create_item(name) for _ in range(0, self.options.orichalcum_in_pool.value)]
|
||||
elif name == "Mythril":
|
||||
item_pool += [self.create_item(name) for _ in range(0, self.options.mythril_in_pool.value)]
|
||||
elif name == "Destiny Islands":
|
||||
if self.options.destiny_islands:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Raft Materials":
|
||||
if self.options.destiny_islands:
|
||||
item_pool += [self.create_item(name) for _ in range(0, self.options.materials_in_pool.value)]
|
||||
elif name not in exclude_items:
|
||||
elif name not in prefilled_items:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
|
||||
for i in range(self.determine_lucky_emblems_in_pool()):
|
||||
item_pool += [self.create_item("Lucky Emblem")]
|
||||
for i in range(self.determine_reports_in_pool()):
|
||||
item_pool += [self.create_item("Ansem's Report " + str(i+1))]
|
||||
|
||||
while len(item_pool) < total_locations and len(level_up_item_pool) > 0:
|
||||
item_pool += [self.create_item(level_up_item_pool.pop())]
|
||||
@@ -230,117 +170,63 @@ class KH1World(World):
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def place_predetermined_items(self) -> None:
|
||||
if self.options.final_rest_door_key.current_key not in ["puppies", "postcards", "lucky_emblems"]:
|
||||
goal_dict = {
|
||||
"sephiroth": "Olympus Coliseum Defeat Sephiroth Ansem's Report 12",
|
||||
"unknown": "Hollow Bastion Defeat Unknown Ansem's Report 13",
|
||||
"final_rest": "End of the World Final Rest Chest"
|
||||
}
|
||||
goal_location_name = goal_dict[self.options.final_rest_door_key.current_key]
|
||||
elif self.options.final_rest_door_key.current_key == "postcards":
|
||||
lpad_number = str(self.options.required_postcards).rjust(2, "0")
|
||||
goal_location_name = "Traverse Town Mail Postcard " + lpad_number + " Event"
|
||||
elif self.options.final_rest_door_key.current_key == "puppies":
|
||||
required_puppies = self.options.required_puppies.value
|
||||
goal_location_name = "Traverse Town Piano Room Return " + str(required_puppies) + " Puppies"
|
||||
if required_puppies == 50 or required_puppies == 99:
|
||||
goal_location_name = goal_location_name + " Reward 2"
|
||||
if self.options.final_rest_door_key.current_key != "lucky_emblems":
|
||||
self.get_location(goal_location_name).place_locked_item(self.create_item("Final Door Key"))
|
||||
self.get_location("Final Ansem").place_locked_item(self.create_event("Victory"))
|
||||
|
||||
if not self.options.randomize_emblem_pieces:
|
||||
goal_dict = {
|
||||
"sephiroth": "Olympus Coliseum Defeat Sephiroth Ansem's Report 12",
|
||||
"unknown": "Hollow Bastion Defeat Unknown Ansem's Report 13",
|
||||
"postcards": "Traverse Town Mail Postcard 10 Event",
|
||||
"final_ansem": "Final Ansem",
|
||||
"puppies": "Traverse Town Piano Room Return 99 Puppies Reward 2",
|
||||
"final_rest": "End of the World Final Rest Chest"
|
||||
}
|
||||
self.get_location(goal_dict[self.options.goal.current_key]).place_locked_item(self.create_item("Victory"))
|
||||
if self.options.vanilla_emblem_pieces:
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Flame)").place_locked_item(self.create_item("Emblem Piece (Flame)"))
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Statue)").place_locked_item(self.create_item("Emblem Piece (Statue)"))
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Fountain)").place_locked_item(self.create_item("Emblem Piece (Fountain)"))
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Chest)").place_locked_item(self.create_item("Emblem Piece (Chest)"))
|
||||
if self.options.randomize_postcards != "all":
|
||||
self.get_location("Traverse Town Item Shop Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 1st District Safe Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Gizmo Shop Postcard 1").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Gizmo Shop Postcard 2").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Item Workshop Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 3rd District Balcony Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Geppetto's House Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
if self.options.randomize_postcards.current_key == "vanilla":
|
||||
self.get_location("Traverse Town 1st District Accessory Shop Roof Chest").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 2nd District Boots and Shoes Awning Chest").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 1st District Blue Trinity Balcony Chest").place_locked_item(self.create_item("Postcard"))
|
||||
if not self.options.randomize_puppies:
|
||||
if self.options.puppy_value.value != 3:
|
||||
self.options.puppy_value.value = 3
|
||||
logging.info(f"{self.player_name}'s value of {self.options.puppy_value.value} for puppy value was changed to 3 as Randomize Puppies is OFF")
|
||||
for i, location in enumerate(VANILLA_PUPPY_LOCATIONS):
|
||||
self.get_location(location).place_locked_item(self.create_item("Puppy"))
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
weights = [data.weight for data in self.fillers.values()]
|
||||
return self.random.choices([filler for filler in self.fillers.keys()], weights)[0]
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = {
|
||||
"atlantica": bool(self.options.atlantica),
|
||||
"auto_attack": bool(self.options.auto_attack),
|
||||
"auto_save": bool(self.options.auto_save),
|
||||
"bad_starting_weapons": bool(self.options.bad_starting_weapons),
|
||||
"beep_hack": bool(self.options.beep_hack),
|
||||
"consistent_finishers": bool(self.options.consistent_finishers),
|
||||
"cups": str(self.options.cups.current_key),
|
||||
"day_2_materials": int(self.options.day_2_materials.value),
|
||||
"death_link": str(self.options.death_link.current_key),
|
||||
"destiny_islands": bool(self.options.destiny_islands),
|
||||
"donald_death_link": bool(self.options.donald_death_link),
|
||||
"early_skip": bool(self.options.early_skip),
|
||||
"end_of_the_world_unlock": str(self.options.end_of_the_world_unlock.current_key),
|
||||
"exp_multiplier": int(self.options.exp_multiplier.value)/16,
|
||||
"exp_zero_in_pool": bool(self.options.exp_zero_in_pool),
|
||||
"extra_shared_abilities": bool(self.options.extra_shared_abilities),
|
||||
"fast_camera": bool(self.options.fast_camera),
|
||||
"faster_animations": bool(self.options.faster_animations),
|
||||
"final_rest_door_key": str(self.options.final_rest_door_key.current_key),
|
||||
"force_stats_on_levels": int(self.options.force_stats_on_levels.value),
|
||||
"four_by_three": bool(self.options.four_by_three),
|
||||
"goofy_death_link": bool(self.options.goofy_death_link),
|
||||
"halloween_town_key_item_bundle": bool(self.options.halloween_town_key_item_bundle),
|
||||
"homecoming_materials": int(self.options.homecoming_materials.value),
|
||||
"hundred_acre_wood": bool(self.options.hundred_acre_wood),
|
||||
"interact_in_battle": bool(self.options.interact_in_battle),
|
||||
"jungle_slider": bool(self.options.jungle_slider),
|
||||
"keyblades_unlock_chests": bool(self.options.keyblades_unlock_chests),
|
||||
"level_checks": int(self.options.level_checks.value),
|
||||
"logic_difficulty": str(self.options.logic_difficulty.current_key),
|
||||
"materials_in_pool": int(self.options.materials_in_pool.value),
|
||||
"max_ap_cost": int(self.options.max_ap_cost.value),
|
||||
"min_ap_cost": int(self.options.min_ap_cost.value),
|
||||
"mythril_in_pool": int(self.options.mythril_in_pool.value),
|
||||
"mythril_price": int(self.options.mythril_price.value),
|
||||
"one_hp": bool(self.options.one_hp),
|
||||
"orichalcum_in_pool": int(self.options.orichalcum_in_pool.value),
|
||||
"orichalcum_price": int(self.options.orichalcum_price.value),
|
||||
"puppy_value": int(self.options.puppy_value.value),
|
||||
"randomize_ap_costs": str(self.options.randomize_ap_costs.current_key),
|
||||
"randomize_emblem_pieces": bool(self.options.exp_zero_in_pool),
|
||||
"randomize_party_member_starting_accessories": bool(self.options.randomize_party_member_starting_accessories),
|
||||
"randomize_postcards": str(self.options.randomize_postcards.current_key),
|
||||
"randomize_puppies": str(self.options.randomize_puppies.current_key),
|
||||
"remote_items": str(self.options.remote_items.current_key),
|
||||
"remote_location_ids": self.get_remote_location_ids(),
|
||||
"required_lucky_emblems_door": self.determine_lucky_emblems_required_to_open_final_rest_door(),
|
||||
"required_lucky_emblems_eotw": self.determine_lucky_emblems_required_to_open_end_of_the_world(),
|
||||
"required_postcards": int(self.options.required_postcards.value),
|
||||
"required_puppies": int(self.options.required_puppies.value),
|
||||
slot_data = {"xpmult": int(self.options.exp_multiplier)/16,
|
||||
"required_reports_eotw": self.determine_reports_required_to_open_end_of_the_world(),
|
||||
"required_reports_door": self.determine_reports_required_to_open_final_rest_door(),
|
||||
"door": self.options.final_rest_door.current_key,
|
||||
"seed": self.multiworld.seed_name,
|
||||
"shorten_go_mode": bool(self.options.shorten_go_mode),
|
||||
"slot_2_level_checks": int(self.options.slot_2_level_checks.value),
|
||||
"stacking_world_items": bool(self.options.stacking_world_items),
|
||||
"starting_items": [item.code for item in self.multiworld.precollected_items[self.player]],
|
||||
"starting_tools": bool(self.options.starting_tools),
|
||||
"super_bosses": bool(self.options.super_bosses),
|
||||
"synthesis_item_name_byte_arrays": self.get_synthesis_item_name_byte_arrays(),
|
||||
"unlock_0_volume": bool(self.options.unlock_0_volume),
|
||||
"unskippable": bool(self.options.unskippable),
|
||||
"warp_anywhere": bool(self.options.warp_anywhere)
|
||||
}
|
||||
"advanced_logic": bool(self.options.advanced_logic),
|
||||
"hundred_acre_wood": bool(self.options.hundred_acre_wood),
|
||||
"atlantica": bool(self.options.atlantica),
|
||||
"goal": str(self.options.goal.current_key)}
|
||||
if self.options.randomize_keyblade_stats:
|
||||
min_str_bonus = min(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
max_str_bonus = max(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
self.options.keyblade_min_str.value = min_str_bonus
|
||||
self.options.keyblade_max_str.value = max_str_bonus
|
||||
min_mp_bonus = min(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
max_mp_bonus = max(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
self.options.keyblade_min_mp.value = min_mp_bonus
|
||||
self.options.keyblade_max_mp.value = max_mp_bonus
|
||||
slot_data["keyblade_stats"] = ""
|
||||
for i in range(22):
|
||||
if i < 4 and self.options.bad_starting_weapons:
|
||||
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + "1,0,"
|
||||
else:
|
||||
str_bonus = int(self.random.randint(min_str_bonus, max_str_bonus))
|
||||
mp_bonus = int(self.random.randint(min_mp_bonus, max_mp_bonus))
|
||||
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + str(str_bonus) + "," + str(mp_bonus) + ","
|
||||
slot_data["keyblade_stats"] = slot_data["keyblade_stats"][:-1]
|
||||
if self.options.donald_death_link:
|
||||
slot_data["donalddl"] = ""
|
||||
if self.options.goofy_death_link:
|
||||
slot_data["goofydl"] = ""
|
||||
if self.options.keyblades_unlock_chests:
|
||||
slot_data["chestslocked"] = ""
|
||||
else:
|
||||
slot_data["chestsunlocked"] = ""
|
||||
if self.options.interact_in_battle:
|
||||
slot_data["interactinbattle"] = ""
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> KH1Item:
|
||||
@@ -355,260 +241,45 @@ class KH1World(World):
|
||||
set_rules(self)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self)
|
||||
|
||||
create_regions(self.multiworld, self.player, self.options)
|
||||
|
||||
def connect_entrances(self):
|
||||
connect_entrances(self)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
"""
|
||||
Generates the json file for use with mod generator.
|
||||
"""
|
||||
generate_json(self, output_directory)
|
||||
connect_entrances(self.multiworld, self.player)
|
||||
|
||||
def generate_early(self):
|
||||
self.determine_level_checks()
|
||||
|
||||
value_names = ["Lucky Emblems to Open End of the World", "Lucky Emblems to Open Final Rest Door", "Lucky Emblems in Pool"]
|
||||
initial_lucky_emblem_settings = [self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value]
|
||||
self.change_numbers_of_lucky_emblems_to_consider()
|
||||
new_lucky_emblem_settings = [self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value]
|
||||
value_names = ["Reports to Open End of the World", "Reports to Open Final Rest Door", "Reports in Pool"]
|
||||
initial_report_settings = [self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value]
|
||||
self.change_numbers_of_reports_to_consider()
|
||||
new_report_settings = [self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value]
|
||||
for i in range(3):
|
||||
if initial_lucky_emblem_settings[i] != new_lucky_emblem_settings[i]:
|
||||
logging.info(f"{self.player_name}'s value {initial_lucky_emblem_settings[i]} for \"{value_names[i]}\" was invalid\n"
|
||||
f"Setting \"{value_names[i]}\" value to {new_lucky_emblem_settings[i]}")
|
||||
|
||||
value_names = ["Day 2 Materials", "Homecoming Materials", "Materials in Pool"]
|
||||
initial_materials_settings = [self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value]
|
||||
self.change_numbers_of_materials_to_consider()
|
||||
new_materials_settings = [self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value]
|
||||
for i in range(3):
|
||||
if initial_materials_settings[i] != new_materials_settings[i]:
|
||||
logging.info(f"{self.player_name}'s value {initial_materials_settings[i]} for \"{value_names[i]}\" was invalid\n"
|
||||
f"Setting \"{value_names[i]}\" value to {new_materials_settings[i]}")
|
||||
|
||||
if self.options.stacking_world_items.value and not self.options.halloween_town_key_item_bundle.value:
|
||||
logging.info(f"{self.player_name}'s value {self.options.halloween_town_key_item_bundle.value} for Halloween Town Key Item Bundle must be TRUE when Stacking World Items is on. Setting to TRUE")
|
||||
self.options.halloween_town_key_item_bundle.value = True
|
||||
if initial_report_settings[i] != new_report_settings[i]:
|
||||
logging.info(f"{self.player_name}'s value {initial_report_settings[i]} for \"{value_names[i]}\" was invalid\n"
|
||||
f"Setting \"{value_names[i]}\" value to {new_report_settings[i]}")
|
||||
|
||||
def change_numbers_of_lucky_emblems_to_consider(self) -> None:
|
||||
if self.options.end_of_the_world_unlock == "lucky_emblems" and self.options.final_rest_door_key == "lucky_emblems":
|
||||
self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value = sorted(
|
||||
[self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value])
|
||||
def change_numbers_of_reports_to_consider(self) -> None:
|
||||
if self.options.end_of_the_world_unlock == "reports" and self.options.final_rest_door == "reports":
|
||||
self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value = sorted(
|
||||
[self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value])
|
||||
|
||||
elif self.options.end_of_the_world_unlock == "lucky_emblems":
|
||||
self.options.required_lucky_emblems_eotw.value, self.options.lucky_emblems_in_pool.value = sorted(
|
||||
[self.options.required_lucky_emblems_eotw.value, self.options.lucky_emblems_in_pool.value])
|
||||
elif self.options.end_of_the_world_unlock == "reports":
|
||||
self.options.required_reports_eotw.value, self.options.reports_in_pool.value = sorted(
|
||||
[self.options.required_reports_eotw.value, self.options.reports_in_pool.value])
|
||||
|
||||
elif self.options.final_rest_door_key == "lucky_emblems":
|
||||
self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value = sorted(
|
||||
[self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value])
|
||||
elif self.options.final_rest_door == "reports":
|
||||
self.options.required_reports_door.value, self.options.reports_in_pool.value = sorted(
|
||||
[self.options.required_reports_door.value, self.options.reports_in_pool.value])
|
||||
|
||||
def determine_lucky_emblems_in_pool(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "lucky_emblems" or self.options.final_rest_door_key == "lucky_emblems":
|
||||
return self.options.lucky_emblems_in_pool.value
|
||||
def determine_reports_in_pool(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "reports" or self.options.final_rest_door == "reports":
|
||||
return self.options.reports_in_pool.value
|
||||
return 0
|
||||
|
||||
def determine_lucky_emblems_required_to_open_end_of_the_world(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "lucky_emblems":
|
||||
return self.options.required_lucky_emblems_eotw.value
|
||||
return -1
|
||||
def determine_reports_required_to_open_end_of_the_world(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "reports":
|
||||
return self.options.required_reports_eotw.value
|
||||
return 14
|
||||
|
||||
def determine_lucky_emblems_required_to_open_final_rest_door(self) -> int:
|
||||
if self.options.final_rest_door_key == "lucky_emblems":
|
||||
return self.options.required_lucky_emblems_door.value
|
||||
return -1
|
||||
|
||||
def change_numbers_of_materials_to_consider(self) -> None:
|
||||
if self.options.destiny_islands:
|
||||
self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value = sorted(
|
||||
[self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value])
|
||||
|
||||
def get_remote_location_ids(self):
|
||||
remote_location_ids = []
|
||||
for location in self.multiworld.get_filled_locations(self.player):
|
||||
if location.name != "Final Ansem":
|
||||
location_data = location_table[location.name]
|
||||
if self.options.remote_items.current_key == "full":
|
||||
if location_data.type != "Starting Accessory":
|
||||
remote_location_ids.append(location_data.code)
|
||||
elif self.player == location.item.player and location.item.name != "Victory":
|
||||
item_data = item_table[location.item.name]
|
||||
if location_data.type == "Chest":
|
||||
if item_data.type in ["Stats"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Reward":
|
||||
if item_data.type in ["Stats"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Static":
|
||||
if item_data.type not in ["Item"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Level Slot 1":
|
||||
if item_data.category not in ["Level Up", "Limited Level Up"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Level Slot 2":
|
||||
if item_data.category not in ["Level Up", "Limited Level Up", "Abilities"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Synth":
|
||||
if item_data.type not in ["Item"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Prize":
|
||||
if item_data.type not in ["Item"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
return remote_location_ids
|
||||
|
||||
def get_slot_2_levels(self):
|
||||
if self.slot_2_levels is None:
|
||||
self.slot_2_levels = []
|
||||
if self.options.max_level_for_slot_2_level_checks - 1 > self.options.level_checks.value:
|
||||
logging.info(f"{self.player_name}'s value of {self.options.max_level_for_slot_2_level_checks.value} for max level for slot 2 level checks is invalid as it exceeds their value of {self.options.level_checks.value} for Level Checks\n"
|
||||
f"Setting max level for slot 2 level checks's value to {self.options.level_checks.value + 1}")
|
||||
self.options.max_level_for_slot_2_level_checks.value = self.options.level_checks.value + 1
|
||||
if self.options.slot_2_level_checks.value > self.options.level_checks.value:
|
||||
logging.info(f"{self.player_name}'s value of {self.options.slot_2_level_checks.value} for slot 2 level checks is invalid as it exceeds their value of {self.options.level_checks.value} for Level Checks\n"
|
||||
f"Setting slot 2 level check's value to {self.options.level_checks.value}")
|
||||
self.options.slot_2_level_checks.value = self.options.level_checks.value
|
||||
if self.options.slot_2_level_checks > self.options.max_level_for_slot_2_level_checks - 1:
|
||||
logging.info(f"{self.player_name}'s value of {self.options.slot_2_level_checks.value} for slot 2 level checks is invalid as it exceeds their value of {self.options.max_level_for_slot_2_level_checks.value} for Max Level for Slot 2 Level Checks\n"
|
||||
f"Setting slot 2 level check's value to {self.options.max_level_for_slot_2_level_checks.value - 1}")
|
||||
self.options.slot_2_level_checks.value = self.options.max_level_for_slot_2_level_checks.value - 1
|
||||
# Range is exclusive of the top, so if max_level_for_slot_2_level_checks is 2 then the top end of the range needs to be 3 as the only level it can choose is 2.
|
||||
self.slot_2_levels = self.random.sample(range(2,self.options.max_level_for_slot_2_level_checks.value + 1), self.options.slot_2_level_checks.value)
|
||||
return self.slot_2_levels
|
||||
|
||||
def get_keyblade_stats(self):
|
||||
# Create keyblade stat array from vanilla
|
||||
keyblade_stats = [x.copy() for x in VANILLA_KEYBLADE_STATS]
|
||||
# Handle shuffling keyblade stats
|
||||
if self.options.keyblade_stats != "vanilla":
|
||||
if self.options.keyblade_stats == "randomize":
|
||||
# Fix any minimum and max values from settings
|
||||
min_str_bonus = min(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
max_str_bonus = max(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
self.options.keyblade_min_str.value = min_str_bonus
|
||||
self.options.keyblade_max_str.value = max_str_bonus
|
||||
min_crit_rate = min(self.options.keyblade_min_crit_rate.value, self.options.keyblade_max_crit_rate.value)
|
||||
max_crit_rate = max(self.options.keyblade_min_crit_rate.value, self.options.keyblade_max_crit_rate.value)
|
||||
self.options.keyblade_min_crit_rate.value = min_crit_rate
|
||||
self.options.keyblade_max_crit_rate.value = max_crit_rate
|
||||
min_crit_str = min(self.options.keyblade_min_crit_str.value, self.options.keyblade_max_crit_str.value)
|
||||
max_crit_str = max(self.options.keyblade_min_crit_str.value, self.options.keyblade_max_crit_str.value)
|
||||
self.options.keyblade_min_crit_str.value = min_crit_str
|
||||
self.options.keyblade_max_crit_str.value = max_crit_str
|
||||
min_recoil = min(self.options.keyblade_min_recoil.value, self.options.keyblade_max_recoil.value)
|
||||
max_recoil = max(self.options.keyblade_min_recoil.value, self.options.keyblade_max_recoil.value)
|
||||
self.options.keyblade_min_recoil.value = min_recoil
|
||||
self.options.keyblade_max_recoil.value = max_recoil
|
||||
min_mp_bonus = min(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
max_mp_bonus = max(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
self.options.keyblade_min_mp.value = min_mp_bonus
|
||||
self.options.keyblade_max_mp.value = max_mp_bonus
|
||||
if self.options.bad_starting_weapons:
|
||||
starting_weapons = keyblade_stats[:4]
|
||||
other_weapons = keyblade_stats[4:]
|
||||
else:
|
||||
starting_weapons = []
|
||||
other_weapons = keyblade_stats
|
||||
for keyblade in other_weapons:
|
||||
keyblade["STR"] = self.random.randint(min_str_bonus, max_str_bonus)
|
||||
keyblade["CRR"] = self.random.randint(min_crit_rate, max_crit_rate)
|
||||
keyblade["CRB"] = self.random.randint(min_crit_str, max_crit_str)
|
||||
keyblade["REC"] = self.random.randint(min_recoil, max_recoil)
|
||||
keyblade["MP"] = self.random.randint(min_mp_bonus, max_mp_bonus)
|
||||
keyblade_stats = starting_weapons + other_weapons
|
||||
elif self.options.keyblade_stats == "shuffle":
|
||||
if self.options.bad_starting_weapons:
|
||||
starting_weapons = keyblade_stats[:4]
|
||||
other_weapons = keyblade_stats[4:]
|
||||
self.random.shuffle(other_weapons)
|
||||
keyblade_stats = starting_weapons + other_weapons
|
||||
else:
|
||||
self.random.shuffle(keyblade_stats)
|
||||
return keyblade_stats
|
||||
|
||||
def determine_level_checks(self):
|
||||
# Handle if remote_items is off and level_checks > number of stats items
|
||||
total_level_up_items = min(99,
|
||||
self.options.strength_increase.value +\
|
||||
self.options.defense_increase.value +\
|
||||
self.options.hp_increase.value +\
|
||||
self.options.mp_increase.value +\
|
||||
self.options.ap_increase.value +\
|
||||
self.options.accessory_slot_increase.value +\
|
||||
self.options.item_slot_increase.value)
|
||||
if self.options.level_checks.value > total_level_up_items and self.options.remote_items.current_key == "off":
|
||||
logging.info(f"{self.player_name}'s value {self.options.level_checks.value} for level_checks was changed.\n"
|
||||
f"This value cannot be more than the number of stat items in the pool when \"remote_items\" is \"off\".\n"
|
||||
f"Set to be equal to number of stat items in pool, {total_level_up_items}.")
|
||||
self.options.level_checks.value = total_level_up_items
|
||||
|
||||
def get_synthesis_item_name_byte_arrays(self):
|
||||
# Get synth item names to show in synthesis menu
|
||||
synthesis_byte_arrays = []
|
||||
for location in self.multiworld.get_filled_locations(self.player):
|
||||
if location.name != "Final Ansem":
|
||||
location_data = location_table[location.name]
|
||||
if location_data.type == "Synth":
|
||||
item_name = re.sub('[^A-Za-z0-9 ]+', '',str(location.item.name.replace("Progressive", "Prog")))[:14]
|
||||
byte_array = []
|
||||
for character in item_name:
|
||||
byte_array.append(CHAR_TO_KH[character])
|
||||
synthesis_byte_arrays.append(byte_array)
|
||||
return synthesis_byte_arrays
|
||||
|
||||
def get_starting_accessory_locations(self):
|
||||
if self.starting_accessory_locations is None:
|
||||
if self.options.randomize_party_member_starting_accessories:
|
||||
self.starting_accessory_locations = list(get_locations_by_type("Starting Accessory").keys())
|
||||
if not self.options.atlantica:
|
||||
self.starting_accessory_locations.remove("Ariel Starting Accessory 1")
|
||||
self.starting_accessory_locations.remove("Ariel Starting Accessory 2")
|
||||
self.starting_accessory_locations.remove("Ariel Starting Accessory 3")
|
||||
self.starting_accessory_locations = self.random.sample(self.starting_accessory_locations, 10)
|
||||
else:
|
||||
self.starting_accessory_locations = []
|
||||
return self.starting_accessory_locations
|
||||
|
||||
def get_starting_accessories(self):
|
||||
if self.starting_accessories is None:
|
||||
if self.options.randomize_party_member_starting_accessories:
|
||||
self.starting_accessories = list(get_items_by_category("Accessory").keys())
|
||||
self.starting_accessories = self.random.sample(self.starting_accessories, 10)
|
||||
else:
|
||||
self.starting_accessories = []
|
||||
return self.starting_accessories
|
||||
|
||||
def get_ap_costs(self):
|
||||
if self.ap_costs is None:
|
||||
ap_costs = VANILLA_ABILITY_AP_COSTS.copy()
|
||||
if self.options.randomize_ap_costs.current_key == "shuffle":
|
||||
possible_costs = []
|
||||
for ap_cost in VANILLA_ABILITY_AP_COSTS:
|
||||
if ap_cost["Randomize"]:
|
||||
possible_costs.append(ap_cost["AP Cost"])
|
||||
self.random.shuffle(possible_costs)
|
||||
for ap_cost in ap_costs:
|
||||
if ap_cost["Randomize"]:
|
||||
ap_cost["AP Cost"] = possible_costs.pop(0)
|
||||
elif self.options.randomize_ap_costs.current_key == "randomize":
|
||||
for ap_cost in ap_costs:
|
||||
if ap_cost["Randomize"]:
|
||||
ap_cost["AP Cost"] = self.random.randint(self.options.min_ap_cost.value, self.options.max_ap_cost.value)
|
||||
elif self.options.randomize_ap_costs.current_key == "distribute":
|
||||
total_ap_value = 0
|
||||
for ap_cost in VANILLA_ABILITY_AP_COSTS:
|
||||
if ap_cost["Randomize"]:
|
||||
total_ap_value = total_ap_value + ap_cost["AP Cost"]
|
||||
for ap_cost in ap_costs:
|
||||
if ap_cost["Randomize"]:
|
||||
total_ap_value = total_ap_value - self.options.min_ap_cost.value
|
||||
ap_cost["AP Cost"] = self.options.min_ap_cost.value
|
||||
while total_ap_value > 0:
|
||||
ap_cost = self.random.choice(ap_costs)
|
||||
if ap_cost["Randomize"]:
|
||||
if ap_cost["AP Cost"] < self.options.max_ap_cost.value:
|
||||
amount_to_add = self.random.randint(1, min(self.options.max_ap_cost.value - ap_cost["AP Cost"], total_ap_value))
|
||||
ap_cost["AP Cost"] = ap_cost["AP Cost"] + amount_to_add
|
||||
total_ap_value = total_ap_value - amount_to_add
|
||||
self.ap_costs = ap_costs
|
||||
return self.ap_costs
|
||||
def determine_reports_required_to_open_final_rest_door(self) -> int:
|
||||
if self.options.final_rest_door == "reports":
|
||||
return self.options.required_reports_door.value
|
||||
return 14
|
||||
|
||||
@@ -7,7 +7,7 @@ configure and export a config file.
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
The Kingdom Hearts AP Randomizer randomizes rewards in the game and adds several items which are used to unlock worlds, Olympus Coliseum cups, and world progression.
|
||||
The Kingdom Hearts AP Randomizer randomizes most rewards in the game and adds several items which are used to unlock worlds, Olympus Coliseum cups, and world progression.
|
||||
|
||||
Worlds can only be accessed by finding the corresponding item. For example, you need to find the `Monstro` item to enter Monstro.
|
||||
|
||||
@@ -21,26 +21,49 @@ Any weapon, accessory, spell, trinity, summon, world, key item, stat up, consuma
|
||||
|
||||
### Locations
|
||||
|
||||
Locations the player can find items include:
|
||||
- Chests
|
||||
- Rewards
|
||||
- Static Events
|
||||
- Map Prizes from things such as Trinities, Wonderland flowers and chairs, etc.
|
||||
- Level ups
|
||||
Locations the player can find items include chests, event rewards, Atlantica clams, level up rewards, 101 Dalmatian rewards, and postcard rewards.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
|
||||
Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
|
||||
certain items to your own world.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
When the player receives an item, your client will display a message displaying the item you have obtained. You will also see a notification in the "INFORMATION" box.
|
||||
When the player receives an item, your client will display a message displaying the item you have obtained. You will also see a notification in the "LEVEL UP" box.
|
||||
|
||||
## What do I do if I encounter a bug with the game?
|
||||
|
||||
Please reach out to Gicu#7034 on Discord.
|
||||
|
||||
## How do I progress in a certain world?
|
||||
|
||||
### The evidence boxes aren't spawning in Wonderland.
|
||||
|
||||
Find `Footprints` in the multiworld.
|
||||
|
||||
### I can't enter any cups in Olympus Coliseum.
|
||||
|
||||
Firstly, find `Entry Pass` in the multiworld. Additionally, `Phil Cup`, `Pegasus Cup`, and `Hercules Cup` are all multiworld items. Finding all 3 grant you access to the Hades Cup and the Platinum Match. Clearing all cups lets you challenge Ice Titan.
|
||||
|
||||
### The slides aren't spawning in Deep Jungle.
|
||||
|
||||
Find `Slides` in the multiworld.
|
||||
|
||||
### I can't progress in Atlantica.
|
||||
Find `Crystal Trident` in the multiworld.
|
||||
|
||||
### I can't progress in Halloween Town.
|
||||
|
||||
Find `Forget-Me-Not` and `Jack-in-the-Box` in the multiworld.
|
||||
|
||||
### The Hollow Bastion Library is missing a book.
|
||||
|
||||
Find `Theon Vol. 6` in the multiworld.
|
||||
|
||||
## How do I enter the End of the World?
|
||||
|
||||
You can enter End of the World by obtaining a number of Ansem's Reports or by finding `End of the World` in the multiworld, depending on your options.
|
||||
|
||||
## Credits
|
||||
This is a collaborative effort from several individuals in the Kingdom Hearts community, but most of all, denhonator.
|
||||
|
||||
|
||||
+36
-81
@@ -1,99 +1,54 @@
|
||||
# Kingdom Hearts Archipelago Randomizer Setup Guide
|
||||
# Kingdom Hearts Randomizer Setup Guide
|
||||
|
||||
<h2 style="text-transform:none";>Required software</h2>
|
||||
## Setting up the required mods
|
||||
|
||||
- KINGDOM HEARTS -HD 1.5+2.5 ReMIX- from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) or [Steam](https://store.steampowered.com/app/2552430/KINGDOM_HEARTS_HD_1525_ReMIX/)
|
||||
BEFORE MODDING, PLEASE INSTALL AND RUN KH1 AT LEAST ONCE.
|
||||
|
||||
- The latest release of [OpenKH](https://github.com/OpenKH/OpenKh/releases)
|
||||
1. Install OpenKH and the LUA Backend
|
||||
|
||||
- The latest release of the [Kingdom Hearts 1FM Randomizer Software](https://github.com/gaithern/KH1FM-RANDOMIZER/releases)
|
||||
Download the [latest release of OpenKH](https://github.com/OpenKH/OpenKh/releases/tag/latest)
|
||||
|
||||
Extract the files to a directory of your choosing.
|
||||
|
||||
Open `OpenKh.Tools.ModsManager.exe` and run first time set up
|
||||
|
||||
When prompted for game edition, choose `PC Release`, select which platform you're using (EGS or Steam), navigate to your `Kingdom Hearts I.5 + II.5` installation folder in the path box and click `Next`
|
||||
|
||||
When prompted, install Panacea, then click `Next`
|
||||
|
||||
When prompted, check KH1 plus any other AP game you play and click `Install and configure LUA backend`, then click `Next`
|
||||
|
||||
Extracting game data for KH1 is unnecessary, but you may want to extract data for KH2 if you plan on playing KH2 AP
|
||||
|
||||
Click `Finish`
|
||||
|
||||
2. Open `OpenKh.Tools.ModsManager.exe`
|
||||
|
||||
- The latest release of [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) for the ArchipelagoKH1Client.exe
|
||||
3. Click the drop-down menu at the top-right and choose `Kingdom Hearts 1`
|
||||
|
||||
<h2 style="text-transform:none";>Setting up the required software</h2>
|
||||
4. Click `Mods>Install a New Mod`
|
||||
|
||||
<h3 style="text-transform:none";>OpenKH</h3>
|
||||
5. In `Add a new mod from GitHub` paste `gaithern/KH-1FM-AP-LUA`
|
||||
|
||||
- Extract the OpenKH files to a directory of your choosing.
|
||||
- When prompted for game edition, choose PC Release, select which platform you're using (EGS or Steam), navigate to your `Kingdom Hearts I.5 + II.5` installation folder in the path box and click `Next`.
|
||||
- When prompted, install Panacea, then click `Next`.
|
||||
- When prompted, check KH1 plus any other AP game you want to play, and click `Install and configure Lua backend`, then click `Next`.
|
||||
- Extract the data for KH1.
|
||||
- Click `Finish`
|
||||
6. Click `Install`
|
||||
|
||||
<h3 style="text-transform:none";>Kingdom Hearts 1FM Randomizer Software</h3>
|
||||
7. Navigate to Mod Loader and click `Build and Run`
|
||||
|
||||
- Extract the Kingdom Hearts 1FM Randomizer Software files in a directory of your choosing.
|
||||
|
||||
<h2 style="text-transform:none";>Obtaining and using the seed zip</h2>
|
||||
## Configuring your YAML file
|
||||
|
||||
- When you generate a game you will see a download link for a KH1 .zip seed on the room page.
|
||||
- After downloading this zip, open `mod_generator.exe` in your Kingdom Hearts 1FM Randomizer Software folder.
|
||||
- Direct `mod_generator.exe` to both your seed zip and your KH1 data folder extracted during your OpenKH set up.
|
||||
- Click `start`.
|
||||
- After some time, you will find a file in your `Output` folder called `mod_YYYYMMDDHHMMSS.zip`
|
||||
- Open `OpenKh.Tools.ModsManager.exe` and ensure that the dropdown in the top right is set to `Kingdom Hearts 1`
|
||||
- Click the green plus, choose `Select and install Mod Archive or Lua Script`, and direct the prompt to your new mod zip.
|
||||
- You should now see a mod on your list called `KH1 Randomizer Seed XYZ` where XYZ is your seed hex value.
|
||||
- Ensure this mod is checked, then, if you want to play right away, click `Mod Loader` at the top.
|
||||
- Click `Build and Run`. Your modded game should now open.
|
||||
### What is a YAML file and why do I need one?
|
||||
|
||||
<h2 style="text-transform:none";>Connecting to your multiworld via the KH1 Client</h2>
|
||||
Your YAML file contains a set of configuration options which provide the generator with information about how it should
|
||||
generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
|
||||
an experience customized for their taste, and different players in the same multiworld can all have different options.
|
||||
|
||||
- Once your game is being hosted, open `ArchipelagoLauncher.exe`.
|
||||
- Find `KH1 Client` and open it.
|
||||
- At the top, in the `Server:` bar, type in the host address and port.
|
||||
- Click the `Connect` button in the top right.
|
||||
- If connection to the server was successful, you'll be prompted to type in your slot named in the `Command:` bar at the bottom.
|
||||
- After typing your slot name, press enter.
|
||||
- If all is well, you are now connected.
|
||||
### Where do I get a YAML file?
|
||||
|
||||
<h2 style="text-transform:none";>FAQ</h2>
|
||||
you can customize your settings by visiting the [Kingdom Hearts Options Page](/games/Kingdom%20Hearts/player-options).
|
||||
|
||||
<h3 style="text-transform:none";>The client did not confirm connection to the game, is that normal?</h3>
|
||||
## Connect to the MultiWorld
|
||||
|
||||
Yes, the game and client communicate via a game communication path set up in your in your `%AppData%` folder, and therefore don't need to establish a socket connection.
|
||||
For first-time players, it is recommended to open your KH1 Client first before opening the game.
|
||||
|
||||
<h3 style="text-transform:none";>I am not sending or receiving items.</h3>
|
||||
|
||||
Check out this [troubleshooting guide](https://docs.google.com/document/d/1oAXxJWrNeqSL-tkB_01bLR0eT0urxz2FBo4URpq3VbM/edit?usp=sharing)
|
||||
|
||||
<h3 style="text-transform:none";>Why aren't the evidence boxes spawning in Wonderland?</h3>
|
||||
|
||||
You'll need to find `Footprints` in your multiworld.
|
||||
|
||||
<h3 style="text-transform:none";>Why won't Phil let me start the Prelims?</h3>
|
||||
|
||||
You'll need to find `Entry Pass` in the multiworld.
|
||||
|
||||
<h3 style="text-transform:none";>Why aren't the slides spawning in Deep Jungle?</h3>
|
||||
|
||||
You'll need to find `Slides` in the multiworld.
|
||||
|
||||
<h3 style="text-transform:none";>Why can't I make progress in Atlantica?</h3>
|
||||
|
||||
You'll need to find `Crystal Trident` in the multiworld.
|
||||
|
||||
<h3 style="text-transform:none";>Why won't the doctor let me progress in Halloween Town?</h3>
|
||||
|
||||
You'll need to find either `Forget-Me-Not` or `Jack-in-the-Box` in the multiworld.
|
||||
|
||||
<h3 style="text-transform:none";>Why is there a book missing in the Hollow Bastion library?</h3>
|
||||
|
||||
You'll need to find `Theon Vol. 6` in the multiworld.
|
||||
|
||||
<h3 style="text-transform:none";>How do I unlock End of the World?</h3>
|
||||
|
||||
Depending on your settings, your options are either finding a specified amount of `Lucky Emblems` or finding the item `End of the World`.
|
||||
|
||||
<h3 style="text-transform:none";>How do I enter Destiny Islands?</h3>
|
||||
|
||||
After obtaining the item `Destiny Islands`, you can land there as an additional option in Traverse Town.
|
||||
|
||||
<h3 style="text-transform:none";>How do I progress to Destiny Islands Day 2 and 3?</h3>
|
||||
|
||||
In order to access Day 2 and 3, you need to collect an amount of `Raft Materials` specified in your settings. When you start Day 3, you'll be immediately warped to Homecoming.
|
||||
|
||||
<h3 style="text-transform:none";>Why can't I use the summon I obtained?</h3>
|
||||
|
||||
You need at least one magic spell before you can use summons.
|
||||
On the title screen, open your KH1 Client and connect to your multiworld.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -5,29 +5,29 @@ class TestDefault(KH1TestBase):
|
||||
|
||||
class TestSephiroth(KH1TestBase):
|
||||
options = {
|
||||
"Final Rest Door Key": 0,
|
||||
"Goal": 0,
|
||||
}
|
||||
|
||||
class TestUnknown(KH1TestBase):
|
||||
options = {
|
||||
"Final Rest Door Key": 1,
|
||||
"Goal": 1,
|
||||
}
|
||||
|
||||
class TestPostcards(KH1TestBase):
|
||||
options = {
|
||||
"Final Rest Door Key": 2,
|
||||
"Goal": 2,
|
||||
}
|
||||
|
||||
class TestLuckyEmblems(KH1TestBase):
|
||||
class TestFinalAnsem(KH1TestBase):
|
||||
options = {
|
||||
"Final Rest Door Key": 3,
|
||||
"Goal": 3,
|
||||
}
|
||||
|
||||
class TestPuppies(KH1TestBase):
|
||||
options = {
|
||||
"Final Rest Door Key": 4,
|
||||
"Goal": 4,
|
||||
}
|
||||
class TestFinalRest(KH1TestBase):
|
||||
options = {
|
||||
"Final Rest Door Key": 5,
|
||||
"Goal": 5,
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ def launch_client():
|
||||
launch_component(launch, name="KH2Client")
|
||||
|
||||
|
||||
components.append(Component("KH2 Client", func=launch_client, component_type=Type.CLIENT))
|
||||
components.append(Component("KH2 Client", "KH2Client", func=launch_client, component_type=Type.CLIENT))
|
||||
|
||||
|
||||
class KingdomHearts2Web(WebWorld):
|
||||
|
||||
@@ -242,9 +242,9 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
# patches.health.setStartHealth(rom, 1)
|
||||
|
||||
patches.inventory.songSelectAfterOcarinaSelect(rom)
|
||||
if options["quickswap"] == Options.Quickswap.option_a:
|
||||
if options["quickswap"] == 'a':
|
||||
patches.core.quickswap(rom, 1)
|
||||
elif options["quickswap"] == Options.Quickswap.option_b:
|
||||
elif options["quickswap"] == 'b':
|
||||
patches.core.quickswap(rom, 0)
|
||||
|
||||
patches.core.addBootsControls(rom, options["boots_controls"])
|
||||
@@ -271,9 +271,9 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||
mw = None
|
||||
if spot.item_owner != spot.location_owner:
|
||||
mw = spot.item_owner
|
||||
if mw > 101:
|
||||
if mw > 100:
|
||||
# There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that
|
||||
mw = 101
|
||||
mw = 100
|
||||
spot.patch(rom, spot.item, multiworld=mw)
|
||||
patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"])
|
||||
patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"])
|
||||
|
||||
@@ -11,18 +11,19 @@ class BeachSword(DroppedKey):
|
||||
super().__init__(0x0F2)
|
||||
|
||||
def patch(self, rom: ROM, option: str, *, multiworld: Optional[int] = None) -> None:
|
||||
# Set the heart piece data
|
||||
super().patch(rom, option, multiworld=multiworld)
|
||||
if option != SWORD or multiworld is not None:
|
||||
# Set the heart piece data
|
||||
super().patch(rom, option, multiworld=multiworld)
|
||||
|
||||
# Patch the room to contain a heart piece instead of the sword on the beach
|
||||
re = RoomEditor(rom, 0x0F2)
|
||||
re.removeEntities(0x31) # remove sword
|
||||
re.addEntity(5, 5, 0x35) # add heart piece
|
||||
re.store(rom)
|
||||
# Patch the room to contain a heart piece instead of the sword on the beach
|
||||
re = RoomEditor(rom, 0x0F2)
|
||||
re.removeEntities(0x31) # remove sword
|
||||
re.addEntity(5, 5, 0x35) # add heart piece
|
||||
re.store(rom)
|
||||
|
||||
# Prevent shield drops from the like-like from turning into swords.
|
||||
rom.patch(0x03, 0x1B9C, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True)
|
||||
rom.patch(0x03, 0x244D, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True)
|
||||
# Prevent shield drops from the like-like from turning into swords.
|
||||
rom.patch(0x03, 0x1B9C, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True)
|
||||
rom.patch(0x03, 0x244D, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True)
|
||||
|
||||
def read(self, rom: ROM) -> str:
|
||||
re = RoomEditor(rom, 0x0F2)
|
||||
|
||||
+188
-232
@@ -23,24 +23,11 @@ class LADXROption:
|
||||
class Logic(Choice, LADXROption):
|
||||
"""
|
||||
Affects where items are allowed to be placed.
|
||||
|
||||
**Normal:** Playable without using any tricks or glitches. Can require
|
||||
knowledge from a vanilla playthrough, such as how to open Color Dungeon.
|
||||
|
||||
**Hard:** More advanced techniques may be required, but glitches are not.
|
||||
Examples include tricky jumps, killing enemies with only pots.
|
||||
|
||||
**Glitched:** Advanced glitches and techniques may be required, but
|
||||
extremely difficult or tedious tricks are not required. Examples include
|
||||
Bomb Triggers, Super Jumps and Jesus Jumps.
|
||||
|
||||
**Hell:** Obscure knowledge and hard techniques may be required. Examples
|
||||
include featherless jumping with boots and/or hookshot, sequential pit
|
||||
buffers and unclipped superjumps. Things in here can be extremely hard to do
|
||||
or very time consuming.
|
||||
"""
|
||||
[Normal] Playable without using any tricks or glitches. Can require knowledge from a vanilla playthrough, such as how to open Color Dungeon.
|
||||
[Hard] More advanced techniques may be required, but glitches are not. Examples include tricky jumps, killing enemies with only pots.
|
||||
[Glitched] Advanced glitches and techniques may be required, but extremely difficult or tedious tricks are not required. Examples include Bomb Triggers, Super Jumps and Jesus Jumps.
|
||||
[Hell] Obscure knowledge and hard techniques may be required. Examples include featherless jumping with boots and/or hookshot, sequential pit buffers and unclipped superjumps. Things in here can be extremely hard to do or very time consuming."""
|
||||
display_name = "Logic"
|
||||
rich_text_doc = True
|
||||
ladxr_name = "logic"
|
||||
# option_casual = 0
|
||||
option_normal = 1
|
||||
@@ -53,8 +40,8 @@ class Logic(Choice, LADXROption):
|
||||
|
||||
class TradeQuest(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
Trade quest items are randomized. Each NPC takes its normal trade quest
|
||||
item and gives a randomized item in return.
|
||||
[On] adds the trade items to the pool (the trade locations will always be local items)
|
||||
[Off] (default) doesn't add them
|
||||
"""
|
||||
display_name = "Trade Quest"
|
||||
ladxr_name = "tradequest"
|
||||
@@ -62,32 +49,40 @@ class TradeQuest(DefaultOffToggle, LADXROption):
|
||||
|
||||
class TextShuffle(DefaultOffToggle):
|
||||
"""
|
||||
Shuffles all text in the game.
|
||||
[On] Shuffles all the text in the game
|
||||
[Off] (default) doesn't shuffle them.
|
||||
"""
|
||||
display_name = "Text Shuffle"
|
||||
|
||||
|
||||
class Rooster(DefaultOnToggle, LADXROption):
|
||||
"""
|
||||
Adds the rooster to the item pool. If disabled, the overworld will be
|
||||
modified so that any location requiring the rooster is accessible by other
|
||||
means.
|
||||
[On] Adds the rooster to the item pool.
|
||||
[Off] The rooster spot is still a check giving an item. But you will never find the rooster. In that case, any rooster spot is accessible without rooster by other means.
|
||||
"""
|
||||
display_name = "Rooster"
|
||||
ladxr_name = "rooster"
|
||||
|
||||
|
||||
class Boomerang(Choice):
|
||||
"""
|
||||
[Normal] requires Magnifying Lens to get the boomerang.
|
||||
[Gift] The boomerang salesman will give you a random item, and the boomerang is shuffled.
|
||||
"""
|
||||
display_name = "Boomerang"
|
||||
|
||||
normal = 0
|
||||
gift = 1
|
||||
default = gift
|
||||
|
||||
|
||||
class EntranceShuffle(Choice, LADXROption):
|
||||
"""
|
||||
Randomizes where overworld entrances lead.
|
||||
|
||||
**Simple:** Single-entrance caves/houses that have items are shuffled
|
||||
amongst each other.
|
||||
|
||||
If *Dungeon Shuffle* is enabled, then dungeons will be shuffled with all the
|
||||
non-connector entrances in the pool. Note, some entrances can lead into water, use
|
||||
the warp-to-home from the save&quit menu to escape this.
|
||||
"""
|
||||
[WARNING] Experimental, may fail to fill
|
||||
Randomizes where overworld entrances lead to.
|
||||
[Simple] Single-entrance caves/houses that have items are shuffled amongst each other.
|
||||
If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool.
|
||||
Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this."""
|
||||
|
||||
# [Advanced] Simple, but two-way connector caves are shuffled in their own pool as well.
|
||||
# [Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool.
|
||||
@@ -99,22 +94,22 @@ class EntranceShuffle(Choice, LADXROption):
|
||||
# option_expert = 3
|
||||
# option_insanity = 4
|
||||
default = option_none
|
||||
display_name = "Entrance Shuffle"
|
||||
display_name = "Experimental Entrance Shuffle"
|
||||
ladxr_name = "entranceshuffle"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class DungeonShuffle(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
Randomizes dungeon entrances with each other.
|
||||
[WARNING] Experimental, may fail to fill
|
||||
Randomizes dungeon entrances within eachother
|
||||
"""
|
||||
display_name = "Dungeon Shuffle"
|
||||
display_name = "Experimental Dungeon Shuffle"
|
||||
ladxr_name = "dungeonshuffle"
|
||||
|
||||
|
||||
class APTitleScreen(DefaultOnToggle):
|
||||
"""
|
||||
Enables AP specific title screen and disables the intro cutscene.
|
||||
Enables AP specific title screen and disables the intro cutscene
|
||||
"""
|
||||
display_name = "AP Title Screen"
|
||||
|
||||
@@ -129,7 +124,6 @@ class BossShuffle(Choice):
|
||||
|
||||
class DungeonItemShuffle(Choice):
|
||||
display_name = "Dungeon Item Shuffle"
|
||||
rich_text_doc = True
|
||||
option_original_dungeon = 0
|
||||
option_own_dungeons = 1
|
||||
option_own_world = 2
|
||||
@@ -144,15 +138,12 @@ class DungeonItemShuffle(Choice):
|
||||
|
||||
class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
**Own Dungeons:** The item will be within a dungeon in your world.
|
||||
|
||||
**Own World:** The item will be somewhere in your world.
|
||||
|
||||
**Any World:** The item could be anywhere.
|
||||
|
||||
**Different World:** The item will be somewhere in another world.
|
||||
Shuffle Nightmare Keys
|
||||
[Original Dungeon] The item will be within its original dungeon
|
||||
[Own Dungeons] The item will be within a dungeon in your world
|
||||
[Own World] The item will be somewhere in your world
|
||||
[Any World] The item could be anywhere
|
||||
[Different World] The item will be somewhere in another world
|
||||
"""
|
||||
display_name = "Shuffle Nightmare Keys"
|
||||
ladxr_item = "NIGHTMARE_KEY"
|
||||
@@ -160,15 +151,12 @@ class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||
|
||||
class ShuffleSmallKeys(DungeonItemShuffle):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
**Own Dungeons:** The item will be within a dungeon in your world.
|
||||
|
||||
**Own World:** The item will be somewhere in your world.
|
||||
|
||||
**Any World:** The item could be anywhere.
|
||||
|
||||
**Different World:** The item will be somewhere in another world.
|
||||
Shuffle Small Keys
|
||||
[Original Dungeon] The item will be within its original dungeon
|
||||
[Own Dungeons] The item will be within a dungeon in your world
|
||||
[Own World] The item will be somewhere in your world
|
||||
[Any World] The item could be anywhere
|
||||
[Different World] The item will be somewhere in another world
|
||||
"""
|
||||
display_name = "Shuffle Small Keys"
|
||||
ladxr_item = "KEY"
|
||||
@@ -176,15 +164,12 @@ class ShuffleSmallKeys(DungeonItemShuffle):
|
||||
|
||||
class ShuffleMaps(DungeonItemShuffle):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
**Own Dungeons:** The item will be within a dungeon in your world.
|
||||
|
||||
**Own World:** The item will be somewhere in your world.
|
||||
|
||||
**Any World:** The item could be anywhere.
|
||||
|
||||
**Different World:** The item will be somewhere in another world.
|
||||
Shuffle Dungeon Maps
|
||||
[Original Dungeon] The item will be within its original dungeon
|
||||
[Own Dungeons] The item will be within a dungeon in your world
|
||||
[Own World] The item will be somewhere in your world
|
||||
[Any World] The item could be anywhere
|
||||
[Different World] The item will be somewhere in another world
|
||||
"""
|
||||
display_name = "Shuffle Maps"
|
||||
ladxr_item = "MAP"
|
||||
@@ -192,15 +177,12 @@ class ShuffleMaps(DungeonItemShuffle):
|
||||
|
||||
class ShuffleCompasses(DungeonItemShuffle):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
**Own Dungeons:** The item will be within a dungeon in your world.
|
||||
|
||||
**Own World:** The item will be somewhere in your world.
|
||||
|
||||
**Any World:** The item could be anywhere.
|
||||
|
||||
**Different World:** The item will be somewhere in another world.
|
||||
Shuffle Dungeon Compasses
|
||||
[Original Dungeon] The item will be within its original dungeon
|
||||
[Own Dungeons] The item will be within a dungeon in your world
|
||||
[Own World] The item will be somewhere in your world
|
||||
[Any World] The item could be anywhere
|
||||
[Different World] The item will be somewhere in another world
|
||||
"""
|
||||
display_name = "Shuffle Compasses"
|
||||
ladxr_item = "COMPASS"
|
||||
@@ -208,15 +190,12 @@ class ShuffleCompasses(DungeonItemShuffle):
|
||||
|
||||
class ShuffleStoneBeaks(DungeonItemShuffle):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
**Own Dungeons:** The item will be within a dungeon in your world.
|
||||
|
||||
**Own World:** The item will be somewhere in your world.
|
||||
|
||||
**Any World:** The item could be anywhere.
|
||||
|
||||
**Different World:** The item will be somewhere in another world.
|
||||
Shuffle Owl Beaks
|
||||
[Original Dungeon] The item will be within its original dungeon
|
||||
[Own Dungeons] The item will be within a dungeon in your world
|
||||
[Own World] The item will be somewhere in your world
|
||||
[Any World] The item could be anywhere
|
||||
[Different World] The item will be somewhere in another world
|
||||
"""
|
||||
display_name = "Shuffle Stone Beaks"
|
||||
ladxr_item = "STONE_BEAK"
|
||||
@@ -224,17 +203,13 @@ class ShuffleStoneBeaks(DungeonItemShuffle):
|
||||
|
||||
class ShuffleInstruments(DungeonItemShuffle):
|
||||
"""
|
||||
**Original Dungeon:** The item will be within its original dungeon.
|
||||
|
||||
**Own Dungeons:** The item will be within a dungeon in your world.
|
||||
|
||||
**Own World:** The item will be somewhere in your world.
|
||||
|
||||
**Any World:** The item could be anywhere.
|
||||
|
||||
**Different World:** The item will be somewhere in another world.
|
||||
|
||||
**Vanilla:** The item will be in its vanilla location in your world.
|
||||
Shuffle Instruments
|
||||
[Original Dungeon] The item will be within its original dungeon
|
||||
[Own Dungeons] The item will be within a dungeon in your world
|
||||
[Own World] The item will be somewhere in your world
|
||||
[Any World] The item could be anywhere
|
||||
[Different World] The item will be somewhere in another world
|
||||
[Vanilla] The item will be in its vanilla location in your world
|
||||
"""
|
||||
display_name = "Shuffle Instruments"
|
||||
ladxr_item = "INSTRUMENT"
|
||||
@@ -245,18 +220,12 @@ class ShuffleInstruments(DungeonItemShuffle):
|
||||
|
||||
class Goal(Choice, LADXROption):
|
||||
"""
|
||||
The Goal of the game.
|
||||
|
||||
**Instruments:** The Wind Fish's Egg will only open if you have the required
|
||||
number of Instruments of the Sirens, and play the Ballad of the Wind Fish.
|
||||
|
||||
**Seashells:** The Egg will open when you bring 20 seashells. The Ballad and
|
||||
Ocarina are not needed.
|
||||
|
||||
**Open:** The Egg will start pre-opened.
|
||||
The Goal of the game
|
||||
[Instruments] The Wind Fish's Egg will only open if you have the required number of Instruments of the Sirens, and play the Ballad of the Wind Fish.
|
||||
[Seashells] The Egg will open when you bring 20 seashells. The Ballad and Ocarina are not needed.
|
||||
[Open] The Egg will start pre-opened.
|
||||
"""
|
||||
display_name = "Goal"
|
||||
rich_text_doc = True
|
||||
ladxr_name = "goal"
|
||||
option_instruments = 1
|
||||
option_seashells = 2
|
||||
@@ -273,7 +242,7 @@ class Goal(Choice, LADXROption):
|
||||
|
||||
class InstrumentCount(Range, LADXROption):
|
||||
"""
|
||||
Sets the number of instruments required to open the Egg.
|
||||
Sets the number of instruments required to open the Egg
|
||||
"""
|
||||
display_name = "Instrument Count"
|
||||
ladxr_name = None
|
||||
@@ -284,8 +253,7 @@ class InstrumentCount(Range, LADXROption):
|
||||
|
||||
class NagMessages(DefaultOffToggle, LADXROption):
|
||||
"""
|
||||
Controls if nag messages are shown when rocks and crystals are touched.
|
||||
Useful for glitches, annoying for everything else.
|
||||
Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else.
|
||||
"""
|
||||
display_name = "Nag Messages"
|
||||
ladxr_name = "nagmessages"
|
||||
@@ -294,30 +262,31 @@ class NagMessages(DefaultOffToggle, LADXROption):
|
||||
class MusicChangeCondition(Choice):
|
||||
"""
|
||||
Controls how the music changes.
|
||||
|
||||
**Sword:** When you pick up a sword, the music changes.
|
||||
|
||||
**Always:** You always have the post-sword music.
|
||||
[Sword] When you pick up a sword, the music changes
|
||||
[Always] You always have the post-sword music
|
||||
"""
|
||||
display_name = "Music Change Condition"
|
||||
rich_text_doc = True
|
||||
option_sword = 0
|
||||
option_always = 1
|
||||
default = option_always
|
||||
|
||||
|
||||
# Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default',
|
||||
# description="""
|
||||
# [Normal} health works as you would expect.
|
||||
# [Inverted] you start with 9 heart containers, but killing a boss will take a heartcontainer instead of giving one.
|
||||
# [Start with 1] normal game, you just start with 1 heart instead of 3.
|
||||
# [Low max] replace heart containers with heart pieces."""),
|
||||
|
||||
|
||||
class HardMode(Choice, LADXROption):
|
||||
"""
|
||||
**Oracle:** Less iframes and health from drops. Bombs damage yourself. Water
|
||||
damages you without flippers. No pieces of power or acorns.
|
||||
|
||||
**Hero:** Switch version hero mode, double damage, no heart/fairy drops.
|
||||
|
||||
**OHKO:** You die on a single hit, always.
|
||||
[Oracle] Less iframes and health from drops. Bombs damage yourself. Water damages you without flippers. No piece of power or acorn.
|
||||
[Hero] Switch version hero mode, double damage, no heart/fairy drops.
|
||||
[One hit KO] You die on a single hit, always.
|
||||
"""
|
||||
display_name = "Hard Mode"
|
||||
ladxr_name = "hardmode"
|
||||
rich_text_doc = True
|
||||
option_none = 0
|
||||
option_oracle = 1
|
||||
option_hero = 2
|
||||
@@ -325,26 +294,44 @@ class HardMode(Choice, LADXROption):
|
||||
default = option_none
|
||||
|
||||
|
||||
# Setting('steal', 'Gameplay', 't', 'Stealing from the shop',
|
||||
# options=[('always', 'a', 'Always'), ('never', 'n', 'Never'), ('default', '', 'Normal')], default='default',
|
||||
# description="""Effects when you can steal from the shop. Stealing is bad and never in logic.
|
||||
# [Normal] requires the sword before you can steal.
|
||||
# [Always] you can always steal from the shop
|
||||
# [Never] you can never steal from the shop."""),
|
||||
class Bowwow(Choice):
|
||||
"""Allows BowWow to be taken into any area. Certain enemies and bosses are given a new weakness to BowWow.
|
||||
[Normal] BowWow is in the item pool, but can be logically expected as a damage source.
|
||||
[Swordless] The progressive swords are removed from the item pool.
|
||||
"""
|
||||
display_name = "BowWow"
|
||||
normal = 0
|
||||
swordless = 1
|
||||
default = normal
|
||||
|
||||
|
||||
class Overworld(Choice, LADXROption):
|
||||
"""
|
||||
**Open Mabe:** Replaces rock on the east side of Mabe Village with bushes,
|
||||
allowing access to Ukuku Prairie without Power Bracelet.
|
||||
[Open Mabe] Replaces rock on the east side of Mabe Village with bushes, allowing access to Ukuku Prairie without Power Bracelet.
|
||||
"""
|
||||
display_name = "Overworld"
|
||||
ladxr_name = "overworld"
|
||||
rich_text_doc = True
|
||||
option_normal = 0
|
||||
option_open_mabe = 1
|
||||
default = option_normal
|
||||
|
||||
|
||||
# Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
|
||||
# description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
|
||||
|
||||
|
||||
class Quickswap(Choice, LADXROption):
|
||||
"""
|
||||
Instead of opening the map, the *SELECT* button swaps the top item of your inventory on to your *A* or *B* button.
|
||||
Adds that the SELECT button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.
|
||||
"""
|
||||
display_name = "Quickswap"
|
||||
ladxr_name = "quickswap"
|
||||
rich_text_doc = True
|
||||
option_none = 0
|
||||
option_a = 1
|
||||
option_b = 2
|
||||
@@ -353,11 +340,10 @@ class Quickswap(Choice, LADXROption):
|
||||
|
||||
class TextMode(Choice, LADXROption):
|
||||
"""
|
||||
**Fast:** Makes text appear twice as fast.
|
||||
[Fast] Makes text appear twice as fast
|
||||
"""
|
||||
display_name = "Text Mode"
|
||||
ladxr_name = "textmode"
|
||||
rich_text_doc = True
|
||||
option_normal = 0
|
||||
option_fast = 1
|
||||
default = option_fast
|
||||
@@ -377,8 +363,7 @@ class LowHpBeep(Choice, LADXROption):
|
||||
|
||||
class NoFlash(DefaultOnToggle, LADXROption):
|
||||
"""
|
||||
Remove the flashing light effects from Mamu, shopkeeper and MadBatter.
|
||||
Useful for capture cards and people that are sensitive to these things.
|
||||
Remove the flashing light effects from Mamu, shopkeeper and MadBatter. Useful for capture cards and people that are sensitive to these things.
|
||||
"""
|
||||
display_name = "No Flash"
|
||||
ladxr_name = "noflash"
|
||||
@@ -386,34 +371,23 @@ class NoFlash(DefaultOnToggle, LADXROption):
|
||||
|
||||
class BootsControls(Choice):
|
||||
"""
|
||||
Adds an additional button to activate Pegasus Boots (does nothing if you
|
||||
haven't picked up your boots!)
|
||||
|
||||
**Vanilla:** Nothing changes, you have to equip the boots to use them.
|
||||
|
||||
**Bracelet:** Holding down the button for the bracelet also activates boots
|
||||
(somewhat like Link to the Past).
|
||||
|
||||
**Press A:** Holding down A activates boots.
|
||||
|
||||
**Press B:** Holding down B activates boots.
|
||||
Adds additional button to activate Pegasus Boots (does nothing if you haven't picked up your boots!)
|
||||
[Vanilla] Nothing changes, you have to equip the boots to use them
|
||||
[Bracelet] Holding down the button for the bracelet also activates boots (somewhat like Link to the Past)
|
||||
[Press A] Holding down A activates boots
|
||||
[Press B] Holding down B activates boots
|
||||
"""
|
||||
display_name = "Boots Controls"
|
||||
rich_text_doc = True
|
||||
option_vanilla = 0
|
||||
option_bracelet = 1
|
||||
option_press_a = 2
|
||||
alias_a = 2
|
||||
option_press_b = 3
|
||||
alias_b = 3
|
||||
|
||||
|
||||
class LinkPalette(Choice, LADXROption):
|
||||
"""
|
||||
Sets Link's palette.
|
||||
|
||||
A-D are color palettes usually used during the damage animation and can
|
||||
change based on where you are.
|
||||
Sets link's palette
|
||||
A-D are color palettes usually used during the damage animation and can change based on where you are.
|
||||
"""
|
||||
display_name = "Link's Palette"
|
||||
ladxr_name = "linkspalette"
|
||||
@@ -434,21 +408,14 @@ class LinkPalette(Choice, LADXROption):
|
||||
|
||||
class TrendyGame(Choice):
|
||||
"""
|
||||
**Easy:** All of the items hold still for you.
|
||||
|
||||
**Normal:** The vanilla behavior.
|
||||
|
||||
**Hard:** The trade item also moves.
|
||||
|
||||
**Harder:** The items move faster.
|
||||
|
||||
**Hardest:** The items move diagonally.
|
||||
|
||||
**Impossible:** The items move impossibly fast, may scroll on and off the
|
||||
screen.
|
||||
[Easy] All of the items hold still for you
|
||||
[Normal] The vanilla behavior
|
||||
[Hard] The trade item also moves
|
||||
[Harder] The items move faster
|
||||
[Hardest] The items move diagonally
|
||||
[Impossible] The items move impossibly fast, may scroll on and off the screen
|
||||
"""
|
||||
display_name = "Trendy Game"
|
||||
rich_text_doc = True
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
@@ -468,24 +435,15 @@ class GfxMod(DefaultOffToggle):
|
||||
class Palette(Choice):
|
||||
"""
|
||||
Sets the palette for the game.
|
||||
|
||||
Note: A few places aren't patched, such as the menu and a few color dungeon
|
||||
tiles.
|
||||
|
||||
**Normal:** The vanilla palette.
|
||||
|
||||
**1-Bit:** One bit of color per channel.
|
||||
|
||||
**2-Bit:** Two bits of color per channel.
|
||||
|
||||
**Greyscale:** Shades of grey.
|
||||
|
||||
**Pink:** Aesthetic.
|
||||
|
||||
**Inverted:** Inverted.
|
||||
Note: A few places aren't patched, such as the menu and a few color dungeon tiles.
|
||||
[Normal] The vanilla palette
|
||||
[1-Bit] One bit of color per channel
|
||||
[2-Bit] Two bits of color per channel
|
||||
[Greyscale] Shades of grey
|
||||
[Pink] Aesthetic
|
||||
[Inverted] Inverted
|
||||
"""
|
||||
display_name = "Palette"
|
||||
rich_text_doc = True
|
||||
option_normal = 0
|
||||
option_1bit = 1
|
||||
option_2bit = 2
|
||||
@@ -496,15 +454,12 @@ class Palette(Choice):
|
||||
|
||||
class Music(Choice, LADXROption):
|
||||
"""
|
||||
**Vanilla:** Regular Music
|
||||
|
||||
**Shuffled:** Shuffled Music
|
||||
|
||||
**Off:** No music
|
||||
[Vanilla] Regular Music
|
||||
[Shuffled] Shuffled Music
|
||||
[Off] No music
|
||||
"""
|
||||
display_name = "Music"
|
||||
ladxr_name = "music"
|
||||
rich_text_doc = True
|
||||
option_vanilla = 0
|
||||
option_shuffled = 1
|
||||
option_off = 2
|
||||
@@ -520,14 +475,10 @@ class Music(Choice, LADXROption):
|
||||
|
||||
class Warps(Choice):
|
||||
"""
|
||||
**Improved:** Adds remake style warp screen to the game. Choose your warp
|
||||
destination on the map after jumping in a portal and press *B* to select.
|
||||
|
||||
**Improved Additional:** Improved warps, and adds a warp point at Crazy
|
||||
Tracy's house (the Mambo teleport spot) and Eagle's Tower.
|
||||
[Improved] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
||||
[Improved Additional] Improved warps, and adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower.
|
||||
"""
|
||||
display_name = "Warps"
|
||||
rich_text_doc = True
|
||||
option_vanilla = 0
|
||||
option_improved = 1
|
||||
option_improved_additional = 2
|
||||
@@ -536,24 +487,19 @@ class Warps(Choice):
|
||||
|
||||
class InGameHints(DefaultOnToggle):
|
||||
"""
|
||||
When enabled, owl statues and library books may indicate the location of
|
||||
your items in the multiworld.
|
||||
When enabled, owl statues and library books may indicate the location of your items in the multiworld.
|
||||
"""
|
||||
display_name = "In-game Hints"
|
||||
|
||||
|
||||
class TarinsGift(Choice):
|
||||
"""
|
||||
**Local Progression:** Forces Tarin's gift to be an item that immediately
|
||||
opens up local checks. Has little effect in single player games, and isn't
|
||||
always necessary with randomized entrances.
|
||||
|
||||
**Bush Breaker:** Forces Tarin's gift to be an item that can destroy bushes.
|
||||
|
||||
**Any Item:** Tarin's gift can be any item for any world
|
||||
[Local Progression] Forces Tarin's gift to be an item that immediately opens up local checks.
|
||||
Has little effect in single player games, and isn't always necessary with randomized entrances.
|
||||
[Bush Breaker] Forces Tarin's gift to be an item that can destroy bushes.
|
||||
[Any Item] Tarin's gift can be any item for any world
|
||||
"""
|
||||
display_name = "Tarin's Gift"
|
||||
rich_text_doc = True
|
||||
option_local_progression = 0
|
||||
option_bush_breaker = 1
|
||||
option_any_item = 2
|
||||
@@ -562,69 +508,67 @@ class TarinsGift(Choice):
|
||||
|
||||
class StabilizeItemPool(DefaultOffToggle):
|
||||
"""
|
||||
By default, some rupees in the item pool are randomly swapped with bombs,
|
||||
arrows, powders, or capacity upgrades. This set of items is also used as
|
||||
filler. This option disables that swapping and makes *Nothing* the filler
|
||||
item.
|
||||
By default, rupees in the item pool may be randomly swapped with bombs, arrows, powders, or capacity upgrades. This option disables that swapping, which is useful for plando.
|
||||
"""
|
||||
display_name = "Stabilize Item Pool"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class ForeignItemIcons(Choice):
|
||||
"""
|
||||
Choose how to display foreign items.
|
||||
|
||||
**Guess By Name:** Foreign items can look like any Link's Awakening item.
|
||||
|
||||
**Indicate Progression:** Foreign items are either a Piece of Power
|
||||
(progression) or Guardian Acorn (non-progression).
|
||||
[Guess By Name] Foreign items can look like any Link's Awakening item.
|
||||
[Indicate Progression] Foreign items are either a Piece of Power (progression) or Guardian Acorn (non-progression).
|
||||
"""
|
||||
display_name = "Foreign Item Icons"
|
||||
rich_text_doc = True
|
||||
option_guess_by_name = 0
|
||||
option_indicate_progression = 1
|
||||
default = option_guess_by_name
|
||||
|
||||
|
||||
ladx_option_groups = [
|
||||
OptionGroup("Gameplay Adjustments", [
|
||||
InGameHints,
|
||||
TarinsGift,
|
||||
HardMode,
|
||||
TrendyGame,
|
||||
OptionGroup("Goal Options", [
|
||||
Goal,
|
||||
InstrumentCount,
|
||||
]),
|
||||
OptionGroup("World Layout", [
|
||||
Overworld,
|
||||
Warps,
|
||||
DungeonShuffle,
|
||||
EntranceShuffle,
|
||||
]),
|
||||
OptionGroup("Item Pool", [
|
||||
OptionGroup("Shuffles", [
|
||||
ShuffleInstruments,
|
||||
ShuffleNightmareKeys,
|
||||
ShuffleSmallKeys,
|
||||
ShuffleMaps,
|
||||
ShuffleCompasses,
|
||||
ShuffleStoneBeaks,
|
||||
ShuffleStoneBeaks
|
||||
]),
|
||||
OptionGroup("Warp Points", [
|
||||
Warps,
|
||||
]),
|
||||
OptionGroup("Miscellaneous", [
|
||||
TradeQuest,
|
||||
Rooster,
|
||||
StabilizeItemPool,
|
||||
]),
|
||||
OptionGroup("Quality of Life & Aesthetic", [
|
||||
TarinsGift,
|
||||
Overworld,
|
||||
TrendyGame,
|
||||
InGameHints,
|
||||
NagMessages,
|
||||
StabilizeItemPool,
|
||||
Quickswap,
|
||||
BootsControls,
|
||||
ForeignItemIcons,
|
||||
GfxMod,
|
||||
HardMode,
|
||||
BootsControls
|
||||
]),
|
||||
OptionGroup("Experimental", [
|
||||
DungeonShuffle,
|
||||
EntranceShuffle
|
||||
]),
|
||||
OptionGroup("Visuals & Sound", [
|
||||
LinkPalette,
|
||||
Palette,
|
||||
APTitleScreen,
|
||||
TextShuffle,
|
||||
TextMode,
|
||||
ForeignItemIcons,
|
||||
APTitleScreen,
|
||||
GfxMod,
|
||||
Music,
|
||||
MusicChangeCondition,
|
||||
LowHpBeep,
|
||||
TextMode,
|
||||
NoFlash,
|
||||
])
|
||||
]
|
||||
@@ -632,12 +576,24 @@ ladx_option_groups = [
|
||||
@dataclass
|
||||
class LinksAwakeningOptions(PerGameCommonOptions):
|
||||
logic: Logic
|
||||
tradequest: TradeQuest
|
||||
rooster: Rooster
|
||||
experimental_dungeon_shuffle: DungeonShuffle
|
||||
# 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'),
|
||||
# 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'),
|
||||
# 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'),
|
||||
# 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'),
|
||||
tradequest: TradeQuest # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'),
|
||||
# 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'),
|
||||
rooster: Rooster # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'),
|
||||
# 'boomerang': Boomerang,
|
||||
# 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'),
|
||||
experimental_dungeon_shuffle: DungeonShuffle # 'Randomizes the dungeon that each dungeon entrance leads to'),
|
||||
experimental_entrance_shuffle: EntranceShuffle
|
||||
# 'bossshuffle': BossShuffle,
|
||||
# 'minibossshuffle': BossShuffle,
|
||||
goal: Goal
|
||||
instrument_count: InstrumentCount
|
||||
# 'itempool': ItemPool,
|
||||
# 'bowwow': Bowwow,
|
||||
# 'overworld': Overworld,
|
||||
link_palette: LinkPalette
|
||||
warps: Warps
|
||||
trendy_game: TrendyGame
|
||||
|
||||
@@ -71,7 +71,7 @@ class LinksAwakeningWebWorld(WebWorld):
|
||||
"setup/en",
|
||||
["zig"]
|
||||
)]
|
||||
theme = "ocean"
|
||||
theme = "dirt"
|
||||
option_groups = ladx_option_groups
|
||||
options_presets: typing.Dict[str, typing.Dict[str, typing.Any]] = {
|
||||
"Keysanity": {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Meritous",
|
||||
"authors": ["KewlioMZX"],
|
||||
"world_version": "1.0.0",
|
||||
"minimum_ap_version": "0.6.4"
|
||||
}
|
||||
@@ -96,6 +96,7 @@ class MM2World(World):
|
||||
location_name_groups = location_groups
|
||||
web = MM2WebWorld()
|
||||
rom_name: bytearray
|
||||
world_version: Tuple[int, int, int] = (0, 3, 2)
|
||||
wily_5_weapons: Dict[int, List[int]]
|
||||
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
@@ -132,9 +133,6 @@ class MM2World(World):
|
||||
Consumables.option_all):
|
||||
stage.add_locations(energy_pickups[region], MM2Location)
|
||||
self.multiworld.regions.append(stage)
|
||||
goal_location = self.get_location(dr_wily)
|
||||
goal_location.place_locked_item(MM2Item("Victory", ItemClassification.progression, None, self.player))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
def create_item(self, name: str) -> MM2Item:
|
||||
item = item_table[name]
|
||||
@@ -191,6 +189,11 @@ class MM2World(World):
|
||||
f"Incompatible starting Robot Master, changing to "
|
||||
f"{self.options.starting_robot_master.current_key.replace('_', ' ').title()}")
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
goal_location = self.get_location(dr_wily)
|
||||
goal_location.place_locked_item(MM2Item("Victory", ItemClassification.progression, None, self.player))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
def fill_hook(self,
|
||||
progitempool: List["Item"],
|
||||
usefulitempool: List["Item"],
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"game": "Mega Man 2",
|
||||
"world_version": "0.3.2",
|
||||
"minimum_ap_version": "0.6.4"
|
||||
}
|
||||
@@ -665,10 +665,4 @@ SONG_DATA: Dict[str, SongData] = {
|
||||
"Midnight Blue": SongData(2900789, "88-1", "MUSE RADIO FM106", True, 2, 5, 7),
|
||||
"overwork feat.Woonoo": SongData(2900790, "88-2", "MUSE RADIO FM106", True, 2, 6, 8),
|
||||
"SUPER CITYLIGHTS": SongData(2900791, "88-3", "MUSE RADIO FM106", True, 5, 7, 10),
|
||||
"Flametide": SongData(2900792, "89-0", "Legendary Voyage, Mystic Treasure", True, 5, 7, 9),
|
||||
"Embrace feat. Kiyon": SongData(2900793, "89-1", "Legendary Voyage, Mystic Treasure", True, 2, 5, 8),
|
||||
"Magazines feat. Nia Suzune": SongData(2900794, "89-2", "Legendary Voyage, Mystic Treasure", True, 3, 6, 8),
|
||||
"Temptation": SongData(2900795, "89-3", "Legendary Voyage, Mystic Treasure", False, 5, 8, 10),
|
||||
"PwP": SongData(2900796, "89-4", "Legendary Voyage, Mystic Treasure", True, 3, 6, 9),
|
||||
"I Can Show You": SongData(2900797, "89-5", "Legendary Voyage, Mystic Treasure", False, 5, 7, 9),
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Muse Dash",
|
||||
"authors": ["DeamonHunter"],
|
||||
"world_version": "1.5.25",
|
||||
"minimum_ap_version": "0.6.3"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Noita",
|
||||
"authors": ["Heinermann", "ScipioWright"],
|
||||
"minimum_ap_version": "0.6.4",
|
||||
"world_version": "1.4.0"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Pokemon Emerald",
|
||||
"world_version": "2.4.1",
|
||||
"minimum_ap_version": "0.6.1",
|
||||
"authors": ["Zunawe"]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -397,10 +397,6 @@ def randomize_abilities(world: "PokemonEmeraldWorld") -> None:
|
||||
ability_blacklist = {ability_label_to_value[label] for label in ability_blacklist_labels}
|
||||
ability_whitelist = [a.ability_id for a in data.abilities if a.ability_id not in ability_blacklist]
|
||||
|
||||
# If every ability is blacklisted, set all abilities to Cacophony, effectively disabling abilities
|
||||
if len(ability_whitelist) == 0:
|
||||
ability_whitelist = [data.constants["ABILITY_CACOPHONY"]]
|
||||
|
||||
if world.options.abilities == RandomizeAbilities.option_follow_evolutions:
|
||||
already_modified: Set[int] = set()
|
||||
|
||||
|
||||
@@ -713,8 +713,7 @@ class PokemonRedBlueWorld(World):
|
||||
"require_pokedex": self.options.require_pokedex.value,
|
||||
"area_1_to_1_mapping": self.options.area_1_to_1_mapping.value,
|
||||
"blind_trainers": self.options.blind_trainers.value,
|
||||
"game_version": self.options.game_version.value,
|
||||
"exp_all": self.options.exp_all.value,
|
||||
"v5_update": True,
|
||||
|
||||
}
|
||||
if self.options.type_chart_seed == "random" or self.options.type_chart_seed.value.isdigit():
|
||||
|
||||
@@ -810,10 +810,7 @@
|
||||
{
|
||||
"id": 48105,
|
||||
"name": "Engine controls blueprint",
|
||||
"region": "CaravanIsland",
|
||||
"requiresAccessToItems": [
|
||||
"Zipline tool"
|
||||
]
|
||||
"region": "CaravanIsland"
|
||||
},
|
||||
{
|
||||
"id": 48106,
|
||||
|
||||
+85
-268
@@ -170,309 +170,126 @@ sample_chao_names = [
|
||||
"Portia",
|
||||
"Graves",
|
||||
"Kaycee",
|
||||
"Ghandi",
|
||||
"Medli",
|
||||
"Jak",
|
||||
"Wario",
|
||||
"Theo",
|
||||
]
|
||||
|
||||
totally_real_item_names: dict[str, list[str]] = {
|
||||
"Bumper Stickers": [
|
||||
"Bonus Score",
|
||||
"Boosting Bumper",
|
||||
],
|
||||
totally_real_item_names = [
|
||||
"Mallet",
|
||||
"Lava Rod",
|
||||
"Master Knife",
|
||||
"Slippers",
|
||||
"Spade",
|
||||
|
||||
"Castlevania 64": [
|
||||
"Earth card",
|
||||
"Venus card",
|
||||
"Ax",
|
||||
"Storehouse Key",
|
||||
],
|
||||
"Progressive Car Upgrade",
|
||||
"Bonus Token",
|
||||
|
||||
"Celeste 64": [
|
||||
"Blueberry",
|
||||
"Side Flip",
|
||||
"Triple Dash Refills",
|
||||
"Swap Blocks",
|
||||
"Dream Blocks",
|
||||
],
|
||||
"Shortnail",
|
||||
"Runmaster",
|
||||
|
||||
"Celeste (Open World)": [
|
||||
"Green Boosters",
|
||||
"Triple Dash Refills",
|
||||
"Rising Platforms",
|
||||
"Red Bubbles",
|
||||
"Granny's Car Keys",
|
||||
"Blueberry",
|
||||
],
|
||||
"Courage Form",
|
||||
"Auto Courage",
|
||||
"Donald Defender",
|
||||
"Goofy Blizzard",
|
||||
"Ultimate Weapon",
|
||||
|
||||
"Civilization VI": [
|
||||
"Advanced Trebuchets",
|
||||
"The Wheel 2",
|
||||
"NFTs",
|
||||
],
|
||||
"Song of the Sky Whale",
|
||||
"Gryphon Shoes",
|
||||
"Wing Key",
|
||||
"Strength Anklet",
|
||||
|
||||
"Donkey Kong Country 3": [
|
||||
"Progressive Car Upgrade",
|
||||
"Bonus Token",
|
||||
],
|
||||
"Hairclip",
|
||||
|
||||
"Factorio": [
|
||||
"logistic-ai",
|
||||
"progressive-militia",
|
||||
"progressive-stronger-explosives",
|
||||
"uranium-food",
|
||||
],
|
||||
"Key of Wisdom",
|
||||
|
||||
"A Hat in Time": [
|
||||
"Fire Hat",
|
||||
"69 Pons",
|
||||
"Relic (Green Canyon)",
|
||||
"Relic (Cooler Cow)",
|
||||
"Time Fragment",
|
||||
],
|
||||
"Baking",
|
||||
"Progressive Block Mining",
|
||||
|
||||
"Hollow Knight": [
|
||||
"Shortnail",
|
||||
"Runmaster",
|
||||
],
|
||||
"Jar",
|
||||
"Whistle of Space",
|
||||
"Rito Tunic",
|
||||
|
||||
"Jak and Daxter The Precursor Legacy": [
|
||||
"69 Precursor Orbs",
|
||||
"Jump Roll",
|
||||
"Roll Kick",
|
||||
],
|
||||
"Kitchen Sink",
|
||||
|
||||
"Kirby's Dream Land 3": [
|
||||
"CooCoo",
|
||||
],
|
||||
"Rock Badge",
|
||||
"Key Card",
|
||||
"Pikachu",
|
||||
"Eevee",
|
||||
"HM02 Strength",
|
||||
|
||||
"Kingdom Hearts 2": [
|
||||
"Courage Form",
|
||||
"Auto Courage",
|
||||
"Donald Defender",
|
||||
"Goofy Blizzard",
|
||||
"Ultimate Weapon",
|
||||
],
|
||||
"Progressive Astromancers",
|
||||
"Progressive Chefs",
|
||||
"The Living Safe",
|
||||
"Lady Quinn",
|
||||
|
||||
"Lingo": [
|
||||
"Art Gallery (First Floor)",
|
||||
"Color Hunt - Pink Barrier",
|
||||
],
|
||||
"Dio's Worst Enemy",
|
||||
|
||||
"A Link to the Past": [
|
||||
"Mallet",
|
||||
"Lava Rod",
|
||||
"Master Knife",
|
||||
"Slippers",
|
||||
"Spade",
|
||||
"Big Key (Dark Palace)",
|
||||
"Big Key (Hera Tower)",
|
||||
],
|
||||
"Pink Chaos Emerald",
|
||||
"Black Chaos Emerald",
|
||||
"Tails - Large Cannon",
|
||||
"Eggman - Bazooka",
|
||||
"Eggman - Booster",
|
||||
"Knuckles - Shades",
|
||||
"Sonic - Magic Shoes",
|
||||
"Shadow - Bounce Bracelet",
|
||||
"Rouge - Air Necklace",
|
||||
"Big Key (Eggman's Pyramid)",
|
||||
|
||||
"Links Awakening DX": [
|
||||
"Song of the Sky Whale",
|
||||
"Gryphon Shoes",
|
||||
"Wing Key",
|
||||
"Strength Anklet",
|
||||
],
|
||||
"Sensor Bunker",
|
||||
"Phantom",
|
||||
"Soldier",
|
||||
|
||||
"Mario & Luigi Superstar Saga": [
|
||||
"Mega Nut",
|
||||
],
|
||||
"Plasma Suit",
|
||||
"Gravity Beam",
|
||||
"Hi-Jump Ball",
|
||||
|
||||
"The Messenger": [
|
||||
"Key of Anger",
|
||||
"Time Shard (69)",
|
||||
"Hydro",
|
||||
],
|
||||
"Cannon Unlock LLL",
|
||||
"Feather Cap",
|
||||
|
||||
"Muse Dash": [
|
||||
"U.N. Owen Was Her",
|
||||
"Renai Circulation",
|
||||
"Flyers",
|
||||
],
|
||||
"Progressive Yoshi",
|
||||
"Purple Switch Palace",
|
||||
"Cape Feather",
|
||||
|
||||
"Noita": [
|
||||
"Gold (69)",
|
||||
"Sphere",
|
||||
"Melee Die",
|
||||
],
|
||||
"Cane of Bryan",
|
||||
|
||||
"Ocarina of Time": [
|
||||
"Jar",
|
||||
"Whistle of Space",
|
||||
"Rito Tunic",
|
||||
"Boss Key (Forest Haven)",
|
||||
"Boss Key (Swamp Palace)",
|
||||
"Boss Key (Great Bay Temple)",
|
||||
],
|
||||
"Van Repair",
|
||||
"Autumn",
|
||||
"Galaxy Knife",
|
||||
"Green Cabbage Seeds",
|
||||
|
||||
"Old School Runescape": [
|
||||
"Area: Taverly",
|
||||
"Area: Meiyerditch",
|
||||
"Fire Cape",
|
||||
],
|
||||
"Timespinner Cog 1",
|
||||
|
||||
"Overcooked! 2": [
|
||||
"Kitchen Sink",
|
||||
],
|
||||
"Ladder",
|
||||
|
||||
"Paint": [
|
||||
"AI Enhance",
|
||||
"Paint Bucket",
|
||||
"Pen",
|
||||
],
|
||||
"Visible Dots",
|
||||
|
||||
"Pokemon Red and Blue": [
|
||||
"Rock Badge",
|
||||
"Key Card",
|
||||
"Pikachu",
|
||||
"Eevee",
|
||||
"HM02 Strength",
|
||||
"HM05 Fly",
|
||||
"HM01 Surf",
|
||||
"Card Key 12F",
|
||||
],
|
||||
"CooCoo",
|
||||
|
||||
"Risk of Rain 2": [
|
||||
"Dio's Worst Enemy",
|
||||
"Stage 5",
|
||||
"Mythical Item",
|
||||
],
|
||||
"Blueberry",
|
||||
|
||||
"Rogue Legacy": [
|
||||
"Progressive Astromancers",
|
||||
"Progressive Chefs",
|
||||
"The Living Safe",
|
||||
"Lady Quinn",
|
||||
],
|
||||
"Ear of Luigi",
|
||||
|
||||
"Saving Princess": [
|
||||
"Fire Spreadshot",
|
||||
"Volcano Key",
|
||||
"Frozen Key",
|
||||
],
|
||||
"Mega Nut",
|
||||
|
||||
"Secret of Evermore": [
|
||||
"Mantis Claw",
|
||||
"Progressive pants",
|
||||
"Deflect",
|
||||
],
|
||||
"DUELIST ALLIANCE",
|
||||
"DUEL OVERLOAD",
|
||||
"POWER OF THE ELEMENTS",
|
||||
"S:P Little Knight",
|
||||
"Red-Eyes Dark Dragoon",
|
||||
|
||||
"shapez": [
|
||||
"Spinner",
|
||||
"Toggle",
|
||||
"Slicer",
|
||||
"Splitter",
|
||||
],
|
||||
"Fire Hat",
|
||||
|
||||
"SMZ3": [
|
||||
"Cane of Bryan",
|
||||
],
|
||||
"Area: Taverly",
|
||||
"Area: Meiyerditch",
|
||||
"Fire Cape",
|
||||
|
||||
"Sonic Adventure 2 Battle": [
|
||||
"Pink Chaos Emerald",
|
||||
"Black Chaos Emerald",
|
||||
"Tails - Large Cannon",
|
||||
"Eggman - Bazooka",
|
||||
"Eggman - Booster",
|
||||
"Knuckles - Shades",
|
||||
"Sonic - Magic Shoes",
|
||||
"Shadow - Bounce Bracelet",
|
||||
"Rouge - Air Necklace",
|
||||
"Big Key (Eggman's Pyramid)",
|
||||
],
|
||||
"Donald Zeta Flare",
|
||||
|
||||
"Starcraft 2": [
|
||||
"Sensor Bunker",
|
||||
"Phantom",
|
||||
"Soldier",
|
||||
],
|
||||
"Category One of a Kind",
|
||||
"Category Fuller House",
|
||||
|
||||
"Stardew Valley": [
|
||||
"Van Repair",
|
||||
"Ship Repair",
|
||||
"Autumn",
|
||||
"Galaxy Knife",
|
||||
"Green Cabbage Seeds",
|
||||
"Casket",
|
||||
"Pet Moonlight Jelly",
|
||||
"Adventurer's Guild Key",
|
||||
],
|
||||
"Passive Camoflage",
|
||||
|
||||
"Super Mario Land 2": [
|
||||
"Luigi Coin",
|
||||
"Luigi Zone Progression",
|
||||
"Hard Mode",
|
||||
],
|
||||
|
||||
"Super Metroid": [
|
||||
"Plasma Suit",
|
||||
"Gravity Beam",
|
||||
"Hi-Jump Ball",
|
||||
],
|
||||
|
||||
"Super Mario 64": [
|
||||
"Cannon Unlock LLL",
|
||||
"Feather Cap",
|
||||
],
|
||||
|
||||
"Super Mario World": [
|
||||
"Progressive Yoshi",
|
||||
"Purple Switch Palace",
|
||||
"Cape Feather",
|
||||
"Fire Flower",
|
||||
"Cling",
|
||||
"Twirl Jump",
|
||||
],
|
||||
|
||||
"Timespinner": [
|
||||
"Timespinner Cog 1",
|
||||
"Leg Cannon",
|
||||
],
|
||||
|
||||
"TUNIC": [
|
||||
"Ladder To West Forest",
|
||||
"Money x69",
|
||||
"Page 69",
|
||||
"Master Sword",
|
||||
],
|
||||
|
||||
"The Wind Waker": [
|
||||
"Ballad of Storms",
|
||||
"Wind God's Song",
|
||||
"Earth God's Song",
|
||||
"Ordon's Pearl",
|
||||
],
|
||||
|
||||
"The Witness": [
|
||||
"Visible Dots",
|
||||
],
|
||||
|
||||
"Yacht Dice": [
|
||||
"Category One of a Kind",
|
||||
"Category Fuller House",
|
||||
],
|
||||
|
||||
"Yoshi's Island": [
|
||||
"Ear of Luigi",
|
||||
"+69 Stars",
|
||||
"Water Melon",
|
||||
"World 7 Gate",
|
||||
"Small Spring Ball",
|
||||
],
|
||||
|
||||
"Yu-Gi-Oh! 2006": [
|
||||
"DUELIST ALLIANCE",
|
||||
"DUEL OVERLOAD",
|
||||
"POWER OF THE ELEMENTS",
|
||||
"S:P Little Knight",
|
||||
"Red-Eyes Dark Dragoon",
|
||||
"Maxx C"
|
||||
],
|
||||
}
|
||||
"Earth Card",
|
||||
]
|
||||
|
||||
all_exits = [
|
||||
0x00, # Lobby to Neutral
|
||||
|
||||
@@ -1406,8 +1406,7 @@ def set_mission_upgrade_rules_standard(multiworld: MultiWorld, world: World, pla
|
||||
lambda state: (state.has(ItemName.rouge_mystic_melody, player) and
|
||||
state.has(ItemName.rouge_treasure_scope, player)))
|
||||
add_rule(multiworld.get_location(LocationName.white_jungle_lifebox_2, player),
|
||||
lambda state: (state.has(ItemName.shadow_flame_ring, player) and
|
||||
state.has(ItemName.shadow_air_shoes, player)))
|
||||
lambda state: state.has(ItemName.shadow_flame_ring, player))
|
||||
|
||||
add_rule(multiworld.get_location(LocationName.metal_harbor_lifebox_3, player),
|
||||
lambda state: state.has(ItemName.sonic_light_shoes, player))
|
||||
@@ -2063,8 +2062,6 @@ def set_mission_upgrade_rules_standard(multiworld: MultiWorld, world: World, pla
|
||||
add_rule(multiworld.get_location(LocationName.mad_space_big, player),
|
||||
lambda state: state.has(ItemName.rouge_iron_boots, player))
|
||||
|
||||
add_rule(multiworld.get_location(LocationName.cannon_core_big_1, player),
|
||||
lambda state: state.has(ItemName.tails_booster, player))
|
||||
add_rule(multiworld.get_location(LocationName.cannon_core_big_2, player),
|
||||
lambda state: state.has(ItemName.tails_booster, player) and
|
||||
state.has(ItemName.eggman_jet_engine, player))
|
||||
|
||||
@@ -613,8 +613,7 @@ class SA2BWorld(World):
|
||||
self.options.chao_stats.value > 0 or \
|
||||
self.options.chao_animal_parts or \
|
||||
self.options.chao_kindergarten or \
|
||||
self.options.black_market_slots.value > 0 or \
|
||||
self.options.goal.value == 7:
|
||||
self.options.black_market_slots.value > 0:
|
||||
return True;
|
||||
|
||||
return False
|
||||
@@ -758,16 +757,13 @@ class SA2BWorld(World):
|
||||
item_names = []
|
||||
player_names = []
|
||||
progression_flags = []
|
||||
totally_real_item_names_copy = totally_real_item_names.copy()
|
||||
location_names = [(LocationName.chao_black_market_base + str(i)) for i in range(1, self.options.black_market_slots.value + 1)]
|
||||
locations = [self.multiworld.get_location(location_name, self.player) for location_name in location_names]
|
||||
for location in locations:
|
||||
if location.item.classification & ItemClassification.trap:
|
||||
item_name = ""
|
||||
if location.item.game in totally_real_item_names:
|
||||
item_name = self.random.choice(totally_real_item_names[location.item.game])
|
||||
else:
|
||||
random_game_names: list[str] = self.random.choice(list(totally_real_item_names.values()))
|
||||
item_name = self.random.choice(random_game_names)
|
||||
item_name = self.random.choice(totally_real_item_names_copy)
|
||||
totally_real_item_names_copy.remove(item_name)
|
||||
item_names.append(item_name)
|
||||
else:
|
||||
item_names.append(location.item.name)
|
||||
|
||||
@@ -468,8 +468,7 @@ def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterIte
|
||||
|
||||
for item in item_list:
|
||||
# Catch-all for all of a faction's items
|
||||
# Unit upgrades required for no-builds will get the FilterExcluded lifted when flagging AllowedOrphan
|
||||
if not terran_build_missions and item.data.race == SC2Race.TERRAN:
|
||||
if not terran_missions and item.data.race == SC2Race.TERRAN:
|
||||
if item.name not in item_groups.nova_equipment:
|
||||
item.flags |= ItemFilterFlags.FilterExcluded
|
||||
continue
|
||||
@@ -484,6 +483,10 @@ def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterIte
|
||||
continue
|
||||
|
||||
# Faction units
|
||||
if (not terran_build_missions
|
||||
and item.data.type in (item_tables.TerranItemType.Unit, item_tables.TerranItemType.Building, item_tables.TerranItemType.Mercenary)
|
||||
):
|
||||
item.flags |= ItemFilterFlags.FilterExcluded
|
||||
if (not zerg_build_missions
|
||||
and item.data.type in (item_tables.ZergItemType.Unit, item_tables.ZergItemType.Mercenary, item_tables.ZergItemType.Evolution_Pit)
|
||||
):
|
||||
@@ -658,7 +661,6 @@ def flag_allowed_orphan_items(world: SC2World, item_list: List[FilterItem]) -> N
|
||||
item_names.MEDIC_STABILIZER_MEDPACKS, item_names.MARINE_LASER_TARGETING_SYSTEM,
|
||||
):
|
||||
item.flags |= ItemFilterFlags.AllowedOrphan
|
||||
item.flags &= ~ItemFilterFlags.FilterExcluded
|
||||
# These rules only trigger on Standard tactics
|
||||
if SC2Mission.BELLY_OF_THE_BEAST in missions and world.options.required_tactics == RequiredTactics.option_standard:
|
||||
for item in item_list:
|
||||
@@ -668,7 +670,6 @@ def flag_allowed_orphan_items(world: SC2World, item_list: List[FilterItem]) -> N
|
||||
item_names.FIREBAT_NANO_PROJECTORS, item_names.FIREBAT_JUGGERNAUT_PLATING, item_names.FIREBAT_PROGRESSIVE_STIMPACK
|
||||
):
|
||||
item.flags |= ItemFilterFlags.AllowedOrphan
|
||||
item.flags &= ~ItemFilterFlags.FilterExcluded
|
||||
if SC2Mission.EVIL_AWOKEN in missions and world.options.required_tactics == RequiredTactics.option_standard:
|
||||
for item in item_list:
|
||||
if item.name in (item_names.STALKER_PHASE_REACTOR, item_names.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, item_names.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION):
|
||||
|
||||
+12
-8
@@ -21,11 +21,10 @@ import random
|
||||
import concurrent.futures
|
||||
import time
|
||||
import uuid
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# CommonClient import first to trigger ModuleUpdater
|
||||
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser, handle_url_arg
|
||||
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser
|
||||
from Utils import init_logging, is_windows, async_start
|
||||
from .item import item_names, item_parents, race_to_item_type
|
||||
from .item.item_annotations import ITEM_NAME_ANNOTATIONS
|
||||
@@ -1299,15 +1298,20 @@ class CompatItemHolder(typing.NamedTuple):
|
||||
quantity: int = 1
|
||||
|
||||
|
||||
async def main(args: typing.Sequence[str] | None):
|
||||
def parse_uri(uri: str) -> str:
|
||||
if "://" in uri:
|
||||
uri = uri.split("://", 1)[1]
|
||||
return uri.split('?', 1)[0]
|
||||
|
||||
|
||||
async def main():
|
||||
multiprocessing.freeze_support()
|
||||
parser = get_base_parser()
|
||||
parser.add_argument('--name', default=None, help="Slot Name to connect as.")
|
||||
args, uri = parser.parse_known_args(args)
|
||||
args, uri = parser.parse_known_args()
|
||||
|
||||
if uri and uri[0].startswith('archipelago://'):
|
||||
args.url = uri[0]
|
||||
handle_url_arg(args, parser)
|
||||
args.connect = parse_uri(' '.join(uri))
|
||||
|
||||
ctx = SC2Context(args.connect, args.password)
|
||||
ctx.auth = args.name
|
||||
@@ -2342,7 +2346,7 @@ def force_settings_save_on_close() -> None:
|
||||
_has_forced_save = True
|
||||
|
||||
|
||||
def launch(*args: str):
|
||||
def launch():
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main(args))
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1860,7 +1860,7 @@ item_table = {
|
||||
item_names.DARK_TEMPLAR_ARCHON_MERGE: ItemData(417 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 27, SC2Race.PROTOSS, classification=ItemClassification.progression, parent=item_names.DARK_TEMPLAR),
|
||||
item_names.ASCENDANT_ARCHON_MERGE: ItemData(418 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 28, SC2Race.PROTOSS, classification=ItemClassification.progression_skip_balancing, parent=item_names.ASCENDANT),
|
||||
item_names.SCOUT_SUPPLY_EFFICIENCY: ItemData(419 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 29, SC2Race.PROTOSS, parent=item_names.SCOUT),
|
||||
item_names.REAVER_BARGAIN_BIN_PRICES: ItemData(420 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_5, 0, SC2Race.PROTOSS, parent=item_names.REAVER),
|
||||
item_names.REAVER_BARGAIN_BIN_PRICES: ItemData(420 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_5, 0, SC2Race.PROTOSS, parent=item_names.SCOUT),
|
||||
|
||||
|
||||
# War Council
|
||||
|
||||
+49
-52
@@ -2150,7 +2150,8 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Victory",
|
||||
SC2WOL_LOC_ID_OFFSET + 2800,
|
||||
LocationType.VICTORY,
|
||||
lambda state: logic.terran_competent_comp(state, 2),
|
||||
lambda state: logic.terran_competent_comp(state)
|
||||
and logic.terran_army_weapon_armor_upgrade_min_level(state) >= 2,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHATTER_THE_SKY.mission_name,
|
||||
@@ -2171,21 +2172,24 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Southeast Coolant Tower",
|
||||
SC2WOL_LOC_ID_OFFSET + 2803,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.terran_competent_comp(state, 2),
|
||||
lambda state: logic.terran_competent_comp(state)
|
||||
and logic.terran_army_weapon_armor_upgrade_min_level(state) >= 2,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHATTER_THE_SKY.mission_name,
|
||||
"Southwest Coolant Tower",
|
||||
SC2WOL_LOC_ID_OFFSET + 2804,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.terran_competent_comp(state, 2),
|
||||
lambda state: logic.terran_competent_comp(state)
|
||||
and logic.terran_army_weapon_armor_upgrade_min_level(state) >= 2,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHATTER_THE_SKY.mission_name,
|
||||
"Leviathan",
|
||||
SC2WOL_LOC_ID_OFFSET + 2805,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.terran_competent_comp(state, 2),
|
||||
lambda state: logic.terran_competent_comp(state)
|
||||
and logic.terran_army_weapon_armor_upgrade_min_level(state) >= 2,
|
||||
hard_rule=logic.terran_any_anti_air,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -2258,7 +2262,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 100,
|
||||
LocationType.VICTORY,
|
||||
lambda state: (
|
||||
logic.zerg_common_unit(state)
|
||||
logic.zerg_common_unit
|
||||
or state.has_any((item_names.ZERGLING, item_names.PYGALISK), player)
|
||||
),
|
||||
),
|
||||
@@ -2275,7 +2279,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
LocationType.VANILLA,
|
||||
lambda state: adv_tactics
|
||||
or (
|
||||
logic.zerg_common_unit(state)
|
||||
logic.zerg_common_unit
|
||||
or state.has_any((item_names.ZERGLING, item_names.PYGALISK), player)
|
||||
),
|
||||
),
|
||||
@@ -2286,7 +2290,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
LocationType.VANILLA,
|
||||
lambda state: adv_tactics
|
||||
or (
|
||||
logic.zerg_common_unit(state)
|
||||
logic.zerg_common_unit
|
||||
or state.has_any((item_names.ZERGLING, item_names.PYGALISK), player)
|
||||
),
|
||||
),
|
||||
@@ -2297,7 +2301,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
LocationType.VANILLA,
|
||||
lambda state: adv_tactics
|
||||
or (
|
||||
logic.zerg_common_unit(state)
|
||||
logic.zerg_common_unit
|
||||
or state.has_any((item_names.ZERGLING, item_names.PYGALISK), player)
|
||||
),
|
||||
),
|
||||
@@ -2320,7 +2324,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
LocationType.EXTRA,
|
||||
lambda state: adv_tactics
|
||||
or (
|
||||
logic.zerg_common_unit(state)
|
||||
logic.zerg_common_unit
|
||||
or state.has_any((item_names.ZERGLING, item_names.PYGALISK), player)
|
||||
),
|
||||
),
|
||||
@@ -2330,7 +2334,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 108,
|
||||
LocationType.CHALLENGE,
|
||||
lambda state: (
|
||||
logic.zerg_common_unit(state)
|
||||
logic.zerg_common_unit
|
||||
or state.has_any((item_names.ZERGLING, item_names.PYGALISK), player)
|
||||
),
|
||||
flags=LocationFlag.SPEEDRUN,
|
||||
@@ -2341,7 +2345,8 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 200,
|
||||
LocationType.VICTORY,
|
||||
lambda state: logic.basic_kerrigan(state)
|
||||
or kerriganless,
|
||||
or kerriganless
|
||||
or logic.grant_story_tech == GrantStoryTech.option_grant,
|
||||
hard_rule=logic.zerg_any_units_back_in_the_saddle_requirement,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -2350,7 +2355,8 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 201,
|
||||
LocationType.EXTRA,
|
||||
lambda state: logic.basic_kerrigan(state)
|
||||
or kerriganless,
|
||||
or kerriganless
|
||||
or logic.grant_story_tech == GrantStoryTech.option_grant,
|
||||
hard_rule=logic.zerg_any_units_back_in_the_saddle_requirement,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -2377,7 +2383,8 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 205,
|
||||
LocationType.EXTRA,
|
||||
lambda state: logic.basic_kerrigan(state)
|
||||
or kerriganless,
|
||||
or kerriganless
|
||||
or logic.grant_story_tech == GrantStoryTech.option_grant,
|
||||
hard_rule=logic.zerg_any_units_back_in_the_saddle_requirement,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -2443,7 +2450,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
lambda state: (
|
||||
logic.zerg_competent_comp(state)
|
||||
and logic.zerg_competent_anti_air(state)
|
||||
and (logic.basic_kerrigan(state, False) or kerriganless)
|
||||
and (logic.basic_kerrigan(state) or kerriganless)
|
||||
and logic.zerg_defense_rating(state, False, False) >= 3
|
||||
and logic.zerg_power_rating(state) >= 5
|
||||
),
|
||||
@@ -2535,7 +2542,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
lambda state: (
|
||||
logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state)
|
||||
),
|
||||
hard_rule=logic.zerg_any_anti_air,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHOOT_THE_MESSENGER.mission_name,
|
||||
@@ -2570,7 +2576,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
lambda state: (
|
||||
logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state)
|
||||
),
|
||||
hard_rule=logic.zerg_any_anti_air,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHOOT_THE_MESSENGER.mission_name,
|
||||
@@ -2612,7 +2617,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 510,
|
||||
LocationType.CHALLENGE,
|
||||
logic.zerg_competent_comp_competent_aa,
|
||||
hard_rule=logic.zerg_any_anti_air,
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -2621,7 +2625,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 511,
|
||||
LocationType.CHALLENGE,
|
||||
logic.zerg_competent_comp_competent_aa,
|
||||
hard_rule=logic.zerg_any_anti_air,
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -2630,7 +2633,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2HOTS_LOC_ID_OFFSET + 512,
|
||||
LocationType.CHALLENGE,
|
||||
logic.zerg_competent_comp_competent_aa,
|
||||
hard_rule=logic.zerg_any_anti_air,
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -3532,7 +3534,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
kerriganless
|
||||
or (
|
||||
logic.two_kerrigan_actives(state)
|
||||
and logic.basic_kerrigan(state)
|
||||
and (logic.basic_kerrigan(state) or logic.grant_story_tech == GrantStoryTech.option_grant)
|
||||
and logic.kerrigan_levels(state, 25)
|
||||
)
|
||||
),
|
||||
@@ -3556,7 +3558,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
kerriganless
|
||||
or (
|
||||
logic.two_kerrigan_actives(state)
|
||||
and logic.basic_kerrigan(state)
|
||||
and (logic.basic_kerrigan(state) or logic.grant_story_tech == GrantStoryTech.option_grant)
|
||||
and logic.kerrigan_levels(state, 25)
|
||||
)
|
||||
),
|
||||
@@ -3860,11 +3862,15 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2LOTV_LOC_ID_OFFSET + 300,
|
||||
LocationType.VICTORY,
|
||||
lambda state: adv_tactics
|
||||
or state.has_any((
|
||||
item_names.STALKER_PHASE_REACTOR,
|
||||
item_names.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES,
|
||||
item_names.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION,
|
||||
), player),
|
||||
or state.count_from_list(
|
||||
(
|
||||
item_names.STALKER_PHASE_REACTOR,
|
||||
item_names.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES,
|
||||
item_names.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION,
|
||||
),
|
||||
player,
|
||||
)
|
||||
>= 2,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.EVIL_AWOKEN.mission_name,
|
||||
@@ -4576,7 +4582,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
lambda state: (
|
||||
logic.protoss_deathball(state)
|
||||
and logic.protoss_power_rating(state) >= 6
|
||||
and (adv_tactics or logic.protoss_unsealing_the_past_ledge_requirement(state))
|
||||
and (adv_tactics or logic.protoss_fleet(state))
|
||||
),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -4587,7 +4593,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
lambda state: (
|
||||
logic.protoss_deathball(state)
|
||||
and logic.protoss_power_rating(state) >= 6
|
||||
and (adv_tactics or logic.protoss_unsealing_the_past_ledge_requirement(state))
|
||||
and (adv_tactics or logic.protoss_fleet(state))
|
||||
),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -7250,7 +7256,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 1809,
|
||||
LocationType.MASTERY,
|
||||
lambda state: (
|
||||
logic.protoss_anti_armor_anti_air(state)
|
||||
logic.protoss_anti_armor_anti_air
|
||||
and logic.protoss_defense_rating(state, False) >= 6
|
||||
and logic.protoss_common_unit(state)
|
||||
and logic.protoss_deathball(state)
|
||||
@@ -9081,7 +9087,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Close Obelisk",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 4801,
|
||||
LocationType.VANILLA,
|
||||
lambda state: adv_tactics or logic.zerg_common_unit(state),
|
||||
lambda state: adv_tactics or logic.zerg_common_unit,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.ECHOES_OF_THE_FUTURE_Z.mission_name,
|
||||
@@ -10047,7 +10053,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
logic.terran_common_unit(state)
|
||||
and logic.terran_competent_anti_air(state)
|
||||
),
|
||||
hard_rule=logic.terran_any_anti_air,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHOOT_THE_MESSENGER_T.mission_name,
|
||||
@@ -10085,7 +10090,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
logic.terran_common_unit(state)
|
||||
and logic.terran_competent_anti_air(state)
|
||||
),
|
||||
hard_rule=logic.terran_any_anti_air,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHOOT_THE_MESSENGER_T.mission_name,
|
||||
@@ -10129,7 +10133,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 6710,
|
||||
LocationType.CHALLENGE,
|
||||
lambda state: logic.terran_beats_protoss_deathball(state)
|
||||
and logic.terran_common_unit(state),
|
||||
and logic.terran_common_unit(state),
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -10138,9 +10142,8 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 6711,
|
||||
LocationType.CHALLENGE,
|
||||
lambda state: logic.terran_beats_protoss_deathball(state)
|
||||
and logic.terran_competent_ground_to_air(state)
|
||||
and logic.terran_common_unit(state),
|
||||
hard_rule=logic.terran_any_anti_air,
|
||||
and logic.terran_competent_ground_to_air(state)
|
||||
and logic.terran_common_unit(state),
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -10149,9 +10152,8 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 6712,
|
||||
LocationType.CHALLENGE,
|
||||
lambda state: logic.terran_beats_protoss_deathball(state)
|
||||
and logic.terran_competent_ground_to_air(state)
|
||||
and logic.terran_common_unit(state),
|
||||
hard_rule=logic.terran_any_anti_air,
|
||||
and logic.terran_competent_ground_to_air(state)
|
||||
and logic.terran_common_unit(state),
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -10163,7 +10165,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
logic.protoss_common_unit(state)
|
||||
and logic.protoss_anti_armor_anti_air(state)
|
||||
),
|
||||
hard_rule=logic.protoss_any_anti_air_unit,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHOOT_THE_MESSENGER_P.mission_name,
|
||||
@@ -10201,7 +10202,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
logic.protoss_common_unit(state)
|
||||
and logic.protoss_anti_armor_anti_air(state)
|
||||
),
|
||||
hard_rule=logic.protoss_any_anti_air_unit,
|
||||
),
|
||||
make_location_data(
|
||||
SC2Mission.SHOOT_THE_MESSENGER_P.mission_name,
|
||||
@@ -10249,7 +10249,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 6810,
|
||||
LocationType.CHALLENGE,
|
||||
logic.protoss_competent_comp,
|
||||
hard_rule=logic.protoss_any_anti_air_unit,
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -10258,7 +10257,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 6811,
|
||||
LocationType.CHALLENGE,
|
||||
logic.protoss_competent_comp,
|
||||
hard_rule=logic.protoss_any_anti_air_unit,
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -10267,7 +10265,6 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 6812,
|
||||
LocationType.CHALLENGE,
|
||||
logic.protoss_competent_comp,
|
||||
hard_rule=logic.protoss_any_anti_air_unit,
|
||||
flags=LocationFlag.BASEBUST,
|
||||
),
|
||||
make_location_data(
|
||||
@@ -11844,7 +11841,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Victory",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 9600,
|
||||
LocationType.VICTORY,
|
||||
lambda state: logic.protoss_deathball(state)
|
||||
lambda state: logic.protoss_deathball
|
||||
or (adv_tactics and logic.protoss_competent_comp(state)),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -11879,7 +11876,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Main Path Command Center",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 9605,
|
||||
LocationType.EXTRA,
|
||||
lambda state: logic.protoss_deathball(state)
|
||||
lambda state: logic.protoss_deathball
|
||||
or (adv_tactics and logic.protoss_competent_comp(state)),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -12029,7 +12026,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Victory",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 10000,
|
||||
LocationType.VICTORY,
|
||||
lambda state: logic.zerg_competent_comp(state)
|
||||
lambda state: logic.zerg_competent_comp
|
||||
and logic.zerg_moderate_anti_air(state),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -12037,7 +12034,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"First Prisoner Group",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 10001,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.zerg_competent_comp(state)
|
||||
lambda state: logic.zerg_competent_comp
|
||||
and logic.zerg_moderate_anti_air(state),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -12045,7 +12042,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Second Prisoner Group",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 10002,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.zerg_competent_comp(state)
|
||||
lambda state: logic.zerg_competent_comp
|
||||
and logic.zerg_moderate_anti_air(state),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -12053,7 +12050,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"First Pylon",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 10003,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.zerg_competent_comp(state)
|
||||
lambda state: logic.zerg_competent_comp
|
||||
and logic.zerg_moderate_anti_air(state),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -12061,7 +12058,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
"Second Pylon",
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 10004,
|
||||
LocationType.VANILLA,
|
||||
lambda state: logic.zerg_competent_comp(state)
|
||||
lambda state: logic.zerg_competent_comp
|
||||
and logic.zerg_moderate_anti_air(state),
|
||||
),
|
||||
make_location_data(
|
||||
@@ -12664,7 +12661,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
|
||||
SC2_RACESWAP_LOC_ID_OFFSET + 11406,
|
||||
LocationType.CHALLENGE,
|
||||
lambda state: (
|
||||
logic.zerg_brothers_in_arms_requirement(state)
|
||||
logic.zerg_brothers_in_arms_requirement
|
||||
and logic.zerg_base_buster(state)
|
||||
and logic.zerg_power_rating(state) >= 8
|
||||
),
|
||||
|
||||
@@ -10,10 +10,6 @@ from BaseClasses import CollectionState
|
||||
if TYPE_CHECKING:
|
||||
from .nodes import SC2MOGenMission
|
||||
|
||||
def always_true(state: CollectionState) -> bool:
|
||||
"""Helper method to avoid creating trivial lambdas"""
|
||||
return True
|
||||
|
||||
|
||||
class EntryRule(ABC):
|
||||
buffer_fulfilled: bool
|
||||
@@ -64,11 +60,6 @@ class EntryRule(ABC):
|
||||
"""Used in the client to determine accessibility while playing and to populate tooltips."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def find_mandatory_mission(self) -> SC2MOGenMission | None:
|
||||
"""Should return any mission that is mandatory to fulfill the entry rule, or `None` if there is no such mission."""
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuleData(ABC):
|
||||
@@ -112,11 +103,6 @@ class BeatMissionsEntryRule(EntryRule):
|
||||
mission_ids,
|
||||
resolved_reqs
|
||||
)
|
||||
|
||||
def find_mandatory_mission(self) -> SC2MOGenMission | None:
|
||||
if len(self.missions_to_beat) > 0:
|
||||
return self.missions_to_beat[0]
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -154,7 +140,7 @@ class CountMissionsEntryRule(EntryRule):
|
||||
def __init__(self, missions_to_count: List[SC2MOGenMission], target_amount: int, visual_reqs: List[Union[str, SC2MOGenMission]]):
|
||||
super().__init__()
|
||||
self.missions_to_count = missions_to_count
|
||||
if target_amount <= -1 or target_amount > len(missions_to_count):
|
||||
if target_amount == -1 or target_amount > len(missions_to_count):
|
||||
self.target_amount = len(missions_to_count)
|
||||
else:
|
||||
self.target_amount = target_amount
|
||||
@@ -169,20 +155,7 @@ class CountMissionsEntryRule(EntryRule):
|
||||
return max(mission_depth, self.target_amount - 1) # -1 because depth is zero-based but amount is one-based
|
||||
|
||||
def to_lambda(self, player: int) -> Callable[[CollectionState], bool]:
|
||||
if self.target_amount == 0:
|
||||
return always_true
|
||||
|
||||
beat_items = [mission.beat_item() for mission in self.missions_to_count]
|
||||
def count_missions(state: CollectionState) -> bool:
|
||||
count = 0
|
||||
for mission in range(len(self.missions_to_count)):
|
||||
if state.has(beat_items[mission], player):
|
||||
count += 1
|
||||
if count == self.target_amount:
|
||||
return True
|
||||
return False
|
||||
|
||||
return count_missions
|
||||
return lambda state: self.target_amount <= sum(state.has(mission.beat_item(), player) for mission in self.missions_to_count)
|
||||
|
||||
def to_slot_data(self) -> RuleData:
|
||||
resolved_reqs: List[Union[str, int]] = [req if isinstance(req, str) else req.mission.id for req in self.visual_reqs]
|
||||
@@ -192,11 +165,6 @@ class CountMissionsEntryRule(EntryRule):
|
||||
self.target_amount,
|
||||
resolved_reqs
|
||||
)
|
||||
|
||||
def find_mandatory_mission(self) -> SC2MOGenMission | None:
|
||||
if self.target_amount > 0 and self.target_amount == len(self.missions_to_count):
|
||||
return self.missions_to_count[0]
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -248,21 +216,13 @@ class SubRuleEntryRule(EntryRule):
|
||||
self.rule_id = rule_id
|
||||
self.rules_to_check = rules_to_check
|
||||
self.min_depth = -1
|
||||
if target_amount <= -1 or target_amount > len(rules_to_check):
|
||||
if target_amount == -1 or target_amount > len(rules_to_check):
|
||||
self.target_amount = len(rules_to_check)
|
||||
else:
|
||||
self.target_amount = target_amount
|
||||
|
||||
def _is_fulfilled(self, beaten_missions: Set[SC2MOGenMission], in_region_check: bool) -> bool:
|
||||
if len(self.rules_to_check) == 0:
|
||||
return True
|
||||
count = 0
|
||||
for rule in self.rules_to_check:
|
||||
if rule.is_fulfilled(beaten_missions, in_region_check):
|
||||
count += 1
|
||||
if count == self.target_amount:
|
||||
return True
|
||||
return False
|
||||
return self.target_amount <= sum(rule.is_fulfilled(beaten_missions, in_region_check) for rule in self.rules_to_check)
|
||||
|
||||
def _get_depth(self, beaten_missions: Set[SC2MOGenMission]) -> int:
|
||||
if len(self.rules_to_check) == 0:
|
||||
@@ -275,21 +235,7 @@ class SubRuleEntryRule(EntryRule):
|
||||
|
||||
def to_lambda(self, player: int) -> Callable[[CollectionState], bool]:
|
||||
sub_lambdas = [rule.to_lambda(player) for rule in self.rules_to_check]
|
||||
if self.target_amount == 0:
|
||||
return always_true
|
||||
if len(sub_lambdas) == 1:
|
||||
return sub_lambdas[0]
|
||||
|
||||
def count_rules(state: CollectionState) -> bool:
|
||||
count = 0
|
||||
for sub_lambda in sub_lambdas:
|
||||
if sub_lambda(state):
|
||||
count += 1
|
||||
if count == self.target_amount:
|
||||
return True
|
||||
return False
|
||||
|
||||
return count_rules
|
||||
return lambda state, sub_lambdas=sub_lambdas: self.target_amount <= sum(sub_lambda(state) for sub_lambda in sub_lambdas)
|
||||
|
||||
def to_slot_data(self) -> SubRuleRuleData:
|
||||
sub_rules = [rule.to_slot_data() for rule in self.rules_to_check]
|
||||
@@ -298,14 +244,6 @@ class SubRuleEntryRule(EntryRule):
|
||||
sub_rules,
|
||||
self.target_amount
|
||||
)
|
||||
|
||||
def find_mandatory_mission(self) -> SC2MOGenMission | None:
|
||||
if self.target_amount > 0 and self.target_amount == len(self.rules_to_check):
|
||||
for sub_rule in self.rules_to_check:
|
||||
mandatory_mission = sub_rule.find_mandatory_mission()
|
||||
if mandatory_mission is not None:
|
||||
return mandatory_mission
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -424,9 +362,6 @@ class ItemEntryRule(EntryRule):
|
||||
item_ids,
|
||||
visual_reqs
|
||||
)
|
||||
|
||||
def find_mandatory_mission(self) -> SC2MOGenMission | None:
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -491,60 +491,23 @@ def make_connections(mission_order: SC2MOGenMissionOrder, world: 'SC2World'):
|
||||
for layout in campaign.layouts:
|
||||
for mission in layout.missions:
|
||||
if not mission.option_empty:
|
||||
mission_uses_rule = mission.entry_rule.target_amount > 0
|
||||
mission_rule = mission.entry_rule.to_lambda(player)
|
||||
mandatory_prereq = mission.entry_rule.find_mandatory_mission()
|
||||
# Only layout entrances need to consider campaign & layout prerequisites
|
||||
if mission.option_entrance:
|
||||
campaign_uses_rule = campaign.entry_rule.target_amount > 0
|
||||
campaign_rule = campaign.entry_rule.to_lambda(player)
|
||||
layout_uses_rule = layout.entry_rule.target_amount > 0
|
||||
layout_rule = layout.entry_rule.to_lambda(player)
|
||||
|
||||
# Any mandatory prerequisite mission is good enough
|
||||
mandatory_prereq = campaign.entry_rule.find_mandatory_mission() if mandatory_prereq is None else mandatory_prereq
|
||||
mandatory_prereq = layout.entry_rule.find_mandatory_mission() if mandatory_prereq is None else mandatory_prereq
|
||||
|
||||
# Avoid calling obviously unused lambdas
|
||||
if campaign_uses_rule:
|
||||
if layout_uses_rule:
|
||||
if mission_uses_rule:
|
||||
unlock_rule = lambda state, campaign_rule=campaign_rule, layout_rule=layout_rule, mission_rule=mission_rule: \
|
||||
campaign_rule(state) and layout_rule(state) and mission_rule(state)
|
||||
else:
|
||||
unlock_rule = lambda state, campaign_rule=campaign_rule, layout_rule=layout_rule: \
|
||||
campaign_rule(state) and layout_rule(state)
|
||||
else:
|
||||
if mission_uses_rule:
|
||||
unlock_rule = lambda state, campaign_rule=campaign_rule, mission_rule=mission_rule: \
|
||||
campaign_rule(state) and mission_rule(state)
|
||||
else:
|
||||
unlock_rule = campaign_rule
|
||||
elif layout_uses_rule:
|
||||
if mission_uses_rule:
|
||||
unlock_rule = lambda state, layout_rule=layout_rule, mission_rule=mission_rule: \
|
||||
layout_rule(state) and mission_rule(state)
|
||||
else:
|
||||
unlock_rule = layout_rule
|
||||
elif mission_uses_rule:
|
||||
unlock_rule = mission_rule
|
||||
else:
|
||||
unlock_rule = None
|
||||
elif mission_uses_rule:
|
||||
campaign_rule = mission.parent().parent().entry_rule.to_lambda(player)
|
||||
layout_rule = mission.parent().entry_rule.to_lambda(player)
|
||||
unlock_rule = lambda state, campaign_rule=campaign_rule, layout_rule=layout_rule, mission_rule=mission_rule: \
|
||||
campaign_rule(state) and layout_rule(state) and mission_rule(state)
|
||||
else:
|
||||
unlock_rule = mission_rule
|
||||
else:
|
||||
unlock_rule = None
|
||||
|
||||
# Connect to a discovered mandatory mission if possible
|
||||
if mandatory_prereq is not None:
|
||||
connect(world, names, mandatory_prereq.mission.mission_name, mission.mission.mission_name, unlock_rule)
|
||||
else:
|
||||
# If no mission is known to be mandatory, connect to all previous missions instead
|
||||
for prev_mission in mission.prev:
|
||||
connect(world, names, prev_mission.mission.mission_name, mission.mission.mission_name, unlock_rule)
|
||||
# As a last resort connect to Menu
|
||||
if len(mission.prev) == 0:
|
||||
connect(world, names, "Menu", mission.mission.mission_name, unlock_rule)
|
||||
# Individually connect to previous missions
|
||||
for prev_mission in mission.prev:
|
||||
connect(world, names, prev_mission.mission.mission_name, mission.mission.mission_name,
|
||||
lambda state, unlock_rule=unlock_rule: unlock_rule(state))
|
||||
# If there are no previous missions, connect to Menu instead
|
||||
if len(mission.prev) == 0:
|
||||
connect(world, names, "Menu", mission.mission.mission_name,
|
||||
lambda state, unlock_rule=unlock_rule: unlock_rule(state))
|
||||
|
||||
|
||||
def connect(world: 'SC2World', used_names: Dict[str, int], source: str, target: str,
|
||||
|
||||
+8
-20
@@ -63,7 +63,7 @@ class Sc2MissionSet(OptionSet):
|
||||
return self.value.__len__()
|
||||
|
||||
|
||||
class SelectedRaces(OptionSet):
|
||||
class SelectRaces(OptionSet):
|
||||
"""
|
||||
Pick which factions' missions and items can be shuffled into the world.
|
||||
"""
|
||||
@@ -152,7 +152,6 @@ class MissionOrder(Choice):
|
||||
option_golden_path = 10
|
||||
option_hopscotch = 11
|
||||
option_custom = 99
|
||||
default = option_golden_path
|
||||
|
||||
|
||||
class MaximumCampaignSize(Range):
|
||||
@@ -171,7 +170,7 @@ class TwoStartPositions(Toggle):
|
||||
If turned on and 'grid', 'hopscotch', or 'golden_path' mission orders are selected,
|
||||
removes the first mission and allows both of the next two missions to be played from the start.
|
||||
"""
|
||||
display_name = "Two start missions"
|
||||
display_name = "Start with two unlocked missions on grid"
|
||||
default = Toggle.option_false
|
||||
|
||||
|
||||
@@ -252,21 +251,10 @@ class PlayerColorNova(ColorChoice):
|
||||
|
||||
|
||||
class EnabledCampaigns(OptionSet):
|
||||
"""
|
||||
Determines which campaign's missions will be used.
|
||||
Wings of Liberty, Prophecy, and Prologue are the only free-to-play campaigns.
|
||||
Valid campaign names:
|
||||
- 'Wings of Liberty'
|
||||
- 'Prophecy'
|
||||
- 'Heart of the Swarm'
|
||||
- 'Whispers of Oblivion (Legacy of the Void: Prologue)'
|
||||
- 'Legacy of the Void'
|
||||
- 'Into the Void (Legacy of the Void: Epilogue)'
|
||||
- 'Nova Covert Ops'
|
||||
"""
|
||||
"""Determines which campaign's missions will be used"""
|
||||
display_name = "Enabled Campaigns"
|
||||
valid_keys = {campaign.campaign_name for campaign in SC2Campaign if campaign != SC2Campaign.GLOBAL}
|
||||
default = set((SC2Campaign.WOL.campaign_name,))
|
||||
default = valid_keys
|
||||
|
||||
|
||||
class EnableRaceSwapVariants(Choice):
|
||||
@@ -1065,7 +1053,7 @@ class VictoryCache(Range):
|
||||
Controls how many additional checks are awarded for completing a mission.
|
||||
Goal missions are unaffected by this option.
|
||||
"""
|
||||
display_name = "Victory Cache"
|
||||
display_name = "Victory Checks"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 0
|
||||
@@ -1354,7 +1342,7 @@ class Starcraft2Options(PerGameCommonOptions):
|
||||
player_color_zerg: PlayerColorZerg
|
||||
player_color_zerg_primal: PlayerColorZergPrimal
|
||||
player_color_nova: PlayerColorNova
|
||||
selected_races: SelectedRaces
|
||||
selected_races: SelectRaces
|
||||
enabled_campaigns: EnabledCampaigns
|
||||
enable_race_swap: EnableRaceSwapVariants
|
||||
mission_race_balancing: EnableMissionRaceBalancing
|
||||
@@ -1448,7 +1436,7 @@ option_groups = [
|
||||
ShuffleCampaigns,
|
||||
AllInMap,
|
||||
TwoStartPositions,
|
||||
SelectedRaces,
|
||||
SelectRaces,
|
||||
ExcludeVeryHardMissions,
|
||||
EnableMissionRaceBalancing,
|
||||
]),
|
||||
@@ -1560,7 +1548,7 @@ def get_option_value(world: Union['SC2World', None], name: str) -> int:
|
||||
|
||||
|
||||
def get_enabled_races(world: Optional['SC2World']) -> Set[SC2Race]:
|
||||
race_names = world.options.selected_races.value if world and len(world.options.selected_races.value) > 0 else SelectedRaces.valid_keys
|
||||
race_names = world.options.selected_races.value if world and len(world.options.selected_races.value) > 0 else SelectRaces.valid_keys
|
||||
return {race for race in SC2Race if race.get_title() in race_names}
|
||||
|
||||
|
||||
|
||||
+135
-158
@@ -47,15 +47,8 @@ if TYPE_CHECKING:
|
||||
from . import SC2World
|
||||
|
||||
|
||||
def min2(a: int, b: int) -> int:
|
||||
"""`min()` that only takes two values; faster than baseline int by about 2x"""
|
||||
if a <= b:
|
||||
return a
|
||||
return b
|
||||
|
||||
|
||||
class SC2Logic:
|
||||
def __init__(self, world: Optional["SC2World"]) -> None:
|
||||
def __init__(self, world: Optional["SC2World"]):
|
||||
# Note: Don't store a reference to the world so we can cache this object on the world object
|
||||
self.player = -1 if world is None else world.player
|
||||
self.logic_level: int = world.options.required_tactics.value if world else RequiredTactics.default
|
||||
@@ -116,7 +109,7 @@ class SC2Logic:
|
||||
# has_group with count = 0 is always true for item placement and always false for SC2 item filtering
|
||||
return state.has_group("Missions", self.player, 0)
|
||||
|
||||
def get_very_hard_required_upgrade_level(self) -> bool:
|
||||
def get_very_hard_required_upgrade_level(self):
|
||||
return 2 if self.advanced_tactics else 3
|
||||
|
||||
def weapon_armor_upgrade_count(self, upgrade_item: str, state: CollectionState) -> int:
|
||||
@@ -140,7 +133,7 @@ class SC2Logic:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def soa_power_rating(self, state: CollectionState) -> bool:
|
||||
def soa_power_rating(self, state: CollectionState):
|
||||
power_rating = 0
|
||||
# Spear of Adun Ultimates (Strongest)
|
||||
for item, rating in soa_ultimate_ratings.items():
|
||||
@@ -210,7 +203,7 @@ class SC2Logic:
|
||||
def terran_common_unit(self, state: CollectionState) -> bool:
|
||||
return state.has_any(self.basic_terran_units, self.player)
|
||||
|
||||
def terran_early_tech(self, state: CollectionState) -> bool:
|
||||
def terran_early_tech(self, state: CollectionState):
|
||||
"""
|
||||
Basic combat unit that can be deployed quickly from mission start
|
||||
:param state
|
||||
@@ -454,7 +447,7 @@ class SC2Logic:
|
||||
defense_score += 2
|
||||
return defense_score
|
||||
|
||||
def terran_competent_comp(self, state: CollectionState, upgrade_level: int = 1) -> bool:
|
||||
def terran_competent_comp(self, state: CollectionState) -> bool:
|
||||
# All competent comps require anti-air
|
||||
if not self.terran_competent_anti_air(state):
|
||||
return False
|
||||
@@ -462,12 +455,12 @@ class SC2Logic:
|
||||
infantry_weapons = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, state)
|
||||
infantry_armor = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, state)
|
||||
infantry = state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player)
|
||||
if infantry_weapons >= upgrade_level + 1 and infantry_armor >= upgrade_level and infantry and self.terran_bio_heal(state):
|
||||
if infantry_weapons >= 2 and infantry_armor >= 1 and infantry and self.terran_bio_heal(state):
|
||||
return True
|
||||
# Mass Air-To-Ground
|
||||
ship_weapons = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state)
|
||||
ship_armor = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR, state)
|
||||
if ship_weapons >= upgrade_level and ship_armor >= upgrade_level:
|
||||
if ship_weapons >= 1 and ship_armor >= 1:
|
||||
air = (
|
||||
state.has_any({item_names.BANSHEE, item_names.BATTLECRUISER}, self.player)
|
||||
or state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY}, self.player)
|
||||
@@ -480,7 +473,7 @@ class SC2Logic:
|
||||
# Strong Mech
|
||||
vehicle_weapons = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, state)
|
||||
vehicle_armor = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, state)
|
||||
if vehicle_weapons >= upgrade_level and vehicle_armor >= upgrade_level:
|
||||
if vehicle_weapons >= 1 and vehicle_armor >= 1:
|
||||
strong_vehicle = state.has_any({item_names.THOR, item_names.SIEGE_TANK}, self.player)
|
||||
light_frontline = state.has_any(
|
||||
{item_names.MARINE, item_names.DOMINION_TROOPER, item_names.HELLION, item_names.VULTURE}, self.player
|
||||
@@ -769,27 +762,27 @@ class SC2Logic:
|
||||
def zerg_army_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int:
|
||||
count: int = WEAPON_ARMOR_UPGRADE_MAX_LEVEL
|
||||
if self.has_zerg_melee_unit:
|
||||
count = min2(count, self.zerg_melee_weapon_armor_upgrade_min_level(state))
|
||||
count = min(count, self.zerg_melee_weapon_armor_upgrade_min_level(state))
|
||||
if self.has_zerg_ranged_unit:
|
||||
count = min2(count, self.zerg_ranged_weapon_armor_upgrade_min_level(state))
|
||||
count = min(count, self.zerg_ranged_weapon_armor_upgrade_min_level(state))
|
||||
if self.has_zerg_air_unit:
|
||||
count = min2(count, self.zerg_flyer_weapon_armor_upgrade_min_level(state))
|
||||
count = min(count, self.zerg_flyer_weapon_armor_upgrade_min_level(state))
|
||||
return count
|
||||
|
||||
def zerg_melee_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int:
|
||||
return min2(
|
||||
return min(
|
||||
self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_MELEE_ATTACK, state),
|
||||
self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_GROUND_CARAPACE, state),
|
||||
)
|
||||
|
||||
def zerg_ranged_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int:
|
||||
return min2(
|
||||
return min(
|
||||
self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_MISSILE_ATTACK, state),
|
||||
self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_GROUND_CARAPACE, state),
|
||||
)
|
||||
|
||||
def zerg_flyer_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int:
|
||||
return min2(
|
||||
return min(
|
||||
self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_FLYER_ATTACK, state),
|
||||
self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_FLYER_CARAPACE, state),
|
||||
)
|
||||
@@ -1089,17 +1082,20 @@ class SC2Logic:
|
||||
)
|
||||
)
|
||||
|
||||
def zergling_hydra_roach_start(self, state: CollectionState) -> bool:
|
||||
def zergling_hydra_roach_start(self, state: CollectionState):
|
||||
"""
|
||||
Created mainly for engine of destruction start, but works for other missions with no-build starts.
|
||||
"""
|
||||
return state.has_any((
|
||||
return state.has_any(
|
||||
{
|
||||
item_names.ZERGLING_ADRENAL_OVERLOAD,
|
||||
item_names.HYDRALISK_FRENZY,
|
||||
item_names.ROACH_HYDRIODIC_BILE,
|
||||
item_names.ZERGLING_RAPTOR_STRAIN,
|
||||
item_names.ROACH_CORPSER_STRAIN,
|
||||
), self.player)
|
||||
},
|
||||
self.player,
|
||||
)
|
||||
|
||||
def kerrigan_levels(self, state: CollectionState, target: int, story_levels_available=True) -> bool:
|
||||
if (story_levels_available and self.story_levels_granted) or not self.kerrigan_unit_available:
|
||||
@@ -1115,7 +1111,7 @@ class SC2Logic:
|
||||
# Levels from missions beaten
|
||||
levels = self.kerrigan_levels_per_mission_completed * state.count_group("Missions", self.player)
|
||||
if self.kerrigan_levels_per_mission_completed_cap != -1:
|
||||
levels = min2(levels, self.kerrigan_levels_per_mission_completed_cap)
|
||||
levels = min(levels, self.kerrigan_levels_per_mission_completed_cap)
|
||||
# Levels from items
|
||||
for kerrigan_level_item in kerrigan_levels:
|
||||
level_amount = get_full_item_list()[kerrigan_level_item].number
|
||||
@@ -1123,14 +1119,12 @@ class SC2Logic:
|
||||
levels += item_count * level_amount
|
||||
# Total level cap
|
||||
if self.kerrigan_total_level_cap != -1:
|
||||
levels = min2(levels, self.kerrigan_total_level_cap)
|
||||
levels = min(levels, self.kerrigan_total_level_cap)
|
||||
|
||||
return levels >= target
|
||||
|
||||
def basic_kerrigan(self, state: CollectionState, story_tech_available=True) -> bool:
|
||||
if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant:
|
||||
return True
|
||||
# One active ability that can be used to defeat enemies directly
|
||||
def basic_kerrigan(self, state: CollectionState) -> bool:
|
||||
# One active ability that can be used to defeat enemies directly on Standard
|
||||
if not state.has_any(
|
||||
(
|
||||
item_names.KERRIGAN_LEAPING_STRIKE,
|
||||
@@ -1151,9 +1145,7 @@ class SC2Logic:
|
||||
return True
|
||||
return False
|
||||
|
||||
def two_kerrigan_actives(self, state: CollectionState, story_tech_available=True) -> bool:
|
||||
if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant:
|
||||
return True
|
||||
def two_kerrigan_actives(self, state: CollectionState) -> bool:
|
||||
count = 0
|
||||
for i in range(7):
|
||||
if state.has_any(kerrigan_logic_active_abilities, self.player):
|
||||
@@ -1633,17 +1625,20 @@ class SC2Logic:
|
||||
and state.has_any((item_names.SUPPLICANT, item_names.SHIELD_BATTERY), self.player)
|
||||
)
|
||||
|
||||
def zealot_sentry_slayer_start(self, state: CollectionState) -> bool:
|
||||
def zealot_sentry_slayer_start(self, state: CollectionState):
|
||||
"""
|
||||
Created mainly for engine of destruction start, but works for other missions with no-build starts.
|
||||
"""
|
||||
return state.has_any((
|
||||
return state.has_any(
|
||||
{
|
||||
item_names.ZEALOT_WHIRLWIND,
|
||||
item_names.SENTRY_DOUBLE_SHIELD_RECHARGE,
|
||||
item_names.SLAYER_PHASE_BLINK,
|
||||
item_names.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES,
|
||||
item_names.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION,
|
||||
), self.player)
|
||||
},
|
||||
self.player,
|
||||
)
|
||||
|
||||
# Mission-specific rules
|
||||
def ghost_of_a_chance_requirement(self, state: CollectionState) -> bool:
|
||||
@@ -2017,7 +2012,7 @@ class SC2Logic:
|
||||
and (self.advanced_tactics or state.has(item_names.YGGDRASIL, self.player))
|
||||
)
|
||||
|
||||
def protoss_supernova_requirement(self, state: CollectionState) -> bool:
|
||||
def protoss_supernova_requirement(self, state: CollectionState):
|
||||
return (
|
||||
(
|
||||
state.count(item_names.PROGRESSIVE_WARP_RELOCATE, self.player) >= 2
|
||||
@@ -2138,7 +2133,7 @@ class SC2Logic:
|
||||
and self.protoss_fleet(state)
|
||||
)
|
||||
|
||||
def terran_engine_of_destruction_requirement(self, state: CollectionState) -> bool:
|
||||
def terran_engine_of_destruction_requirement(self, state: CollectionState) -> int:
|
||||
power_rating = self.terran_power_rating(state)
|
||||
if power_rating < 3 or not self.marine_medic_upgrade(state) or not self.terran_common_unit(state):
|
||||
return False
|
||||
@@ -2151,7 +2146,7 @@ class SC2Logic:
|
||||
and state.has_any((item_names.BANSHEE, item_names.LIBERATOR), self.player)
|
||||
)
|
||||
|
||||
def zerg_engine_of_destruction_requirement(self, state: CollectionState) -> bool:
|
||||
def zerg_engine_of_destruction_requirement(self, state: CollectionState) -> int:
|
||||
power_rating = self.zerg_power_rating(state)
|
||||
if (
|
||||
power_rating < 3
|
||||
@@ -2166,21 +2161,21 @@ class SC2Logic:
|
||||
else:
|
||||
return self.zerg_base_buster(state)
|
||||
|
||||
def protoss_engine_of_destruction_requirement(self, state: CollectionState) -> bool:
|
||||
def protoss_engine_of_destruction_requirement(self, state: CollectionState):
|
||||
return (
|
||||
self.zealot_sentry_slayer_start(state)
|
||||
and self.protoss_repair_odin(state)
|
||||
and (self.protoss_deathball(state) or self.protoss_fleet(state))
|
||||
)
|
||||
|
||||
def zerg_repair_odin(self, state: CollectionState) -> bool:
|
||||
def zerg_repair_odin(self, state: CollectionState):
|
||||
return (
|
||||
self.zerg_has_infested_scv(state)
|
||||
or state.has_all({item_names.SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION, item_names.SWARM_QUEEN}, self.player)
|
||||
or (self.advanced_tactics and state.has(item_names.SWARM_QUEEN, self.player))
|
||||
)
|
||||
|
||||
def protoss_repair_odin(self, state: CollectionState) -> bool:
|
||||
def protoss_repair_odin(self, state: CollectionState):
|
||||
return (
|
||||
state.has(item_names.SENTRY, self.player)
|
||||
or state.has_all((item_names.CARRIER, item_names.CARRIER_REPAIR_DRONES), self.player)
|
||||
@@ -2201,7 +2196,7 @@ class SC2Logic:
|
||||
def protoss_in_utter_darkness_requirement(self, state: CollectionState) -> bool:
|
||||
return self.protoss_competent_comp(state) and self.protoss_defense_rating(state, True) >= 4
|
||||
|
||||
def terran_all_in_requirement(self, state: CollectionState) -> bool:
|
||||
def terran_all_in_requirement(self, state: CollectionState):
|
||||
"""
|
||||
All-in
|
||||
"""
|
||||
@@ -2233,7 +2228,7 @@ class SC2Logic:
|
||||
and state.has_any({item_names.HIVE_MIND_EMULATOR, item_names.PSI_DISRUPTER, item_names.MISSILE_TURRET}, self.player)
|
||||
)
|
||||
|
||||
def zerg_all_in_requirement(self, state: CollectionState) -> bool:
|
||||
def zerg_all_in_requirement(self, state: CollectionState):
|
||||
"""
|
||||
All-in (Zerg)
|
||||
"""
|
||||
@@ -2269,7 +2264,7 @@ class SC2Logic:
|
||||
and state.has_any({item_names.SPORE_CRAWLER, item_names.INFESTED_MISSILE_TURRET}, self.player)
|
||||
)
|
||||
|
||||
def protoss_all_in_requirement(self, state: CollectionState) -> bool:
|
||||
def protoss_all_in_requirement(self, state: CollectionState):
|
||||
"""
|
||||
All-in (Protoss)
|
||||
"""
|
||||
@@ -2400,7 +2395,7 @@ class SC2Logic:
|
||||
return (
|
||||
self.zerg_competent_comp(state)
|
||||
and (self.zerg_competent_anti_air(state) or self.advanced_tactics and self.zerg_moderate_anti_air(state))
|
||||
and (self.basic_kerrigan(state, False) or self.zerg_power_rating(state) >= 4)
|
||||
and (self.basic_kerrigan(state) or self.zerg_power_rating(state) >= 4)
|
||||
)
|
||||
|
||||
def protoss_hand_of_darkness_requirement(self, state: CollectionState) -> bool:
|
||||
@@ -2416,7 +2411,7 @@ class SC2Logic:
|
||||
return self.protoss_deathball(state) and self.protoss_power_rating(state) >= 8
|
||||
|
||||
def zerg_the_reckoning_requirement(self, state: CollectionState) -> bool:
|
||||
if not (self.zerg_power_rating(state) >= 6 or self.basic_kerrigan(state, False)):
|
||||
if not (self.zerg_power_rating(state) >= 6 or self.basic_kerrigan(state)):
|
||||
return False
|
||||
if self.take_over_ai_allies:
|
||||
return (
|
||||
@@ -2441,19 +2436,21 @@ class SC2Logic:
|
||||
|
||||
def protoss_can_attack_behind_chasm(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
state.has_any((
|
||||
item_names.SCOUT,
|
||||
item_names.SKIRMISHER,
|
||||
item_names.TEMPEST,
|
||||
item_names.CARRIER,
|
||||
item_names.SKYLORD,
|
||||
item_names.TRIREME,
|
||||
item_names.VOID_RAY,
|
||||
item_names.DESTROYER,
|
||||
item_names.PULSAR,
|
||||
item_names.DAWNBRINGER,
|
||||
item_names.MOTHERSHIP,
|
||||
), self.player)
|
||||
state.has_any(
|
||||
{
|
||||
item_names.SCOUT,
|
||||
item_names.TEMPEST,
|
||||
item_names.CARRIER,
|
||||
item_names.SKYLORD,
|
||||
item_names.TRIREME,
|
||||
item_names.VOID_RAY,
|
||||
item_names.DESTROYER,
|
||||
item_names.PULSAR,
|
||||
item_names.DAWNBRINGER,
|
||||
item_names.MOTHERSHIP,
|
||||
},
|
||||
self.player,
|
||||
)
|
||||
or self.protoss_has_blink(state)
|
||||
or (
|
||||
state.has(item_names.WARP_PRISM, self.player)
|
||||
@@ -2464,22 +2461,20 @@ class SC2Logic:
|
||||
|
||||
def the_infinite_cycle_requirement(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.kerrigan_levels(state, 70)
|
||||
and (
|
||||
self.grant_story_tech == GrantStoryTech.option_grant
|
||||
or not self.kerrigan_unit_available
|
||||
or (
|
||||
state.has_any(
|
||||
(
|
||||
item_names.KERRIGAN_KINETIC_BLAST,
|
||||
item_names.KERRIGAN_SPAWN_BANELINGS,
|
||||
item_names.KERRIGAN_LEAPING_STRIKE,
|
||||
item_names.KERRIGAN_SPAWN_LEVIATHAN,
|
||||
),
|
||||
self.player,
|
||||
)
|
||||
and self.basic_kerrigan(state)
|
||||
self.grant_story_tech == GrantStoryTech.option_grant
|
||||
or not self.kerrigan_unit_available
|
||||
or (
|
||||
state.has_any(
|
||||
(
|
||||
item_names.KERRIGAN_KINETIC_BLAST,
|
||||
item_names.KERRIGAN_SPAWN_BANELINGS,
|
||||
item_names.KERRIGAN_LEAPING_STRIKE,
|
||||
item_names.KERRIGAN_SPAWN_LEVIATHAN,
|
||||
),
|
||||
self.player,
|
||||
)
|
||||
and self.basic_kerrigan(state)
|
||||
and self.kerrigan_levels(state, 70)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2702,12 +2697,6 @@ class SC2Logic:
|
||||
and (self.take_over_ai_allies or (self.zerg_competent_comp(state) and self.zerg_big_monsters(state)))
|
||||
and self.zerg_power_rating(state) >= 6
|
||||
)
|
||||
|
||||
def protoss_unsealing_the_past_ledge_requirement(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
state.has_any((item_names.COLOSSUS, item_names.WRATHWALKER), self.player)
|
||||
or self.protoss_can_attack_behind_chasm(state)
|
||||
)
|
||||
|
||||
def terran_unsealing_the_past_requirement(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
@@ -2715,7 +2704,7 @@ class SC2Logic:
|
||||
and self.terran_competent_comp(state)
|
||||
and self.terran_power_rating(state) >= 6
|
||||
and (
|
||||
state.has_all((item_names.SIEGE_TANK, item_names.SIEGE_TANK_JUMP_JETS), self.player)
|
||||
state.has_all({item_names.SIEGE_TANK, item_names.SIEGE_TANK_JUMP_JETS}, self.player)
|
||||
or state.has_all(
|
||||
{item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY, item_names.BATTLECRUISER_MOIRAI_IMPULSE_DRIVE}, self.player
|
||||
)
|
||||
@@ -3143,9 +3132,8 @@ class SC2Logic:
|
||||
and (self.terran_cliffjumper(state) or state.has(item_names.BANSHEE, self.player))
|
||||
and self.nova_splash(state)
|
||||
and self.terran_defense_rating(state, True, False) >= 3
|
||||
and (self.advanced_tactics
|
||||
or state.has(item_names.NOVA_JUMP_SUIT_MODULE, self.player)
|
||||
)
|
||||
and self.advanced_tactics
|
||||
or state.has(item_names.NOVA_JUMP_SUIT_MODULE, self.player)
|
||||
)
|
||||
|
||||
def enemy_intelligence_garrisonable_unit(self, state: CollectionState) -> bool:
|
||||
@@ -3367,74 +3355,65 @@ class SC2Logic:
|
||||
|
||||
def has_terran_units(self, target: int) -> Callable[["CollectionState"], bool]:
|
||||
def _has_terran_units(state: CollectionState) -> bool:
|
||||
return (
|
||||
state.count_from_list_unique(
|
||||
item_groups.terran_units + item_groups.terran_buildings, self.player
|
||||
) >= target
|
||||
and (
|
||||
target < 5
|
||||
or self.terran_any_anti_air(state)
|
||||
)
|
||||
and (
|
||||
# Anything that can hit buildings
|
||||
return (state.count_from_list_unique(item_groups.terran_units + item_groups.terran_buildings, self.player) >= target) and (
|
||||
# Anything that can hit buildings
|
||||
state.has_any((
|
||||
# Infantry
|
||||
item_names.MARINE,
|
||||
item_names.FIREBAT,
|
||||
item_names.MARAUDER,
|
||||
item_names.REAPER,
|
||||
item_names.HERC,
|
||||
item_names.DOMINION_TROOPER,
|
||||
item_names.GHOST,
|
||||
item_names.SPECTRE,
|
||||
# Vehicles
|
||||
item_names.HELLION,
|
||||
item_names.VULTURE,
|
||||
item_names.SIEGE_TANK,
|
||||
item_names.WARHOUND,
|
||||
item_names.GOLIATH,
|
||||
item_names.DIAMONDBACK,
|
||||
item_names.THOR,
|
||||
item_names.PREDATOR,
|
||||
item_names.CYCLONE,
|
||||
# Ships
|
||||
item_names.WRAITH,
|
||||
item_names.VIKING,
|
||||
item_names.BANSHEE,
|
||||
item_names.RAVEN,
|
||||
item_names.BATTLECRUISER,
|
||||
# RG
|
||||
item_names.SON_OF_KORHAL,
|
||||
item_names.AEGIS_GUARD,
|
||||
item_names.EMPERORS_SHADOW,
|
||||
item_names.BULWARK_COMPANY,
|
||||
item_names.SHOCK_DIVISION,
|
||||
item_names.BLACKHAMMER,
|
||||
item_names.SKY_FURY,
|
||||
item_names.NIGHT_WOLF,
|
||||
item_names.NIGHT_HAWK,
|
||||
item_names.PRIDE_OF_AUGUSTRGRAD,
|
||||
), self.player)
|
||||
or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player)
|
||||
or state.has_all((item_names.EMPERORS_GUARDIAN, item_names.LIBERATOR_RAID_ARTILLERY), self.player)
|
||||
or state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player)
|
||||
or state.has_all((item_names.WIDOW_MINE, item_names.WIDOW_MINE_DEMOLITION_PAYLOAD), self.player)
|
||||
or (
|
||||
state.has_any((
|
||||
# Infantry
|
||||
item_names.MARINE,
|
||||
item_names.FIREBAT,
|
||||
item_names.MARAUDER,
|
||||
item_names.REAPER,
|
||||
item_names.HERC,
|
||||
item_names.DOMINION_TROOPER,
|
||||
item_names.GHOST,
|
||||
item_names.SPECTRE,
|
||||
# Vehicles
|
||||
item_names.HELLION,
|
||||
item_names.VULTURE,
|
||||
item_names.SIEGE_TANK,
|
||||
item_names.WARHOUND,
|
||||
item_names.GOLIATH,
|
||||
item_names.DIAMONDBACK,
|
||||
item_names.THOR,
|
||||
item_names.PREDATOR,
|
||||
item_names.CYCLONE,
|
||||
# Ships
|
||||
item_names.WRAITH,
|
||||
item_names.VIKING,
|
||||
item_names.BANSHEE,
|
||||
item_names.RAVEN,
|
||||
item_names.BATTLECRUISER,
|
||||
# RG
|
||||
item_names.SON_OF_KORHAL,
|
||||
item_names.AEGIS_GUARD,
|
||||
item_names.EMPERORS_SHADOW,
|
||||
item_names.BULWARK_COMPANY,
|
||||
item_names.SHOCK_DIVISION,
|
||||
item_names.BLACKHAMMER,
|
||||
item_names.SKY_FURY,
|
||||
item_names.NIGHT_WOLF,
|
||||
item_names.NIGHT_HAWK,
|
||||
item_names.PRIDE_OF_AUGUSTRGRAD,
|
||||
# Mercs with shortest initial cooldown (300s)
|
||||
item_names.WAR_PIGS,
|
||||
item_names.DEATH_HEADS,
|
||||
item_names.HELS_ANGELS,
|
||||
item_names.WINGED_NIGHTMARES,
|
||||
), self.player)
|
||||
or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player)
|
||||
or state.has_all((item_names.EMPERORS_GUARDIAN, item_names.LIBERATOR_RAID_ARTILLERY), self.player)
|
||||
or state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player)
|
||||
or state.has_all((item_names.WIDOW_MINE, item_names.WIDOW_MINE_DEMOLITION_PAYLOAD), self.player)
|
||||
or (
|
||||
state.has_any((
|
||||
# Mercs with shortest initial cooldown (300s)
|
||||
item_names.WAR_PIGS,
|
||||
item_names.DEATH_HEADS,
|
||||
item_names.HELS_ANGELS,
|
||||
item_names.WINGED_NIGHTMARES,
|
||||
), self.player)
|
||||
# + 2 upgrades that allow getting faster/earlier mercs
|
||||
and state.count_from_list((
|
||||
item_names.RAPID_REINFORCEMENT,
|
||||
item_names.PROGRESSIVE_FAST_DELIVERY,
|
||||
item_names.ROGUE_FORCES,
|
||||
# item_names.SIGNAL_BEACON, # Probably doesn't help too much on the first unit
|
||||
), self.player) >= 2
|
||||
)
|
||||
# + 2 upgrades that allow getting faster/earlier mercs
|
||||
and state.count_from_list((
|
||||
item_names.RAPID_REINFORCEMENT,
|
||||
item_names.PROGRESSIVE_FAST_DELIVERY,
|
||||
item_names.ROGUE_FORCES,
|
||||
# item_names.SIGNAL_BEACON, # Probably doesn't help too much on the first unit
|
||||
), self.player) >= 2
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3460,10 +3439,6 @@ class SC2Logic:
|
||||
)
|
||||
return (
|
||||
num_units >= target
|
||||
and (
|
||||
target < 5
|
||||
or self.zerg_any_anti_air(state)
|
||||
)
|
||||
and (
|
||||
# Anything that can hit buildings
|
||||
state.has_any((
|
||||
@@ -3481,6 +3456,11 @@ class SC2Logic:
|
||||
item_names.INFESTED_DIAMONDBACK,
|
||||
item_names.INFESTED_SIEGE_TANK,
|
||||
item_names.INFESTED_BANSHEE,
|
||||
# Mercs with <= 300s first drop time
|
||||
item_names.DEVOURING_ONES,
|
||||
item_names.HUNTER_KILLERS,
|
||||
item_names.CAUSTIC_HORRORS,
|
||||
item_names.HUNTERLING,
|
||||
), self.player)
|
||||
or state.has_all((item_names.INFESTOR, item_names.INFESTOR_INFESTED_TERRAN), self.player)
|
||||
or self.morph_baneling(state)
|
||||
@@ -3520,9 +3500,6 @@ class SC2Logic:
|
||||
return (
|
||||
state.count_from_list_unique(item_groups.protoss_units + item_groups.protoss_buildings + [item_names.NEXUS_OVERCHARGE], self.player)
|
||||
>= target
|
||||
) and (
|
||||
target < 5
|
||||
or self.protoss_any_anti_air_unit(state)
|
||||
) and (
|
||||
# Anything that can hit buildings
|
||||
state.has_any((
|
||||
|
||||
@@ -8,9 +8,8 @@ from worlds import AutoWorld
|
||||
from test.general import gen_steps, call_all
|
||||
|
||||
from test.bases import WorldTestBase
|
||||
from .. import SC2World, SC2Campaign
|
||||
from .. import SC2World
|
||||
from .. import client
|
||||
from .. import options
|
||||
|
||||
class Sc2TestBase(WorldTestBase):
|
||||
game = client.SC2Context.game
|
||||
@@ -25,18 +24,6 @@ class Sc2SetupTestBase(unittest.TestCase):
|
||||
This allows potentially generating multiple worlds in one test case, useful for tracking down a rare / sporadic
|
||||
crash.
|
||||
"""
|
||||
ALL_CAMPAIGNS = {
|
||||
'enabled_campaigns': options.EnabledCampaigns.valid_keys,
|
||||
}
|
||||
TERRAN_CAMPAIGNS = {
|
||||
'enabled_campaigns': {SC2Campaign.WOL.campaign_name, SC2Campaign.NCO.campaign_name,}
|
||||
}
|
||||
ZERG_CAMPAIGNS = {
|
||||
'enabled_campaigns': {SC2Campaign.HOTS.campaign_name,}
|
||||
}
|
||||
PROTOSS_CAMPAIGNS = {
|
||||
'enabled_campaigns': {SC2Campaign.PROPHECY.campaign_name, SC2Campaign.PROLOGUE.campaign_name, SC2Campaign.LOTV.campaign_name,}
|
||||
}
|
||||
seed: Optional[int] = None
|
||||
game = SC2World.game
|
||||
player = 1
|
||||
|
||||
@@ -6,12 +6,10 @@ from .test_base import Sc2SetupTestBase
|
||||
from .. import MissionFlag
|
||||
from ..item import item_tables, item_names
|
||||
from BaseClasses import ItemClassification
|
||||
from .. import options
|
||||
|
||||
class TestCustomMissionOrders(Sc2SetupTestBase):
|
||||
def test_mini_wol_generates(self):
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': 'custom',
|
||||
'custom_mission_order': {
|
||||
'Mini Wings of Liberty': {
|
||||
@@ -139,7 +137,6 @@ class TestCustomMissionOrders(Sc2SetupTestBase):
|
||||
test_item = item_names.ZERGLING_METABOLIC_BOOST
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'enabled_campaigns': set(options.EnabledCampaigns.valid_keys),
|
||||
'start_inventory': { test_item: 1 },
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
@@ -167,7 +164,6 @@ class TestCustomMissionOrders(Sc2SetupTestBase):
|
||||
test_item = item_names.ZERGLING_METABOLIC_BOOST
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'enabled_campaigns': set(options.EnabledCampaigns.valid_keys),
|
||||
'start_inventory': { test_item: 1 },
|
||||
'locked_items': { test_item: 1 },
|
||||
'custom_mission_order': {
|
||||
@@ -196,7 +192,6 @@ class TestCustomMissionOrders(Sc2SetupTestBase):
|
||||
test_item = item_names.ZERGLING
|
||||
test_amount = 3
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': 'custom',
|
||||
'locked_items': { test_item: 1 }, # Make sure it is generated as normal
|
||||
'custom_mission_order': {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Unit tests for world generation
|
||||
"""
|
||||
from typing import *
|
||||
|
||||
from .test_base import Sc2SetupTestBase
|
||||
|
||||
from .. import mission_groups, mission_tables, options, locations, SC2Mission, SC2Campaign, SC2Race, unreleased_items, \
|
||||
@@ -16,7 +15,6 @@ from ..options import EnabledCampaigns, NovaGhostOfAChanceVariant, MissionOrder,
|
||||
class TestItemFiltering(Sc2SetupTestBase):
|
||||
def test_explicit_locks_excludes_interact_and_set_flags(self):
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'locked_items': {
|
||||
item_names.MARINE: 0,
|
||||
item_names.MARAUDER: 0,
|
||||
@@ -117,8 +115,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_mission_groups_excludes_all_missions_in_group(self):
|
||||
world_options = {
|
||||
**self.ZERG_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'excluded_missions': [
|
||||
mission_groups.MissionGroupNames.HOTS_ZERUS_MISSIONS,
|
||||
],
|
||||
@@ -161,8 +157,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'excluded_missions': [
|
||||
@@ -178,8 +172,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_all_terran_build_missions_excludes_all_terran_units(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'excluded_missions': [
|
||||
@@ -198,8 +190,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_all_zerg_and_kerrigan_missions_excludes_all_zerg_items(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'excluded_missions': [
|
||||
@@ -215,8 +205,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_all_zerg_build_missions_excludes_zerg_units(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'excluded_missions': [
|
||||
@@ -236,8 +224,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_all_protoss_missions_excludes_all_protoss_items(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'accessibility': 'locations',
|
||||
@@ -255,8 +241,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_excluding_all_protoss_build_missions_excludes_protoss_units(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'accessibility': 'locations',
|
||||
@@ -302,7 +286,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_vanilla_items_only_includes_only_nova_equipment_and_vanilla_and_filler_items(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
# Avoid options that lock non-vanilla items for logic
|
||||
@@ -532,7 +515,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_nco_and_wol_picks_correct_starting_mission(self):
|
||||
world_options = {
|
||||
'mission_order': MissionOrder.option_vanilla,
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
SC2Campaign.NCO.campaign_name
|
||||
@@ -547,7 +529,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
mission_tables.SC2Mission.ZERO_HOUR.mission_name.split(" (")[0]
|
||||
],
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'selected_races': options.SelectedRaces.valid_keys,
|
||||
'selected_races': options.SelectRaces.valid_keys,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
@@ -566,7 +548,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
mission_tables.SC2Mission.ZERO_HOUR.mission_name
|
||||
],
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'selected_races': options.SelectedRaces.valid_keys,
|
||||
'selected_races': options.SelectRaces.valid_keys,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
@@ -774,7 +756,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_kerrigan_levels_per_mission_triggering_pre_fill(self):
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
# Vanilla WoL with all missions
|
||||
'mission_order': options.MissionOrder.option_custom,
|
||||
'custom_mission_order': {
|
||||
'campaign': {
|
||||
@@ -815,7 +797,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_kerrigan_levels_per_mission_and_generic_upgrades_both_triggering_pre_fill(self):
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
# Vanilla WoL with all missions
|
||||
'mission_order': options.MissionOrder.option_custom,
|
||||
'custom_mission_order': {
|
||||
'campaign': {
|
||||
@@ -860,9 +842,10 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
self.assertNotIn(item_names.KERRIGAN_LEVELS_70, itempool)
|
||||
self.assertNotIn(item_names.KERRIGAN_LEVELS_70, starting_inventory)
|
||||
|
||||
|
||||
|
||||
def test_locking_required_items(self):
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_custom,
|
||||
'custom_mission_order': {
|
||||
'campaign': {
|
||||
@@ -908,7 +891,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': campaign_size,
|
||||
'enabled_campaigns': EnabledCampaigns.valid_keys,
|
||||
'selected_races': options.SelectedRaces.valid_keys,
|
||||
'selected_races': options.SelectRaces.valid_keys,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'mission_race_balancing': options.EnableMissionRaceBalancing.option_fully_balanced,
|
||||
}
|
||||
@@ -940,7 +923,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
},
|
||||
'max_number_of_upgrades': 2,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
},
|
||||
@@ -977,7 +959,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
},
|
||||
'max_number_of_upgrades': 2,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.ZERG.get_title(),
|
||||
@@ -1008,7 +989,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
},
|
||||
'max_upgrade_level': MAX_LEVEL,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'generic_upgrade_items': options.GenericUpgradeItems.option_bundle_weapon_and_armor
|
||||
}
|
||||
@@ -1035,13 +1015,12 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_ghost_of_a_chance_generates_without_nco(self) -> None:
|
||||
world_options = {
|
||||
**self.TERRAN_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_custom,
|
||||
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_auto,
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 1,
|
||||
'size': 1, # Give the generator some space to place the key
|
||||
'mission_pool': [
|
||||
SC2Mission.GHOST_OF_A_CHANCE.mission_name
|
||||
]
|
||||
@@ -1057,13 +1036,12 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_ghost_of_a_chance_generates_using_nco_nova(self) -> None:
|
||||
world_options = {
|
||||
**self.TERRAN_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_custom,
|
||||
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_nco,
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 2,
|
||||
'size': 2, # Give the generator some space to place the key
|
||||
'mission_pool': [
|
||||
SC2Mission.LIBERATION_DAY.mission_name, # Starter mission
|
||||
SC2Mission.GHOST_OF_A_CHANCE.mission_name,
|
||||
@@ -1079,13 +1057,12 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_ghost_of_a_chance_generates_with_nco(self) -> None:
|
||||
world_options = {
|
||||
**self.TERRAN_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_custom,
|
||||
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_auto,
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 3,
|
||||
'size': 3, # Give the generator some space to place the key
|
||||
'mission_pool': [
|
||||
SC2Mission.LIBERATION_DAY.mission_name, # Starter mission
|
||||
SC2Mission.GHOST_OF_A_CHANCE.mission_name,
|
||||
@@ -1102,9 +1079,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_exclude_overpowered_items(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'selected_races': [SC2Race.TERRAN.get_title()],
|
||||
@@ -1120,9 +1095,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_exclude_overpowered_items_not_excluded(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'selected_races': [SC2Race.TERRAN.get_title()],
|
||||
@@ -1138,9 +1111,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_exclude_overpowered_items_vanilla_only(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||
'vanilla_items_only': VanillaItemsOnly.option_true,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
@@ -1157,9 +1128,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
def test_exclude_locked_overpowered_items(self) -> None:
|
||||
locked_item = item_names.BATTLECRUISER_ATX_LASER_BATTERY
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||
'locked_items': [locked_item],
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
@@ -1177,9 +1146,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
Checks if all unreleased items are marked properly not to generate
|
||||
"""
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
}
|
||||
@@ -1197,9 +1164,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
Locking overrides this behavior - if they're locked, they must appear
|
||||
"""
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'locked_items': {item_name: 0 for item_name in unreleased_items},
|
||||
@@ -1214,12 +1179,10 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_merc_excluded_excludes_merc_upgrades(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'excluded_items': [item_name for item_name in item_groups.terran_mercenaries],
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'selected_races': [SC2Race.TERRAN.get_title()],
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
@@ -1229,7 +1192,6 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_unexcluded_items_applies_over_op_items(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||
@@ -1253,13 +1215,11 @@ class TestItemFiltering(Sc2SetupTestBase):
|
||||
|
||||
def test_exclude_overpowered_items_and_not_allow_unit_nerfs(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': MissionOrder.option_grid,
|
||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||
'war_council_nerfs': options.WarCouncilNerfs.option_false,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'selected_races': [SC2Race.PROTOSS.get_title()],
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
|
||||
@@ -15,7 +15,6 @@ class ItemFilterTests(Sc2SetupTestBase):
|
||||
},
|
||||
'required_tactics': 'standard',
|
||||
'min_number_of_upgrades': 1,
|
||||
**self.TERRAN_CAMPAIGNS,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title()
|
||||
},
|
||||
@@ -55,7 +54,6 @@ class ItemFilterTests(Sc2SetupTestBase):
|
||||
},
|
||||
'min_number_of_upgrades': 2,
|
||||
'required_tactics': 'standard',
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'selected_races': {
|
||||
SC2Race.PROTOSS.get_title()
|
||||
},
|
||||
@@ -77,7 +75,6 @@ class ItemFilterTests(Sc2SetupTestBase):
|
||||
},
|
||||
'min_number_of_upgrades': 2,
|
||||
'required_tactics': 'standard',
|
||||
**self.PROTOSS_CAMPAIGNS,
|
||||
'selected_races': {
|
||||
SC2Race.PROTOSS.get_title()
|
||||
},
|
||||
|
||||
@@ -116,7 +116,6 @@ class TestRules(unittest.TestCase):
|
||||
test_world.options.take_over_ai_allies.value = take_over_ai_allies
|
||||
test_world.options.kerrigan_presence.value = kerrigan_presence
|
||||
test_world.options.spear_of_adun_passive_ability_presence.value = spear_of_adun_passive_presence
|
||||
test_world.options.enabled_campaigns.value = set(options.EnabledCampaigns.valid_keys)
|
||||
test_world.logic = SC2Logic(test_world) # type: ignore
|
||||
return test_world
|
||||
|
||||
|
||||
@@ -6,14 +6,7 @@ from .test_base import Sc2SetupTestBase
|
||||
from .. import get_all_missions, mission_tables, options
|
||||
from ..item import item_groups, item_tables, item_names
|
||||
from ..mission_tables import SC2Race, SC2Mission, SC2Campaign, MissionFlag
|
||||
from ..options import (
|
||||
EnabledCampaigns, MasteryLocations, MissionOrder, EnableRaceSwapVariants, ShuffleCampaigns,
|
||||
ShuffleNoBuild, StarterUnit, RequiredTactics, KerriganPresence, KerriganLevelItemDistribution, GrantStoryTech,
|
||||
GrantStoryLevels, BasebustLocations, ChallengeLocations, DifficultyCurve, EnableMorphling, ExcludeOverpoweredItems,
|
||||
ExcludeVeryHardMissions, ExtraLocations, GenericUpgradeItems, GenericUpgradeResearch, GenericUpgradeResearchSpeedup,
|
||||
KerriganPrimalStatus, KeyMode, MissionOrderScouting, EnableMissionRaceBalancing,
|
||||
NovaGhostOfAChanceVariant, PreventativeLocations, SpeedrunLocations, TakeOverAIAllies, VanillaItemsOnly
|
||||
)
|
||||
from ..options import EnabledCampaigns, MasteryLocations
|
||||
|
||||
|
||||
class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
@@ -277,7 +270,7 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
|
||||
def test_race_swap_pick_one_has_correct_length_and_includes_swaps(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': options.SelectedRaces.valid_keys,
|
||||
'selected_races': options.SelectRaces.valid_keys,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_pick_one,
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
@@ -348,7 +341,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_kerrigan_max_active_abilities(self):
|
||||
target_number: int = 8
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
@@ -367,7 +359,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_kerrigan_max_passive_abilities(self):
|
||||
target_number: int = 3
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
@@ -386,7 +377,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_spear_of_adun_max_active_abilities(self):
|
||||
target_number: int = 8
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
@@ -406,7 +396,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_spear_of_adun_max_autocasts(self):
|
||||
target_number: int = 2
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
@@ -426,7 +415,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_nova_max_weapons(self):
|
||||
target_number: int = 3
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
@@ -446,7 +434,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_nova_max_gadgets(self):
|
||||
target_number: int = 3
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
@@ -464,7 +451,6 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
|
||||
def test_mercs_only(self) -> None:
|
||||
world_options = {
|
||||
**self.ALL_CAMPAIGNS,
|
||||
'selected_races': [
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.ZERG.get_title(),
|
||||
@@ -504,102 +490,3 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
|
||||
self.assertTupleEqual(terran_nonmerc_units, ())
|
||||
self.assertTupleEqual(zerg_nonmerc_units, ())
|
||||
|
||||
def test_zerg_hots_no_terran_items(self) -> None:
|
||||
# The actual situation the bug got caught
|
||||
world_options = {
|
||||
'basebust_locations': BasebustLocations.option_enabled,
|
||||
'challenge_locations': ChallengeLocations.option_enabled,
|
||||
'difficulty_curve': DifficultyCurve.option_standard,
|
||||
'enable_morphling': EnableMorphling.option_false,
|
||||
'enable_race_swap': EnableRaceSwapVariants.option_disabled,
|
||||
'enabled_campaigns': [SC2Campaign.HOTS.campaign_name],
|
||||
'ensure_generic_items': 25,
|
||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
|
||||
'exclude_very_hard_missions': ExcludeVeryHardMissions.option_default,
|
||||
'excluded_missions': [
|
||||
SC2Mission.SUPREME.mission_name
|
||||
],
|
||||
'extra_locations': ExtraLocations.option_enabled,
|
||||
'generic_upgrade_items': GenericUpgradeItems.option_individual_items,
|
||||
'generic_upgrade_missions': 0,
|
||||
'generic_upgrade_research': GenericUpgradeResearch.option_auto_in_no_build,
|
||||
'generic_upgrade_research_speedup': GenericUpgradeResearchSpeedup.option_false,
|
||||
'grant_story_levels': GrantStoryLevels.option_disabled,
|
||||
'grant_story_tech': GrantStoryTech.option_no_grant,
|
||||
'kerrigan_level_item_distribution': KerriganLevelItemDistribution.option_size_14,
|
||||
'kerrigan_level_item_sum': 86,
|
||||
'kerrigan_levels_per_mission_completed': 0,
|
||||
'kerrigan_levels_per_mission_completed_cap': -1,
|
||||
'kerrigan_max_active_abilities': 12,
|
||||
'kerrigan_max_passive_abilities': 5,
|
||||
'kerrigan_presence': KerriganPresence.option_vanilla,
|
||||
'kerrigan_primal_status': KerriganPrimalStatus.option_vanilla,
|
||||
'kerrigan_total_level_cap': -1,
|
||||
'key_mode': KeyMode.option_progressive_questlines,
|
||||
'mastery_locations': MasteryLocations.option_disabled,
|
||||
'max_number_of_upgrades': -1,
|
||||
'max_upgrade_level': 4,
|
||||
'maximum_campaign_size': 40,
|
||||
'min_number_of_upgrades': 2,
|
||||
'mission_order': MissionOrder.option_mini_campaign,
|
||||
'mission_order_scouting': MissionOrderScouting.option_none,
|
||||
'mission_race_balancing': EnableMissionRaceBalancing.option_semi_balanced,
|
||||
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_wol,
|
||||
'preventative_locations': PreventativeLocations.option_enabled,
|
||||
'required_tactics': RequiredTactics.option_standard,
|
||||
'shuffle_campaigns': ShuffleCampaigns.option_true,
|
||||
'shuffle_no_build': ShuffleNoBuild.option_true,
|
||||
'speedrun_locations': SpeedrunLocations.option_disabled,
|
||||
'start_primary_abilities': 0,
|
||||
'starter_unit': StarterUnit.option_balanced,
|
||||
'starting_supply_per_item': 2,
|
||||
'take_over_ai_allies': TakeOverAIAllies.option_false,
|
||||
'vanilla_items_only': VanillaItemsOnly.option_false,
|
||||
'victory_cache': 0,
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
|
||||
self.assertNotIn(item_names.COMMAND_CENTER_SCANNER_SWEEP, world_item_names)
|
||||
self.assertNotIn(item_names.COMMAND_CENTER_EXTRA_SUPPLIES, world_item_names)
|
||||
self.assertNotIn(item_names.ULTRA_CAPACITORS, world_item_names)
|
||||
self.assertNotIn(item_names.ORBITAL_DEPOTS, world_item_names)
|
||||
self.assertNotIn(item_names.DOMINION_TROOPER, world_item_names)
|
||||
|
||||
def test_all_kerrigan_missions_are_nobuild_and_grant_story_tech_is_on(self) -> None:
|
||||
# The actual situation the bug got caught
|
||||
world_options = {
|
||||
'mission_order': MissionOrder.option_vanilla_shuffled,
|
||||
'selected_races': [
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.ZERG.get_title(),
|
||||
SC2Race.PROTOSS.get_title(),
|
||||
],
|
||||
'enabled_campaigns': [
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
SC2Campaign.PROPHECY.campaign_name,
|
||||
SC2Campaign.HOTS.campaign_name,
|
||||
SC2Campaign.PROLOGUE.campaign_name,
|
||||
SC2Campaign.LOTV.campaign_name,
|
||||
SC2Campaign.EPILOGUE.campaign_name,
|
||||
SC2Campaign.NCO.campaign_name,
|
||||
],
|
||||
'enable_race_swap': EnableRaceSwapVariants.option_shuffle_all_non_vanilla, # Causes no build Kerrigan missions to be present, only nobuilds remain
|
||||
'shuffle_campaigns': ShuffleCampaigns.option_true,
|
||||
'shuffle_no_build': ShuffleNoBuild.option_true,
|
||||
'starter_unit': StarterUnit.option_balanced,
|
||||
'required_tactics': RequiredTactics.option_standard,
|
||||
'kerrigan_presence': KerriganPresence.option_vanilla,
|
||||
'kerrigan_levels_per_mission_completed': 0,
|
||||
'kerrigan_levels_per_mission_completed_cap': -1,
|
||||
'kerrigan_level_item_sum': 87,
|
||||
'kerrigan_level_item_distribution': KerriganLevelItemDistribution.option_size_7,
|
||||
'kerrigan_total_level_cap': -1,
|
||||
'start_primary_abilities': 0,
|
||||
'grant_story_tech': GrantStoryTech.option_grant,
|
||||
'grant_story_levels': GrantStoryLevels.option_additive,
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
# Just check that the world itself generates under those rules and no exception is thrown
|
||||
|
||||
@@ -206,7 +206,7 @@ def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: S
|
||||
set_entrance_rule(multiworld, player, Entrance.enter_skull_cavern, logic.received(Wallet.skull_key))
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.talk_to_mines_dwarf,
|
||||
logic.wallet.can_speak_dwarf() & logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron))
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.buy_from_traveling_merchant, logic.traveling_merchant.has_days() & logic.money.can_spend(1200))
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.buy_from_traveling_merchant, logic.traveling_merchant.has_days() & logic.money.can_spend(1000))
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.buy_from_raccoon, logic.quest.has_raccoon_shop())
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.fish_in_waterfall,
|
||||
logic.skill.has_level(Skill.fishing, 5) & logic.tool.has_fishing_rod(2))
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import SeasonRandomization, EntranceRandomization
|
||||
from ...locations import location_table, LocationTags
|
||||
|
||||
|
||||
class TestTravelingMerchant(SVTestBase):
|
||||
options = {
|
||||
SeasonRandomization: SeasonRandomization.option_randomized_not_winter,
|
||||
EntranceRandomization: EntranceRandomization.option_disabled,
|
||||
}
|
||||
|
||||
def test_purchase_from_traveling_merchant_requires_money(self):
|
||||
traveling_merchant_location_names = [l for l in self.get_real_location_names() if LocationTags.TRAVELING_MERCHANT in location_table[l].tags]
|
||||
|
||||
@@ -174,7 +174,7 @@ item_table: Dict[str, ItemData] = {
|
||||
'Corruption': ItemData('Orb Spell', 1337161),
|
||||
'Lightwall': ItemData('Orb Spell', 1337162, progression=True),
|
||||
'Bleak Ring': ItemData('Orb Passive', 1337163, useful=True),
|
||||
'Scythe Ring': ItemData('Orb Passive', 1337164, useful=True),
|
||||
'Scythe Ring': ItemData('Orb Passive', 1337164),
|
||||
'Pyro Ring': ItemData('Orb Passive', 1337165, progression=True),
|
||||
'Royal Ring': ItemData('Orb Passive', 1337166, progression=True),
|
||||
'Shield Ring': ItemData('Orb Passive', 1337167),
|
||||
@@ -208,11 +208,9 @@ item_table: Dict[str, ItemData] = {
|
||||
'Lab Access Research': ItemData('Lab Access', 1337196, progression=True),
|
||||
'Lab Access Dynamo': ItemData('Lab Access', 1337197, progression=True),
|
||||
'Drawbridge Key': ItemData('Key', 1337198, progression=True),
|
||||
'Cube of Bodie': ItemData('Relic', 1337199, progression=True),
|
||||
# 1337199 Reserved
|
||||
'Spider Trap': ItemData('Trap', 1337200, 0, trap=True),
|
||||
'Lights Out Trap': ItemData('Trap', 1337201, 0, trap=True),
|
||||
'Palm Punch Trap': ItemData('Trap', 1337202, 0, trap=True),
|
||||
# 1337203 - 1337248 Reserved
|
||||
# 1337201 - 1337248 Reserved
|
||||
'Max Sand': ItemData('Stat', 1337249, 14)
|
||||
}
|
||||
|
||||
|
||||
+211
-779
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@ class TimespinnerLogic:
|
||||
self.flag_eye_spy = bool(options and options.eye_spy)
|
||||
self.flag_unchained_keys = bool(options and options.unchained_keys)
|
||||
self.flag_prism_break = bool(options and options.prism_break)
|
||||
self.flag_find_the_flame = bool(options and options.find_the_flame)
|
||||
|
||||
if precalculated_weights:
|
||||
if self.flag_unchained_keys:
|
||||
@@ -94,12 +93,6 @@ class TimespinnerLogic:
|
||||
else:
|
||||
return True
|
||||
|
||||
def can_break_lanterns(self, state: CollectionState) -> bool:
|
||||
if self.flag_find_the_flame:
|
||||
return state.has('Cube of Bodie', self.player)
|
||||
else:
|
||||
return True
|
||||
|
||||
def can_kill_all_3_bosses(self, state: CollectionState) -> bool:
|
||||
if self.flag_prism_break:
|
||||
return state.has_all({'Laser Access M', 'Laser Access I', 'Laser Access A'}, self.player)
|
||||
|
||||
@@ -53,75 +53,6 @@ class BossRando(Choice):
|
||||
option_unscaled = 2
|
||||
alias_true = 1
|
||||
|
||||
class BossRandoType(Choice):
|
||||
"""
|
||||
Sets what type of boss shuffling occurs.
|
||||
Shuffle: Bosses will be shuffled amongst each other
|
||||
Chaos: Bosses will be randomized with the chance of duplicate bosses
|
||||
Singularity: All bosses will be replaced with a single boss
|
||||
Manual: Bosses will be placed according to the Boss Rando Overrides setting
|
||||
"""
|
||||
display_name = "Boss Randomization Type"
|
||||
option_shuffle = 0
|
||||
option_chaos = 1
|
||||
option_singularity = 2
|
||||
option_manual = 3
|
||||
|
||||
class BossRandoOverrides(OptionDict):
|
||||
"""
|
||||
Manual mapping of bosses to the boss they will be replaced with.
|
||||
Bosses that you don't specify will be the vanilla boss.
|
||||
"""
|
||||
bosses = [
|
||||
"FelineSentry",
|
||||
"Varndagroth",
|
||||
"AzureQueen",
|
||||
"GoldenIdol",
|
||||
"Aelana",
|
||||
"Maw",
|
||||
"Cantoran",
|
||||
"Genza",
|
||||
"Nuvius",
|
||||
"Vol",
|
||||
"Prince",
|
||||
"Xarion",
|
||||
"Ravenlord",
|
||||
"Ifrit",
|
||||
"Sandman",
|
||||
"Nightmare",
|
||||
]
|
||||
|
||||
schema = Schema(
|
||||
{
|
||||
Optional(Or(*bosses)): Or(
|
||||
And(
|
||||
{Optional(boss): And(int, lambda n: n >= 0) for boss in bosses},
|
||||
lambda d: any(v > 0 for v in d.values()),
|
||||
),
|
||||
*bosses
|
||||
)
|
||||
}
|
||||
)
|
||||
display_name = "Boss Rando Overrides"
|
||||
default = {
|
||||
"FelineSentry": "FelineSentry",
|
||||
"Varndagroth": "Varndagroth",
|
||||
"AzureQueen": "AzureQueen",
|
||||
"GoldenIdol": "GoldenIdol",
|
||||
"Aelana": "Aelana",
|
||||
"Maw": "Maw",
|
||||
"Cantoran": "Cantoran",
|
||||
"Genza": "Genza",
|
||||
"Nuvius": "Nuvius",
|
||||
"Vol": "Vol",
|
||||
"Prince": "Prince",
|
||||
"Xarion": "Xarion",
|
||||
"Ravenlord": "Ravenlord",
|
||||
"Ifrit": "Ifrit",
|
||||
"Sandman": "Sandman",
|
||||
"Nightmare": "Nightmare"
|
||||
}
|
||||
|
||||
class EnemyRando(Choice):
|
||||
"Wheter enemies will be randomized, and if their damage/hp should be scaled."
|
||||
display_name = "Enemy Randomization"
|
||||
@@ -436,8 +367,8 @@ class TrapChance(Range):
|
||||
class Traps(OptionList):
|
||||
"""List of traps that may be in the item pool to find"""
|
||||
display_name = "Traps Types"
|
||||
valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap", "Spider Trap", "Lights Out Trap", "Palm Punch Trap" }
|
||||
default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap", "Spider Trap", "Lights Out Trap", "Palm Punch Trap" ]
|
||||
valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap", "Spider Trap" }
|
||||
default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap", "Spider Trap" ]
|
||||
|
||||
class PresentAccessWithWheelAndSpindle(Toggle):
|
||||
"""When inverted, allows using the refugee camp warp when both the Timespinner Wheel and Spindle is acquired."""
|
||||
@@ -468,14 +399,6 @@ class RoyalRoadblock(Toggle):
|
||||
"""The Royal Towers entrance door requires a royal orb (Plasma Orb, Plasma Geyser, or Royal Ring) to enter."""
|
||||
display_name = "Royal Roadblock"
|
||||
|
||||
class PureTorcher(Toggle):
|
||||
"""All lanterns contain checks. (Except tutorial)"""
|
||||
display_name = "Pure Torcher"
|
||||
|
||||
class FindTheFlame(Toggle):
|
||||
"""Lanterns in 'Pure Torcher' will not break without new item 'Cube of Bodie'."""
|
||||
display_name = "Find the Flame"
|
||||
|
||||
@dataclass
|
||||
class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
|
||||
start_with_jewelry_box: StartWithJewelryBox
|
||||
@@ -489,8 +412,6 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
|
||||
cantoran: Cantoran
|
||||
lore_checks: LoreChecks
|
||||
boss_rando: BossRando
|
||||
boss_rando_type: BossRandoType
|
||||
boss_rando_overrides: BossRandoOverrides
|
||||
enemy_rando: EnemyRando
|
||||
damage_rando: DamageRando
|
||||
damage_rando_overrides: DamageRandoOverrides
|
||||
@@ -520,8 +441,6 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
|
||||
pyramid_start: PyramidStart
|
||||
gate_keep: GateKeep
|
||||
royal_roadblock: RoyalRoadblock
|
||||
pure_torcher: PureTorcher
|
||||
find_the_flame: FindTheFlame
|
||||
trap_chance: TrapChance
|
||||
traps: Traps
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ class PreCalculatedWeights:
|
||||
flood_lake_serene_bridge: bool
|
||||
flood_lab: bool
|
||||
|
||||
boss_rando_overrides: Dict[str, str]
|
||||
|
||||
def __init__(self, options: TimespinnerOptions, random: Random):
|
||||
if options.rising_tides:
|
||||
weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(options)
|
||||
@@ -53,26 +51,6 @@ class PreCalculatedWeights:
|
||||
self.flood_lake_serene_bridge = False
|
||||
self.flood_lab = False
|
||||
|
||||
boss_rando_weights_overrides: Dict[str, Union[str, Dict[str, int]]] = self.get_boss_rando_weights_overrides(options)
|
||||
self.boss_rando_overrides = {
|
||||
"FelineSentry": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "FelineSentry"),
|
||||
"Varndagroth": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Varndagroth"),
|
||||
"AzureQueen": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "AzureQueen"),
|
||||
"GoldenIdol": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "GoldenIdol"),
|
||||
"Aelana": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Aelana"),
|
||||
"Maw": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Maw"),
|
||||
"Cantoran": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Cantoran"),
|
||||
"Genza": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Genza"),
|
||||
"Nuvius": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Nuvius"),
|
||||
"Vol": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Vol"),
|
||||
"Prince": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Prince"),
|
||||
"Xarion": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Xarion"),
|
||||
"Ravenlord": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Ravenlord"),
|
||||
"Ifrit": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Ifrit"),
|
||||
"Sandman": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Sandman"),
|
||||
"Nightmare": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Nightmare")
|
||||
}
|
||||
|
||||
self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \
|
||||
self.get_pyramid_keys_unlocks(options, random, self.flood_maw, self.flood_xarion, self.flood_lab)
|
||||
|
||||
@@ -164,32 +142,3 @@ class PreCalculatedWeights:
|
||||
return True, True
|
||||
elif result == "FloodedWithSavePointAvailable":
|
||||
return True, False
|
||||
|
||||
@staticmethod
|
||||
def get_boss_rando_weights_overrides(options: TimespinnerOptions) -> Dict[str, Union[str, Dict[str, int]]]:
|
||||
weights_overrides_option: Union[int, Dict[str, Union[str, Dict[str, int]]]] = \
|
||||
options.boss_rando_overrides.value
|
||||
|
||||
default_weights: Dict[str, Dict[str, int]] = options.boss_rando_overrides.default
|
||||
|
||||
if not weights_overrides_option:
|
||||
weights_overrides_option = default_weights
|
||||
else:
|
||||
for key, weights in default_weights.items():
|
||||
if not key in weights_overrides_option:
|
||||
weights_overrides_option[key] = weights
|
||||
|
||||
return weights_overrides_option
|
||||
|
||||
@staticmethod
|
||||
def roll_boss_rando_setting(random: Random, all_weights: Dict[str, Union[Dict[str, int], str]],
|
||||
key: str) -> str:
|
||||
|
||||
weights: Union[Dict[str, int], str] = all_weights[key]
|
||||
|
||||
if isinstance(weights, dict):
|
||||
result: str = random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
|
||||
else:
|
||||
result: str = weights
|
||||
|
||||
return result
|
||||
|
||||
@@ -28,11 +28,9 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp
|
||||
create_region(world, player, locations_per_region, 'Sealed Caves (Sirens)'),
|
||||
create_region(world, player, locations_per_region, 'Military Fortress'),
|
||||
create_region(world, player, locations_per_region, 'Military Fortress (hangar)'),
|
||||
create_region(world, player, locations_per_region, 'Lab Entrance'),
|
||||
create_region(world, player, locations_per_region, 'Main Lab'),
|
||||
create_region(world, player, locations_per_region, 'Lab Research'),
|
||||
create_region(world, player, locations_per_region, 'The lab'),
|
||||
create_region(world, player, locations_per_region, 'The lab (power off)'),
|
||||
create_region(world, player, locations_per_region, 'The lab (upper)'),
|
||||
create_region(world, player, locations_per_region, 'Emperors tower (courtyard)'),
|
||||
create_region(world, player, locations_per_region, 'Emperors tower'),
|
||||
create_region(world, player, locations_per_region, 'Skeleton Shaft'),
|
||||
create_region(world, player, locations_per_region, 'Sealed Caves (Xarion)'),
|
||||
@@ -43,7 +41,6 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp
|
||||
create_region(world, player, locations_per_region, 'Lower Lake Serene'),
|
||||
create_region(world, player, locations_per_region, 'Caves of Banishment (upper)'),
|
||||
create_region(world, player, locations_per_region, 'Caves of Banishment (Maw)'),
|
||||
create_region(world, player, locations_per_region, 'Caves of Banishment (Flooded)'),
|
||||
create_region(world, player, locations_per_region, 'Caves of Banishment (Sirens)'),
|
||||
create_region(world, player, locations_per_region, 'Castle Ramparts'),
|
||||
create_region(world, player, locations_per_region, 'Castle Keep'),
|
||||
@@ -112,19 +109,16 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp
|
||||
connect(world, player, 'Military Fortress', 'Temporal Gyre', lambda state: state.has('Timespinner Wheel', player) and logic.can_kill_all_3_bosses(state))
|
||||
connect(world, player, 'Military Fortress', 'Military Fortress (hangar)', logic.has_doublejump)
|
||||
connect(world, player, 'Military Fortress (hangar)', 'Military Fortress')
|
||||
connect(world, player, 'Military Fortress (hangar)', 'Lab Entrance', lambda state: state.has('Water Mask', player) if flooded.flood_lab else logic.has_doublejump(state))
|
||||
connect(world, player, 'Lab Entrance', 'Main Lab', lambda state: logic.has_keycard_B(state))
|
||||
connect(world, player, 'Main Lab', 'Lab Entrance')
|
||||
connect(world, player, 'Lab Entrance', 'Military Fortress (hangar)')
|
||||
connect(world, player, 'Military Fortress (hangar)', 'The lab', lambda state: logic.has_keycard_B(state) and (state.has('Water Mask', player) if flooded.flood_lab else logic.has_doublejump(state)))
|
||||
connect(world, player, 'Temporal Gyre', 'Military Fortress')
|
||||
connect(world, player, 'Main Lab', 'Lab Research', lambda state: state.has('Lab Access Research', player) if options.lock_key_amadeus else logic.has_doublejump_of_npc(state))
|
||||
connect(world, player, 'Main Lab', 'The lab (upper)', lambda state: logic.has_forwarddash_doublejump(state) and ((not options.lock_key_amadeus) or state.has('Lab Access Genza', player)))
|
||||
connect(world, player, 'The lab (upper)', 'Main Lab', lambda state: options.lock_key_amadeus and state.has('Lab Access Genza', player))
|
||||
connect(world, player, 'The lab (upper)', 'Emperors tower (courtyard)', logic.has_forwarddash_doublejump)
|
||||
connect(world, player, 'The lab', 'Military Fortress')
|
||||
connect(world, player, 'The lab', 'The lab (power off)', lambda state: options.lock_key_amadeus or logic.has_doublejump_of_npc(state))
|
||||
connect(world, player, 'The lab (power off)', 'The lab', lambda state: not flooded.flood_lab or state.has('Water Mask', player))
|
||||
connect(world, player, 'The lab (power off)', 'The lab (upper)', lambda state: logic.has_forwarddash_doublejump(state) and ((not options.lock_key_amadeus) or state.has('Lab Access Genza', player)))
|
||||
connect(world, player, 'The lab (upper)', 'The lab (power off)', lambda state: options.lock_key_amadeus and state.has('Lab Access Genza', player))
|
||||
connect(world, player, 'The lab (upper)', 'Emperors tower', logic.has_forwarddash_doublejump)
|
||||
connect(world, player, 'The lab (upper)', 'Ancient Pyramid (entrance)', lambda state: state.has_all({'Timespinner Wheel', 'Timespinner Spindle', 'Timespinner Gear 1', 'Timespinner Gear 2', 'Timespinner Gear 3'}, player))
|
||||
connect(world, player, 'Emperors tower (courtyard)', 'The lab (upper)')
|
||||
connect(world, player, 'Emperors tower (courtyard)', 'Emperors tower', logic.has_doublejump)
|
||||
connect(world, player, 'Emperors tower', 'Emperors tower (courtyard)')
|
||||
connect(world, player, 'Emperors tower', 'The lab (upper)')
|
||||
connect(world, player, 'Skeleton Shaft', 'Lake desolation')
|
||||
connect(world, player, 'Skeleton Shaft', 'Sealed Caves (Xarion)', logic.has_keycard_A)
|
||||
connect(world, player, 'Skeleton Shaft', 'Space time continuum', logic.has_teleport)
|
||||
@@ -151,7 +145,6 @@ def create_regions_and_locations(world: MultiWorld, player: int, options: Timesp
|
||||
connect(world, player, 'Caves of Banishment (upper)', 'Space time continuum', logic.has_teleport)
|
||||
connect(world, player, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: logic.has_doublejump(state) if not flooded.flood_maw else state.has('Water Mask', player))
|
||||
connect(world, player, 'Caves of Banishment (Maw)', 'Caves of Banishment (Sirens)', lambda state: state.has_any({'Gas Mask', 'Talaria Attachment'}, player) )
|
||||
connect(world, player, 'Caves of Banishment (Maw)', 'Caves of Banishment (Flooded)', lambda state: flooded.flood_maw or state.has('Water Mask', player))
|
||||
connect(world, player, 'Caves of Banishment (Maw)', 'Space time continuum', logic.has_teleport)
|
||||
connect(world, player, 'Caves of Banishment (Sirens)', 'Forest')
|
||||
connect(world, player, 'Castle Ramparts', 'Forest')
|
||||
|
||||
@@ -3,7 +3,7 @@ from BaseClasses import Item, Tutorial, ItemClassification
|
||||
from .Items import get_item_names_per_category
|
||||
from .Items import item_table, starter_melee_weapons, starter_spells, filler_items, starter_progression_items, pyramid_start_starter_progression_items
|
||||
from .Locations import get_location_datas, EventId
|
||||
from .Options import BackwardsCompatiableTimespinnerOptions, Toggle, BossRandoType
|
||||
from .Options import BackwardsCompatiableTimespinnerOptions, Toggle
|
||||
from .PreCalculatedWeights import PreCalculatedWeights
|
||||
from .Regions import create_regions_and_locations
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
@@ -104,8 +104,6 @@ class TimespinnerWorld(World):
|
||||
"Cantoran": self.options.cantoran.value,
|
||||
"LoreChecks": self.options.lore_checks.value,
|
||||
"BossRando": self.options.boss_rando.value,
|
||||
"BossRandoType": self.options.boss_rando_type.value,
|
||||
"BossRandoOverrides": self.precalculated_weights.boss_rando_overrides,
|
||||
"EnemyRando": self.options.enemy_rando.value,
|
||||
"DamageRando": self.options.damage_rando.value,
|
||||
"DamageRandoOverrides": self.options.damage_rando_overrides.value,
|
||||
@@ -134,8 +132,6 @@ class TimespinnerWorld(World):
|
||||
"PyramidStart": self.options.pyramid_start.value,
|
||||
"GateKeep": self.options.gate_keep.value,
|
||||
"RoyalRoadblock": self.options.royal_roadblock.value,
|
||||
"PureTorcher": self.options.pure_torcher.value,
|
||||
"FindTheFlame": self.options.find_the_flame.value,
|
||||
"Traps": self.options.traps.value,
|
||||
"DeathLink": self.options.death_link.value,
|
||||
"StinkyMaw": True,
|
||||
@@ -183,8 +179,6 @@ class TimespinnerWorld(World):
|
||||
self.options.cantoran.value = slot_data["Cantoran"]
|
||||
self.options.lore_checks.value = slot_data["LoreChecks"]
|
||||
self.options.boss_rando.value = slot_data["BossRando"]
|
||||
self.options.boss_rando_type.value = slot_data["BossRandoType"]
|
||||
self.precalculated_weights.boss_rando_overrides = slot_data["BossRandoOverrides"]
|
||||
self.options.damage_rando.value = slot_data["DamageRando"]
|
||||
self.options.damage_rando_overrides.value = slot_data["DamageRandoOverrides"]
|
||||
self.options.hp_cap.value = slot_data["HpCap"]
|
||||
@@ -205,7 +199,6 @@ class TimespinnerWorld(World):
|
||||
self.options.rising_tides.value = slot_data["RisingTides"]
|
||||
self.options.unchained_keys.value = slot_data["UnchainedKeys"]
|
||||
self.options.back_to_the_future.value = slot_data["PresentAccessWithWheelAndSpindle"]
|
||||
self.options.prism_break.value = slot_data["PrismBreak"]
|
||||
self.options.traps.value = slot_data["Traps"]
|
||||
self.options.death_link.value = slot_data["DeathLink"]
|
||||
# Readonly slot_data["StinkyMaw"]
|
||||
@@ -242,10 +235,7 @@ class TimespinnerWorld(World):
|
||||
spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n')
|
||||
else:
|
||||
spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n')
|
||||
|
||||
if self.options.boss_rando.value and self.options.boss_rando_type.value == BossRandoType.option_manual:
|
||||
spoiler_handle.write(f'Selected bosses: {self.precalculated_weights.boss_rando_overrides}\n')
|
||||
|
||||
|
||||
if self.options.rising_tides:
|
||||
flooded_areas: List[str] = []
|
||||
|
||||
@@ -308,9 +298,7 @@ class TimespinnerWorld(World):
|
||||
if not item.advancement:
|
||||
return item
|
||||
|
||||
if name == 'Tablet' and not self.options.downloadable_items:
|
||||
item.classification = ItemClassification.filler
|
||||
elif name == 'Library Keycard V' and not (self.options.downloadable_items or self.options.pure_torcher):
|
||||
if (name == 'Tablet' or name == 'Library Keycard V') and not self.options.downloadable_items:
|
||||
item.classification = ItemClassification.filler
|
||||
elif name == 'Oculus Ring' and not self.options.eye_spy:
|
||||
item.classification = ItemClassification.filler
|
||||
@@ -327,8 +315,6 @@ class TimespinnerWorld(World):
|
||||
item.classification = ItemClassification.filler
|
||||
elif name == "Drawbridge Key" and not self.options.gate_keep:
|
||||
item.classification = ItemClassification.filler
|
||||
elif name == "Cube of Bodie" and not self.options.find_the_flame:
|
||||
item.classification = ItemClassification.filler
|
||||
|
||||
return item
|
||||
|
||||
@@ -375,9 +361,6 @@ class TimespinnerWorld(World):
|
||||
if not self.options.gate_keep:
|
||||
excluded_items.add('Drawbridge Key')
|
||||
|
||||
if not self.options.find_the_flame:
|
||||
excluded_items.add('Cube of Bodie')
|
||||
|
||||
for item in self.multiworld.precollected_items[self.player]:
|
||||
if item.name not in self.item_name_groups['UseItem']:
|
||||
excluded_items.add(item.name)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user