Skip to content

Commit

Permalink
Remade branches because I'm stupid
Browse files Browse the repository at this point in the history
  • Loading branch information
Ehseezed committed Aug 15, 2024
1 parent 56aabe5 commit 3e6b945
Show file tree
Hide file tree
Showing 8 changed files with 968 additions and 0 deletions.
283 changes: 283 additions & 0 deletions worlds/snailiad/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
from typing import Dict, List, Any

from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
from worlds.generic.Rules import set_rule
from .items import item_name_to_id, item_table, filler_items, item_name_group, slot_data_item_names
from .locations import location_table, location_name_group, location_name_to_id
from .rules import create_region_rules, create_location_rules, blue_door, green_door, red_door, pink_door, \
has_devastator
from .regions import snailiad_regions
from .options import SnailiadOptions
from worlds.AutoWorld import WebWorld, World
from decimal import Decimal, ROUND_HALF_UP


class SnailiadWeb(WebWorld):
tutorials = [
Tutorial(
tutorial_name= "Multiworld Setup Guide",
description= "A Guide to setting up a Snailiad+ Multiworld",
language="English",
file_name="setup_en.md",
link="setup/en",
authors=["Ehseezed, EpsilonTheDerg"]
)
]
theme = "ice"
game = "Snailiad+"


class SnailiadItem(Item):
game: str = "Snailiad"


class SnailiadLocation(Location):
game: str = "Snailiad"


class SnailiadWorld(World):
"""
Snail™
""" # todo Description
game = "Snailiad+"
web = SnailiadWeb()

data_version = 0
options: SnailiadOptions
options_dataclass = SnailiadOptions
item_name_groups = item_name_group
location_name_groups = location_name_group

item_name_to_id = item_name_to_id
location_name_to_id = location_name_to_id

slot_data_items: List[SnailiadItem]

def generate_early(self) -> None:
if hasattr(self.multiworld, "re_gen_passthrough"):
if "Snailiad" in self.multiworld.re_gen_passthrough:
passthrough = self.multiworld.re_gen_passthrough["Snailiad"]
self.options.Progressive_Items.value = passthrough["Progressive_Items"]
self.options.Randomization_Type.value = passthrough["Randomization_Type"]

def create_item(self, name: str) -> SnailiadItem:
item_data = item_table[name]
return SnailiadItem(name, item_data.classification, self.item_name_to_id[name], self.player)

def create_items(self) -> None:
character = self.options.Character_Select
progressive = self.options.Progressive_Items
helix_locks = self.options.Helix_Locks
difficulty = self.options.Difficulty_Select
traps = self.options.Trap_Fill

nothings = 2 # SSB and DRW will never be generated
nothings += 3 # Squared Snelks, Scorching Snelks, and Hidden Hideout did not have items in the original game they are rando exclusive

snailiad_items: List[SnailiadItem] = []

items_to_create: Dict[str, int] = {item: data.quantity_in_item_pool for item, data in item_table.items()}

if self.options.Randomization_Type.value != self.options.Randomization_Type.option_pro:
nothings -= 5 # Original Testing Room, Scorching Snelks, Squared Snelks, Hidden Hideout and Lost Loot are not locations

if progressive:
items_to_create["Pea Shooter"] = 0
items_to_create["Boomerang"] = 0
items_to_create["Rainbow Wave"] = 0
items_to_create["Progressive Weapon"] = 3

items_to_create["Rapid Fire"] = 0
items_to_create["Devastator"] = 0
items_to_create["Progressive Modifier"] = 2

items_to_create["Ice Snail"] = 0
items_to_create["Gravity Shell"] = 0
items_to_create["Full Metal Snail"] = 0
if difficulty == difficulty.option_insane:
items_to_create["Progressive Shell"] = 2
nothings += 1
else:
items_to_create["Progressive Shell"] = 3

if difficulty == difficulty.option_insane:
items_to_create["Ice Snail"] = 0
nothings += 1

if character == character.option_sluggy:
items_to_create["Shell Shield"] = 0
nothings += 1

if character == character.option_upside:
items_to_create["Gravity Shell"] = 0
items_to_create["Magnetic Foot"] = 1

if character == character.option_leggy:
items_to_create["Gravity Shell"] = 0
items_to_create["Corkscrew Jump"] = 1

