Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Currently, the following games are supported:
* Celeste (Open World)
* Choo-Choo Charles
* APQuest
* BK Simulator

For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
Expand Down
3 changes: 3 additions & 0 deletions docs/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
# Aquaria
/worlds/aquaria/ @tioui

# BK Simulator
/worlds/bksim @EmilyV99

# Blasphemous
/worlds/blasphemous/ @TRPG0

Expand Down
1 change: 1 addition & 0 deletions worlds/bksim/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
1 change: 1 addition & 0 deletions worlds/bksim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .world import BKSimWorld as BKSimWorld
5 changes: 5 additions & 0 deletions worlds/bksim/archipelago.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"game": "BKSimulator",
"authors": ["EmilyV99"],
"world_version": "0.1.2"
}
17 changes: 17 additions & 0 deletions worlds/bksim/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from enum import StrEnum

game_name = 'BKSimulator'


class RID(StrEnum):
HOME = 'Home'
SUNNY = 'Sunny'
RAINY = 'Rainy'
SNOWY = 'Snowy'


class ITEM(StrEnum):
SHOES = 'Better Shoes'
BOOTS = 'Snow Boots'
NEWLOC = 'New Location'
TOY = 'Plastic Toy'
27 changes: 27 additions & 0 deletions worlds/bksim/docs/en_BKSimulator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# BK Simulator

## What is BK Simulator?
You have to walk to BK, in various weather conditions. Each time you reach BK is a check. Collect every check (and return home!) to goal.

<iframe frameborder="0" src="https://itch.io/embed/4088465?linkback=true" width="552" height="167"><a href="https://emilyv99.itch.io/bk-simulator">BK Simulator by Emily</a></iframe>

## Settings
[Options Page](../player-options)
- You choose how many locations are in the game. Collecting ALL of them is required as the goal.
- You can adjust the distance to the nearest BK at the start. This will heavily control the speed / length of the game.
- You can also adjust the speed gained for each speed upgrade.
- You can set a percentage of your Shoe/Boot upgrades to be replaced with filler. (Higher values will be fulfilled as much as is possible without breaking logic requirements)

## Items / Logic
Items:
- Shoe upgrades allow you to walk faster
- Snow Boots allow you to walk in snow (and walk faster in snow)
- Opening a new BK location cuts the distance you need to walk in half

Logic:
- Sunny weather is the standard, and where you should start
- Rainy weather is slower to walk in, and expects that you have more upgrades than sunny weather expects
- Snowy weather requires snow boots to walk in
- Higher-numbered checks will expect gradually more items to become in-logic.
- "Sphere 1" includes the first 2 Sunny checks only.

32 changes: 32 additions & 0 deletions worlds/bksim/docs/setup_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# BK Simulator Setup Guide

## Required Software

- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest), to generate or use text client.

## How to play

- Open the game, connect to your slot.
- Choose which weather to walk to BK in.
- Each time you reach BK, you send a check. You do still need to walk home after, before you can start your next trip!

<iframe frameborder="0" src="https://itch.io/embed/4088465?linkback=true" width="552" height="167"><a href="https://emilyv99.itch.io/bk-simulator">BK Simulator by Emily</a></iframe>

## Settings
- You choose how many locations are in the game. Collecting ALL of them is required as the goal.
- You can adjust the distance to the nearest BK at the start. This will heavily control the speed / length of the game.
- You can also adjust the speed gained for each speed upgrade.
- You can set a percentage of your Shoe/Boot upgrades to be replaced with filler. (Higher values will be fulfilled as much as is possible without breaking logic requirements)

## Items / Logic
Items:
- Shoe upgrades allow you to walk faster
- Snow Boots allow you to walk in snow (and walk faster in snow)
- Opening a new BK location cuts the distance you need to walk in half

Logic:
- Sunny weather is the standard, and where you should start
- Rainy weather is slower to walk in, and expects that you have more upgrades than sunny weather expects
- Snowy weather requires snow boots to walk in
- Higher-numbered checks will expect gradually more items to become in-logic.
- "Sphere 1" includes the first 2 Sunny checks only.
87 changes: 87 additions & 0 deletions worlds/bksim/items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from __future__ import annotations
from math import floor, ceil
from BaseClasses import Item, ItemClassification
from .common import *
import typing
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .world import BKSimWorld


class BKSim_Item(Item):
game = game_name


class ItemInfo(typing.NamedTuple):
name: ITEM
flag: ItemClassification


