about summary refs log tree commit diff stats
path: root/bin/spring
blob: fb2ec2ebb48dfade14242dea0add2d98d235a6cb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env ruby

# This file loads spring without using Bundler, in order to be fast.
# It gets overwritten when you run the `spring binstub` command.

unless defined?(Spring)
  require 'rubygems'
  require 'bundler'

  lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
  spring = lockfile.specs.detect { |spec| spec.name == "spring" }
  if spring
    Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
    gem 'spring', spring.version
    require 'spring/binstub'
  end
end
.sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
"""
Archipelago init file for Lingo
"""
from logging import warning

from BaseClasses import Item, ItemClassification, Tutorial
from Options import OptionError
from worlds.AutoWorld import WebWorld, World
from .datatypes import Room, RoomEntrance
from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem
from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP
from .options import LingoOptions, lingo_option_groups
from .player_logic import LingoPlayerLogic
from .regions import create_regions


class LingoWebWorld(WebWorld):
    option_groups = lingo_option_groups
    theme = "grass"
    tutorials = [Tutorial(
        "Multiworld Setup Guide",
        "A guide to playing Lingo with Archipelago.",
        "English",
        "setup_en.md",
        "setup/en",
        ["hatkirby"]
    )]


class LingoWorld(World):
    """
    Lingo is a first person indie puzzle game in the vein of The Witness. You find yourself in a mazelike, non-Euclidean
    world filled with 800 word puzzles that use a variety of different mechanics.
    """
    game = "Lingo"
    web = LingoWebWorld()

    base_id = 444400
    topology_present = True
    data_version = 1

    options_dataclass = LingoOptions
    options: LingoOptions

    item_name_to_id = {
        name: data.code for name, data in ALL_ITEM_TABLE.items()
    }
    location_name_to_id = {
        name: data.code for name, data in ALL_LOCATION_TABLE.items()
    }
    item_name_groups = ITEMS_BY_GROUP
    location_name_groups = LOCATIONS_BY_GROUP

    player_logic: LingoPlayerLogic

    def generate_early(self):
        if not (self.options.shuffle_doors or self.options.shuffle_colors or self.options.shuffle_sunwarps):
            if self.multiworld.players == 1:
                warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression"
                        f" items. Please turn on Door Shuffle, Color Shuffle, or Sunwarp Shuffle if that doesn't seem"
                        f" right.")
            else:
                raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any"
                                  f" progression items. Please turn on Door Shuffle, Color Shuffle or Sunwarp Shuffle.")

        self.player_logic = LingoPlayerLogic(self)

    def create_regions(self):
        create_regions(self)

    def create_items(self):
        pool = [self.create_item(name) for name in self.player_logic.real_items]

        if self.player_logic.forced_good_item != "":
            new_item = self.create_item(self.player_logic.forced_good_item)
            location_obj = self.multiworld.get_location("Second Room - Good Luck", self.player)
            location_obj.place_locked_item(new_item)

        item_difference = len(self.player_logic.real_locations) - len(pool)
        if item_difference:
            trap_percentage = self.options.trap_percentage
            traps = int(item_difference * trap_percentage / 100.0)
            non_traps = item_difference - traps

            if non_traps:
                skip_percentage = self.options.puzzle_skip_percentage
                skips = int(non_traps * skip_percentage / 100.0)
                non_skips = non_traps - skips

                for i in range(0, non_skips):
                    pool.append(self.create_item(self.get_filler_item_name()))

                for i in range(0, skips):
                    pool.append(self.create_item("Puzzle Skip"))

            if traps:
                total_weight = sum(self.options.trap_weights.values())

                if total_weight == 0:
                    raise OptionError("Sum of trap weights must be at least one.")

                trap_counts = {name: int(weight * traps / total_weight)
                               for name, weight in self.options.trap_weights.items()}
                
                trap_difference = traps - sum(trap_counts.values())
                if trap_difference > 0:
                    allowed_traps = [name for name in TRAP_ITEMS if self.options.trap_weights[name] > 0]
                    for i in range(0, trap_difference):
                        trap_counts[allowed_traps[i % len(allowed_traps)]] += 1

                for name, count in trap_counts.items():
                    for i in range(0, count):
                        pool.append(self.create_item(name))

        self.multiworld.itempool += pool

    def create_item(self, name: str) -> Item:
        item = ALL_ITEM_TABLE[name]

        classification = item.classification
        if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0 \
                and not item.has_doors and all(painting_id not in self.player_logic.painting_mapping
                                               for painting_id in item.painting_ids) \
                and "pilgrim_painting2" not in item.painting_ids:
            # If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings
            # go nowhere, then this item should not be progression. The Pilgrim Room painting is special and needs to be
            # excluded from this.
            classification = ItemClassification.filler

        return LingoItem(name, classification, item.code, self.player)

    def set_rules(self):
        self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)

    def fill_slot_data(self):
        slot_options = [
            "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels",
            "enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks",
            "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps"
        ]

        slot_data = {
            "seed": self.random.randint(0, 1000000),
            **self.options.as_dict(*slot_options),
        }

        if self.options.shuffle_paintings:
            slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping

        if self.options.shuffle_sunwarps:
            slot_data["sunwarp_permutation"] = self.player_logic.sunwarp_mapping

        return slot_data

    def get_filler_item_name(self) -> str:
        filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
        return self.random.choice(filler_list)