Files
Grinch-AP/worlds/paint/__init__.py
MarioManTAW 8541c87c97 Paint: Implement New Game (#4955)
* Paint: Implement New Game

* Add docstring

* Remove unnecessary self.multiworld references

* Implement start_inventory_from_pool

* Convert logic to use LogicMixin

* Add location_exists_with_options function to deduplicate code

* Simplify starting tool creation

* Add Paint to supported games list

* Increment version to 0.4.1

* Update docs to include color selection features

* Fix world attribute definitions

* Fix linting errors

* De-duplicate lists of traps

* Move LogicMixin to __init__.py

* 0.5.0 features - adjustable canvas size increment, updated similarity metric

* Fix OptionError formatting

* Create OptionError when generating single-player game with error-prone settings

* Increment version to 0.5.1

* Update CODEOWNERS

* Update documentation for 0.5.2 client changes

* Simplify region creation

* Add comments describing logic

* Remove unnecessary f-strings

* Remove unused import

* Refactor rules to location class

* Remove unnecessary self.multiworld references

* Update logic to correctly match client-side item caps

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2025-07-23 23:27:50 +02:00

129 lines
5.8 KiB
Python

from typing import Dict, Any
from BaseClasses import CollectionState, Item, MultiWorld, Tutorial, Region
from Options import OptionError
from worlds.AutoWorld import LogicMixin, World, WebWorld
from .items import item_table, PaintItem, item_data_table, traps, deathlink_traps
from .locations import location_table, PaintLocation, location_data_table
from .options import PaintOptions
class PaintWebWorld(WebWorld):
theme = "partyTime"
setup_en = Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Paint in Archipelago.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["MarioManTAW"]
)
tutorials = [setup_en]
class PaintWorld(World):
"""
The classic Microsoft app, reimagined as an Archipelago game! Find your tools, expand your canvas, and paint the
greatest image the world has ever seen.
"""
game = "Paint"
options_dataclass = PaintOptions
options: PaintOptions
web = PaintWebWorld()
location_name_to_id = location_table
item_name_to_id = item_table
origin_region_name = "Canvas"
def generate_early(self) -> None:
if self.options.canvas_size_increment < 50 and self.options.logic_percent <= 55:
if self.multiworld.players == 1:
raise OptionError("Logic Percent must be greater than 55 when generating a single-player world with "
"Canvas Size Increment below 50.")
def get_filler_item_name(self) -> str:
if self.random.randint(0, 99) >= self.options.trap_count:
return "Additional Palette Color"
elif self.options.death_link:
return self.random.choice(deathlink_traps)
else:
return self.random.choice(traps)
def create_item(self, name: str) -> PaintItem:
item = PaintItem(name, item_data_table[name].type, item_data_table[name].code, self.player)
return item
def create_items(self) -> None:
starting_tools = ["Brush", "Pencil", "Eraser/Color Eraser", "Airbrush", "Line", "Rectangle", "Ellipse",
"Rounded Rectangle"]
self.push_precollected(self.create_item("Magnifier"))
self.push_precollected(self.create_item(starting_tools.pop(self.options.starting_tool)))
items_to_create = ["Free-Form Select", "Select", "Fill With Color", "Pick Color", "Text", "Curve", "Polygon"]
items_to_create += starting_tools
items_to_create += ["Progressive Canvas Width"] * (400 // self.options.canvas_size_increment)
items_to_create += ["Progressive Canvas Height"] * (300 // self.options.canvas_size_increment)
depth_items = ["Progressive Color Depth (Red)", "Progressive Color Depth (Green)",
"Progressive Color Depth (Blue)"]
for item in depth_items:
self.push_precollected(self.create_item(item))
items_to_create += depth_items * 6
pre_filled = len(items_to_create)
to_fill = len(self.get_region("Canvas").locations)
if pre_filled > to_fill:
raise OptionError(f"{self.player_name}'s Paint world has too few locations for its required items. "
"Consider adding more locations by raising logic percent or adding fractional checks. "
"Alternatively, increasing the canvas size increment will require fewer items.")
while len(items_to_create) < (to_fill - pre_filled) * (self.options.trap_count / 100) + pre_filled:
if self.options.death_link:
items_to_create += [self.random.choice(deathlink_traps)]
else:
items_to_create += [self.random.choice(traps)]
while len(items_to_create) < to_fill:
items_to_create += ["Additional Palette Color"]
self.multiworld.itempool += [self.create_item(item) for item in items_to_create]
def create_regions(self) -> None:
canvas = Region("Canvas", self.player, self.multiworld)
canvas.locations += [PaintLocation(self.player, loc_name, loc_data.address, canvas)
for loc_name, loc_data in location_data_table.items()
if location_exists_with_options(self, loc_data.address)]
self.multiworld.regions += [canvas]
def set_rules(self) -> None:
from .rules import set_completion_rules
set_completion_rules(self, self.player)
def fill_slot_data(self) -> Dict[str, Any]:
return dict(self.options.as_dict("logic_percent", "goal_percent", "goal_image", "death_link",
"canvas_size_increment"), version="0.5.2")
def collect(self, state: CollectionState, item: Item) -> bool:
change = super().collect(state, item)
if change:
state.paint_percent_stale[self.player] = True
return change
def remove(self, state: CollectionState, item: Item) -> bool:
change = super().remove(state, item)
if change:
state.paint_percent_stale[self.player] = True
return change
def location_exists_with_options(world: PaintWorld, location: int):
l = location % 198600
return l <= world.options.logic_percent * 4 and (l % 4 == 0 or
(l > world.options.half_percent_checks * 4 and l % 2 == 0) or
l > world.options.quarter_percent_checks * 4)
class PaintState(LogicMixin):
paint_percent_available: dict[int, float] # per player
paint_percent_stale: dict[int, bool]
def init_mixin(self, multiworld: MultiWorld) -> None:
self.paint_percent_available = {player: 0 for player in multiworld.get_game_players("Paint")}
self.paint_percent_stale = {player: True for player in multiworld.get_game_players("Paint")}