item_table = [
ItemInfo(ITEM.SHOES, ItemClassification.progression),
ItemInfo(ITEM.BOOTS, ItemClassification.progression),
ItemInfo(ITEM.NEWLOC, ItemClassification.progression),
ItemInfo(ITEM.TOY, ItemClassification.filler),
]
item_name_to_id = {str(name): num for num, (name, _) in enumerate(item_table, 1)}


def get_item_counts(world: BKSimWorld) -> list[int]:
multiworld = world.multiworld
player = world.player
options = world.options

newloc_count: int = 0
shoe_count: int = 0
boot_count: int = 0

for item in multiworld.precollected_items[player]:
if item.name == ITEM.NEWLOC:
newloc_count += 1
elif item.name == ITEM.SHOES:
shoe_count += 1
elif item.name == ITEM.BOOTS:
boot_count += 1

extra_filler_rate: float = max(0, min(100, options.extra_filler_rate.value)) / 100.0
per_weather_locs: int = int(options.locs_per_weather.value)
total_locs: int = per_weather_locs * 3
newloc_items: int = max(0, min(2, per_weather_locs) - newloc_count)
snow_items: int = per_weather_locs
shoe_items: int = per_weather_locs
toy_items: int = per_weather_locs - newloc_items
if extra_filler_rate > 0.0: # replace shoes/boots with filler at the specified rate, but don't break logic
shoe_items = max(0, (int(floor(per_weather_locs - 1) / 2)) + 1 - shoe_count,
ceil(shoe_items * (1.0 - extra_filler_rate)))
snow_items = max(0, (int(floor(per_weather_locs - 1) / 2)) + 1 - boot_count,
ceil(snow_items * (1.0 - extra_filler_rate)))
toy_items += (per_weather_locs - shoe_items) + (per_weather_locs - snow_items)

# ensure proper item count
toy_items += total_locs - (newloc_items + snow_items + shoe_items + toy_items)

return [shoe_items, snow_items, newloc_items, toy_items]


def create_items(world: BKSimWorld) -> None:
multiworld = world.multiworld
player = world.player

counts: list[int] = get_item_counts(world)
itempool = []
for q in range(len(item_table)):
data: ItemInfo = item_table[q]
count: int = counts[q]
itempool += [BKSim_Item(str(data.name), data.flag, q + 1, player) for _ in range(count)]
multiworld.itempool += itempool


def create_item(name: str, player: int) -> BKSim_Item:
itemid = item_name_to_id[name]
_, flag = item_table[itemid - 1]
return BKSim_Item(name, flag, itemid, player)


def create_event_item(event: str, player: int) -> BKSim_Item:
return BKSim_Item(event, ItemClassification.progression, None, player)
27 changes: 27 additions & 0 deletions worlds/bksim/locations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import NamedTuple, Optional
from BaseClasses import Location, Region
from .common import *


class LocInfo(NamedTuple):
name: str
region_id: RID
index: int


class BKSim_Location(Location):
game = game_name
info: Optional[LocInfo]

# override constructor to automatically mark event locations as such, and handle LocInfo
def __init__(self, player: int, name: str, code: Optional[int], parent: Region, info: Optional[LocInfo] = None) -> None:
super(BKSim_Location, self).__init__(player, name, code, parent)
self.event = code is None
self.info = info


location_table = [LocInfo("Sunny %d" % (idx + 1), RID.SUNNY, idx) for idx in range(0,100)]
location_table += [LocInfo("Rainy %d" % (idx + 1), RID.RAINY, idx) for idx in range(0,100)]
location_table += [LocInfo("Snowy %d" % (idx + 1), RID.SNOWY, idx) for idx in range(0,100)]

location_name_to_id = {loc.name: num for num,loc in enumerate(location_table,1)}
67 changes: 67 additions & 0 deletions worlds/bksim/options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from dataclasses import dataclass
from Options import Range, PerGameCommonOptions


class LocationsPerWeather(Range):
"""The total number of locations per type of weather. (There are 3 weather types)"""
display_name = "Locations Per Weather"
range_start = 1
range_end = 100
default = 3


class StartDistance(Range):
"""The distance to the closest BK at the start.
Each 'New Location' upgrade opens a location halfway between your house and the current closest location."""
display_name = "Start Distance"
range_start = 50
range_end = 5000
default = 300