if character == character.option_blobby:
items_to_create["Gravity Shell"] = 0
items_to_create["Angel Hop"] = 1

items_to_create["High Jump"] = 0
items_to_create["Wall Grab"] = 1

if character == character.option_leechy:
items_to_create["Shell Shield"] = 0
nothings += 1

items_to_create["Rapid Fire"] = 0
items_to_create["Backfire"] = 1

# my initial plan to swap the progression status did not work so I swapped to this combo setup from TUNIC and Mssenger for readbility and functionality
if helix_locks:
if self.options.Trap_Fill:
total_pieces = 25
else:
total_pieces = 30
req_pieces = 25
fragments = [self.create_item("Helix Fragment") for _ in range(total_pieces)]
for i in range(0, req_pieces):
fragments[i].classification = ItemClassification.progression_skip_balancing
items_to_create["Helix Fragment"] = 0

items_to_create["Nothing"] = nothings

req_hearts = 4
hearts = [self.create_item("Heart Container") for _ in range(11)]
for i in range(0, req_hearts):
hearts[i].classification = ItemClassification.progression
self.multiworld.itempool += hearts

for item, quantity in items_to_create.items():
for i in range(0, quantity):
snailiad_item: SnailiadItem = self.create_item(item)
snailiad_items.append(snailiad_item)

self.multiworld.itempool += snailiad_items

def create_regions(self) -> None:
for region_name in snailiad_regions:
region = Region(region_name, self.player, self.multiworld)
self.multiworld.regions.append(region)

for region_name, exits in snailiad_regions.items():
region = self.multiworld.get_region(region_name, self.player)
region.add_exits(exits)

for location_name, location_id in self.location_name_to_id.items():
region = self.multiworld.get_region(location_table[location_name].region, self.player)
location = SnailiadLocation(self.player, location_name, location_id, region)
region.locations.append(location)

if self.options.Randomization_Type.value != self.options.Randomization_Type.option_pro:
a0 = self.multiworld.get_region("Snail Town", self.player)
a0.locations.remove(self.multiworld.get_location("Original Testing Room", self.player))

a2 = self.multiworld.get_region("Spiralis Silere", self.player)
a2.locations.remove(self.multiworld.get_location("Squared Snelks", self.player))

a3 = self.multiworld.get_region("Amastrida Abyssus", self.player)
a3.locations.remove(self.multiworld.get_location("Scorching Snelks", self.player))
a3.locations.remove(self.multiworld.get_location("Hidden Hideout", self.player))

a4 = self.multiworld.get_region("Lux Lirata", self.player)
a4.locations.remove(self.multiworld.get_location("Lost Loot", self.player))

boss_1_region = self.multiworld.get_region("Mare Carelia", self.player)
boss_1_location = SnailiadLocation(self.player, "Shell Breaker", None, boss_1_region)

boss_1_location.place_locked_item(SnailiadItem("Boss 1", ItemClassification.progression, None, self.player))
boss_1_region.locations.append(boss_1_location)

boss_2_region = self.multiworld.get_region("Spiralis Silere", self.player)
boss_2_location = SnailiadLocation(self.player, "Stompy", None, boss_2_region)
boss_2_location.place_locked_item(SnailiadItem("Boss 2", ItemClassification.progression, None, self.player))
boss_2_region.locations.append(boss_2_location)

boss_3_region = self.multiworld.get_region("Amastrida Abyssus", self.player)
boss_3_location = SnailiadLocation(self.player, "Space Box", None, boss_3_region)
boss_3_location.place_locked_item(SnailiadItem("Boss 3", ItemClassification.progression, None, self.player))
boss_3_region.locations.append(boss_3_location)

boss_4_region = self.multiworld.get_region("Lux Lirata", self.player)
boss_4_location = SnailiadLocation(self.player, "Moon Snail", None, boss_4_region)
boss_4_location.place_locked_item(SnailiadItem("Boss 4", ItemClassification.progression, None, self.player))
boss_4_region.locations.append(boss_4_location)

helix_region = self.multiworld.get_region("Shrine of Iris", self.player)
helix_location = SnailiadLocation(self.player, "Helix Fragment Cutscene", None, helix_region)
helix_location.place_locked_item(SnailiadItem("All Helix\'s", ItemClassification.progression, None, self.player))
helix_region.locations.append(helix_location)