class SpeedPerUpgrade(Range):
"""The amount of speed gained for each shoe upgrade.
Snow boots give half this speed in the snow."""
display_name = "Speed Per Upgrade"
range_start = 1
range_end = 100
default = 2


class ExtraFillerRate(Range):
"""This percent of shoe/boot upgrades will attempt to be turned into filler.
The number of upgrades will not be reduced below the amount logically required."""
display_name = "Extra Filler Rate"
range_start = 0
range_end = 100
default = 0


@dataclass
class BKSim_Options(PerGameCommonOptions):
locs_per_weather: LocationsPerWeather
start_distance: StartDistance
speed_per_upgrade: SpeedPerUpgrade
extra_filler_rate: ExtraFillerRate


options_presets = {
"Quick": {
"locs_per_weather": 1,
"start_distance": 100,
"speed_per_upgrade": 5,
"extra_filler_rate": 0,
},
"Marathon": {
"locs_per_weather": 1,
"start_distance": 5000,
"speed_per_upgrade": 1,
"extra_filler_rate": 0,
},
"Masochist": {
"locs_per_weather": 100,
"start_distance": 5000,
"speed_per_upgrade": 1,
"extra_filler_rate": 100,
},
}
24 changes: 24 additions & 0 deletions worlds/bksim/regions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations
from BaseClasses import Region
from .locations import location_table, BKSim_Location
from .common import *
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .world import BKSimWorld


def create_regions(world: BKSimWorld) -> None:
multiworld = world.multiworld
player = world.player

for rid in RID:
multiworld.regions.append(Region(str(rid), player, multiworld))

locs_per_weather = world.options.locs_per_weather.value
for locid, locinfo in enumerate(location_table, 1):
if locinfo.index >= locs_per_weather:
continue
region = world.get_region(locinfo.region_id)
if region:
region.locations.append(BKSim_Location(player, locinfo.name, locid, region, locinfo))
45 changes: 45 additions & 0 deletions worlds/bksim/rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from __future__ import annotations
from math import floor
from ..generic.Rules import set_rule
from .common import *
from .locations import BKSim_Location
import typing
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .world import BKSimWorld


def set_rules(world: BKSimWorld) -> None:
multiworld = world.multiworld
player = world.player
options = world.options

world.get_region(RID.HOME).connect(connecting_region=world.get_region(RID.SUNNY))
world.get_region(RID.HOME).connect(connecting_region=world.get_region(RID.RAINY))
world.get_region(RID.HOME).connect(connecting_region=world.get_region(RID.SNOWY), rule=lambda state: state.has(ITEM.BOOTS, player))

locs_list: typing.Iterable[BKSim_Location] = typing.cast(typing.Iterable[BKSim_Location], multiworld.get_locations(player))
loc_count = options.locs_per_weather.value
max_rules = []
for loc in locs_list:
if loc.info.region_id == RID.SUNNY:
if loc.info.index == 0:
continue
tmp_rule = lambda state, idx=loc.info.index: state.has(ITEM.SHOES, player, floor(idx / 2))
if loc.info.index == loc_count - 1:
max_rules.append(tmp_rule)
set_rule(loc, tmp_rule)
elif loc.info.region_id == RID.RAINY:
tmp_rule = lambda state, idx=loc.info.index: (state.has(ITEM.SHOES, player, floor(idx / 2) + 1) and state.has(
ITEM.NEWLOC, player)) or state.has(ITEM.SHOES, player, idx)
if loc.info.index == loc_count - 1:
max_rules.append(tmp_rule)
set_rule(loc, tmp_rule)
elif loc.info.region_id == RID.SNOWY:
tmp_rule = lambda state, idx=loc.info.index: state.has(ITEM.BOOTS, player, floor(idx / 2) + 1)
if loc.info.index == loc_count - 1:
max_rules.append(tmp_rule)
set_rule(loc, tmp_rule)

multiworld.completion_condition[player] = lambda state: all(rule(state) for rule in max_rules)
16 changes: 16 additions & 0 deletions worlds/bksim/web_world.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from worlds.AutoWorld import WebWorld
from . import options
from BaseClasses import Tutorial


class BKSimWebWorld(WebWorld):
options_presets = options.options_presets
setup_en = Tutorial(
"BKSim Setup",
"How to set up BK Simulator for Archipelago",
"English",
"setup_en.md",
"setup/en",
["Emily"]
)
tutorials = [setup_en]
Loading
Loading