self.multiworld.completion_condition[self.player] = lambda state: state.has("Boss 4", self.player)

def set_rules(self) -> None:
create_region_rules(self)
create_location_rules(self)
# Boss event location requirements
if self.options.Helix_Locks:
set_rule(self.multiworld.get_location("Shell Breaker", self.player),
lambda state: blue_door(state, self.player, self) and state.has("Helix Fragment", self.player, 5))
set_rule(self.multiworld.get_location("Stompy", self.player),
lambda state: pink_door(state, self.player, self) and state.has("Helix Fragment", self.player, 10))
set_rule(self.multiworld.get_location("Space Box", self.player),
lambda state: red_door(state, self.player, self) and state.has("Helix Fragment", self.player, 15))
set_rule(self.multiworld.get_location("Moon Snail", self.player),
lambda state: green_door(state, self.player, self) and state.has("Helix Fragment", self.player, 25))
else:
set_rule(self.multiworld.get_location("Shell Breaker", self.player),
lambda state: blue_door(state, self.player, self))
set_rule(self.multiworld.get_location("Stompy", self.player),
lambda state: pink_door(state, self.player, self))
set_rule(self.multiworld.get_location("Space Box", self.player),
lambda state: red_door(state, self.player, self))
set_rule(self.multiworld.get_location("Moon Snail", self.player),
lambda state: green_door(state, self.player, self) and has_devastator(state, self.player))


def get_filler_item_name(self) -> str:
return self.random.choice(filler_items)

def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {
"seed": self.random.randint(0, 2147483647),
"Randomization_Type": self.options.Randomization_Type.value,
"Difficulty_Select": self.options.Difficulty_Select.value,
"Character_Select": self.options.Character_Select.value,
"Progressive_Items": self.options.Progressive_Items.value,
"Start_With_Broom": self.options.Start_With_Broom.value,
"Open_Areas": self.options.Open_Areas.value,
"Helix_Locks": self.options.Helix_Locks.value,
"Music_Shuffle": self.options.Music_Shuffle.value,
"Snails_Have_Hints": self.options.Snails_Have_Hints.value,
"Trap_Fill": self.options.Trap_Fill.value,
"Hidden_Items": self.options.Hidden_Items.value
}

for start_item in self.options.start_inventory_from_pool:
if start_item in slot_data_item_names:
if start_item not in slot_data:
slot_data[start_item] = []
for i in range(0, self.options.start_inventory_from_pool[start_item]):
slot_data[start_item].extend(["Your Shell(?)", self.player])

for plando_item in self.multiworld.plando_items[self.player]:
if plando_item["from_pool"]:
items_to_find = set()
for item_type in [key for key in ["item", "items"] if key in plando_item]:
for item in plando_item[item_type]:
items_to_find.add(item)
for item in items_to_find:
if item in slot_data_item_names:
slot_data[item] = []
for item_location in self.multiworld.find_item_locations(item, self.player):
slot_data[item].extend([item_location.name, item_location.player])

return slot_data

@staticmethod # for universal tracker
def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]:
# returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough
return slot_data
11 changes: 11 additions & 0 deletions worlds/snailiad/docs/en_Snailiad+.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Snailiad+

## What is Snailiad+?
Snailiad+ is a remake of the original Snailiad flash game, with new content and features.
It is a metroidvania-style game where you play as a snail named Snaily, who must save the world from an alien invasion.
The game features a variety of power-ups and abilities, as well as a large interconnected world to explore.

## What does randomization do to this game?

In the Snailiad+ Randomizer, the locations of all the power-ups and abilities in the game are shuffled around with the
possibility of being locked behind other power-ups or abilities completely changing how you approach the world.
1 change: 1 addition & 0 deletions worlds/snailiad/docs/setup_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Just set it up 4-head
75 changes: 75 additions & 0 deletions worlds/snailiad/items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from itertools import groupby
from typing import Dict, List, Set, NamedTuple
from BaseClasses import ItemClassification


class SnailiadItemData(NamedTuple):
classification: ItemClassification
quantity_in_item_pool: int
item_id_offset: int
in_game_id: int
item_group: str = ""


item_base_id = 609342400

item_table: Dict[str, SnailiadItemData] = {
"Pea Shooter": SnailiadItemData(ItemClassification.progression, 1, 0, 0, "Weapon"), # todo Progression or Not
"Boomerang": SnailiadItemData(ItemClassification.progression, 1, 1, 1, "Weapon"),
"Super Secret Boomerang": SnailiadItemData(ItemClassification.progression, 0, 2, 11, "Weapon"), # not generated but kept here for parity
"Rainbow Wave": SnailiadItemData(ItemClassification.progression, 1, 3, 2, "Weapon"),
"Debug Rainbow Wave": SnailiadItemData(ItemClassification.progression, 0, 4, 12, "Weapon"), # not generated but kept here for parity
"Progressive Weapon": SnailiadItemData(ItemClassification.progression, 0, 5, 100000, "Weapon"), # todo confirm with epsilon what IDs the progressibve items are

"Rapid Fire": SnailiadItemData(ItemClassification.progression, 1, 6, 6, "Modifier"),
"Backfire": SnailiadItemData(ItemClassification.progression, 0, 7, 6, "Modifier"),
"Devastator": SnailiadItemData(ItemClassification.progression, 1, 8, 3, "Modifier"),
"Progressive Modifier": SnailiadItemData(ItemClassification.progression, 0, 9, 200000, "Modifier"),

"Ice Snail": SnailiadItemData(ItemClassification.progression, 1, 10, 7, "Shell"),
"Gravity Shell": SnailiadItemData(ItemClassification.progression, 1, 11, 8, "Shell"),
"Magnetic Foot": SnailiadItemData(ItemClassification.progression, 0, 12, 8, "Shell"),
"Corkscrew Jump": SnailiadItemData(ItemClassification.progression, 0, 13, 8, "Shell"),
"Angel Hop": SnailiadItemData(ItemClassification.progression, 0, 14, 8, "Shell"),
"Full Metal Snail": SnailiadItemData(ItemClassification.progression, 1, 15, 9, "Shell"),
"Progressive Shell": SnailiadItemData(ItemClassification.progression, 0, 16, 300000, "Shell"),

"Gravity Shock": SnailiadItemData(ItemClassification.progression, 1, 17, 10, "Ability"),
"High Jump": SnailiadItemData(ItemClassification.progression, 1, 18, 4, "Ability"),
"Wall Grab": SnailiadItemData(ItemClassification.progression, 0, 19, 4, "Ability"),
"Shell Shield": SnailiadItemData(ItemClassification.progression, 1, 20, 5, "Ability"),
"Shellmet": SnailiadItemData(ItemClassification.progression, 0, 21, 5, "Ability"),

"Helix Fragment": SnailiadItemData(ItemClassification.filler, 30, 22, 24, ""),
"Nothing": SnailiadItemData(ItemClassification.filler, 0, 23, 400000, ""), # created in __init__
"Heart Container": SnailiadItemData(ItemClassification.filler, 0, 24, 13, ""), # created in __init__

"Gravity Lock": SnailiadItemData(ItemClassification.trap, 0, 25, 500000, "Trap"),
"Weapon Lock": SnailiadItemData(ItemClassification.trap, 0, 26, 600000, "Trap"),
"Lullaby Trap": SnailiadItemData(ItemClassification.trap, 0, 27, 700000, "Trap"),
"Spider Trap": SnailiadItemData(ItemClassification.trap, 0, 28, 800000, "Trap"),
}

slot_data_item_names = []

item_name_to_id: Dict[str, int] = {name: item_base_id + data.item_id_offset for name, data in item_table.items()}

filler_items: List[str] = [name for name, data in item_table.items() if data.classification == ItemClassification.filler]


def get_item_group(item_name: str) -> str:
return item_table[item_name].item_group


item_name_group: Dict[str, Set[str]] = {
group: set(item_names) for group, item_names in groupby(sorted(item_table, key=get_item_group), get_item_group) if group != ""
}

extra_groups: Dict[str, Set[str]] = {
"Flight": {"Angel Hop", "Corkscrew Jump", "Magnetic Foot", "Gravity Shell"},
"Jump": {"High Jump", "Wall Grab"},
"Shield": {"Shell Shield", "Shellmet"},
"Rapid": {"Rapid Fire", "Backfire"},
} # if item groups are requested to be added, they can be added here

item_name_group.update(extra_groups)
Loading

0 comments on commit 3e6b945

Please sign in to comment.