From de58cb03da9f3e7c67f5642c427175839234240b Mon Sep 17 00:00:00 2001 From: qwint Date: Tue, 7 Jan 2025 16:24:19 -0500 Subject: [PATCH 01/21] Core: Pickle hints by value (#4441) --- NetUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetUtils.py b/NetUtils.py index 64a778c55c..d58bbe81e3 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -10,7 +10,7 @@ import websockets from Utils import ByValue, Version -class HintStatus(enum.IntEnum): +class HintStatus(ByValue, enum.IntEnum): HINT_FOUND = 0 HINT_UNSPECIFIED = 1 HINT_NO_PRIORITY = 10 From fe06fe075e8906282086c7e38d2cd4c04aef4d95 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 7 Jan 2025 23:06:48 +0100 Subject: [PATCH 02/21] Factorio: add fluid mining technology to logic requirements (#4385) --- worlds/factorio/Technologies.py | 24 ++++++++++++++++++------ worlds/factorio/data/techs.json | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index 6111462e8c..192cd1fefb 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -63,17 +63,19 @@ class FactorioElement: class Technology(FactorioElement): # maybe make subclass of Location? - has_modifier: bool factorio_id: int progressive: Tuple[str] unlocks: Union[Set[str], bool] # bool case is for progressive technologies + modifiers: list[str] def __init__(self, technology_name: str, factorio_id: int, progressive: Tuple[str] = (), - has_modifier: bool = False, unlocks: Union[Set[str], bool] = None): + modifiers: list[str] = None, unlocks: Union[Set[str], bool] = None): self.name = technology_name self.factorio_id = factorio_id self.progressive = progressive - self.has_modifier = has_modifier + if modifiers is None: + modifiers = [] + self.modifiers = modifiers if unlocks: self.unlocks = unlocks else: @@ -82,6 +84,10 @@ class Technology(FactorioElement): # maybe make subclass of Location? def __hash__(self): return self.factorio_id + @property + def has_modifier(self) -> bool: + return bool(self.modifiers) + def get_custom(self, world, allowed_packs: Set[str], player: int) -> CustomTechnology: return CustomTechnology(self, world, allowed_packs, player) @@ -191,13 +197,14 @@ class Machine(FactorioElement): recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source +mining_with_fluid_sources: set[str] = set() # recipes and technologies can share names in Factorio for technology_name, data in sorted(techs_future.result().items()): technology = Technology( technology_name, factorio_tech_id, - has_modifier=data["has_modifier"], + modifiers=data.get("modifiers", []), unlocks=set(data["unlocks"]) - start_unlocked_recipes, ) factorio_tech_id += 1 @@ -205,7 +212,8 @@ for technology_name, data in sorted(techs_future.result().items()): technology_table[technology_name] = technology for recipe_name in technology.unlocks: recipe_sources.setdefault(recipe_name, set()).add(technology_name) - + if "mining-with-fluid" in technology.modifiers: + mining_with_fluid_sources.add(technology_name) del techs_future recipes = {} @@ -221,6 +229,8 @@ for resource_name, resource_data in resources_future.result().items(): "energy": resource_data["mining_time"], "category": resource_data["category"] } + if "required_fluid" in resource_data: + recipe_sources.setdefault(f"mining-{resource_name}", set()).update(mining_with_fluid_sources) del resources_future for recipe_name, recipe_data in raw_recipes.items(): @@ -431,7 +441,9 @@ for root in sorted_rows: factorio_tech_id += 1 progressive_technology = Technology(root, factorio_tech_id, tuple(progressive), - has_modifier=any(technology_table[tech].has_modifier for tech in progressive), + modifiers=sorted(set.union( + *(set(technology_table[tech].modifiers) for tech in progressive) + )), unlocks=any(technology_table[tech].unlocks for tech in progressive),) progressive_tech_table[root] = progressive_technology.factorio_id progressive_technology_table[root] = progressive_technology diff --git a/worlds/factorio/data/techs.json b/worlds/factorio/data/techs.json index ecb31126e1..ced2b631d6 100644 --- a/worlds/factorio/data/techs.json +++ b/worlds/factorio/data/techs.json @@ -1 +1 @@ -{"advanced-circuit":{"unlocks":["advanced-circuit"],"requires":["plastics"],"has_modifier":false},"advanced-combinators":{"unlocks":["selector-combinator"],"requires":["circuit-network","chemical-science-pack"],"has_modifier":false},"advanced-material-processing":{"unlocks":["steel-furnace"],"requires":["steel-processing","logistic-science-pack"],"has_modifier":false},"advanced-material-processing-2":{"unlocks":["electric-furnace"],"requires":["advanced-material-processing","chemical-science-pack"],"has_modifier":false},"advanced-oil-processing":{"unlocks":["advanced-oil-processing","heavy-oil-cracking","light-oil-cracking","solid-fuel-from-heavy-oil","solid-fuel-from-light-oil"],"requires":["chemical-science-pack"],"has_modifier":false},"artillery":{"unlocks":["artillery-wagon","artillery-turret","artillery-shell"],"requires":["military-4","tank","concrete","radar"],"has_modifier":false},"atomic-bomb":{"unlocks":["atomic-bomb"],"requires":["military-4","kovarex-enrichment-process","rocketry"],"has_modifier":false},"automated-rail-transportation":{"unlocks":["train-stop","rail-signal","rail-chain-signal"],"requires":["railway"],"has_modifier":false},"automation":{"unlocks":["assembling-machine-1","long-handed-inserter"],"requires":["automation-science-pack"],"has_modifier":false},"automation-2":{"unlocks":["assembling-machine-2"],"requires":["automation","steel-processing","logistic-science-pack"],"has_modifier":false},"automation-3":{"unlocks":["assembling-machine-3"],"requires":["speed-module","production-science-pack","electric-engine"],"has_modifier":false},"automation-science-pack":{"unlocks":["automation-science-pack"],"requires":["steam-power","electronics"],"has_modifier":false},"automobilism":{"unlocks":["car"],"requires":["logistics-2","engine"],"has_modifier":false},"battery":{"unlocks":["battery"],"requires":["sulfur-processing"],"has_modifier":false},"battery-equipment":{"unlocks":["battery-equipment"],"requires":["battery","solar-panel-equipment"],"has_modifier":false},"battery-mk2-equipment":{"unlocks":["battery-mk2-equipment"],"requires":["battery-equipment","low-density-structure","power-armor"],"has_modifier":false},"belt-immunity-equipment":{"unlocks":["belt-immunity-equipment"],"requires":["solar-panel-equipment"],"has_modifier":false},"braking-force-1":{"unlocks":{},"requires":["railway","chemical-science-pack"],"has_modifier":true},"braking-force-2":{"unlocks":{},"requires":["braking-force-1"],"has_modifier":true},"braking-force-3":{"unlocks":{},"requires":["braking-force-2","production-science-pack"],"has_modifier":true},"braking-force-4":{"unlocks":{},"requires":["braking-force-3"],"has_modifier":true},"braking-force-5":{"unlocks":{},"requires":["braking-force-4"],"has_modifier":true},"braking-force-6":{"unlocks":{},"requires":["braking-force-5","utility-science-pack"],"has_modifier":true},"braking-force-7":{"unlocks":{},"requires":["braking-force-6"],"has_modifier":true},"bulk-inserter":{"unlocks":["bulk-inserter"],"requires":["fast-inserter","logistics-2","advanced-circuit"],"has_modifier":true},"chemical-science-pack":{"unlocks":["chemical-science-pack"],"requires":["advanced-circuit","sulfur-processing"],"has_modifier":false},"circuit-network":{"unlocks":["arithmetic-combinator","decider-combinator","constant-combinator","power-switch","programmable-speaker","display-panel","iron-stick"],"requires":["logistic-science-pack"],"has_modifier":true},"cliff-explosives":{"unlocks":["cliff-explosives"],"requires":["explosives","military-2"],"has_modifier":true},"coal-liquefaction":{"unlocks":["coal-liquefaction"],"requires":["advanced-oil-processing","production-science-pack"],"has_modifier":false},"concrete":{"unlocks":["concrete","hazard-concrete","refined-concrete","refined-hazard-concrete","iron-stick"],"requires":["advanced-material-processing","automation-2"],"has_modifier":false},"construction-robotics":{"unlocks":["roboport","passive-provider-chest","storage-chest","construction-robot"],"requires":["robotics"],"has_modifier":true},"defender":{"unlocks":["defender-capsule"],"requires":["military-science-pack"],"has_modifier":true},"destroyer":{"unlocks":["destroyer-capsule"],"requires":["military-4","distractor","speed-module"],"has_modifier":false},"discharge-defense-equipment":{"unlocks":["discharge-defense-equipment"],"requires":["laser-turret","military-3","power-armor","solar-panel-equipment"],"has_modifier":false},"distractor":{"unlocks":["distractor-capsule"],"requires":["defender","military-3","laser"],"has_modifier":false},"effect-transmission":{"unlocks":["beacon"],"requires":["processing-unit","production-science-pack"],"has_modifier":false},"efficiency-module":{"unlocks":["efficiency-module"],"requires":["modules"],"has_modifier":false},"efficiency-module-2":{"unlocks":["efficiency-module-2"],"requires":["efficiency-module","processing-unit"],"has_modifier":false},"efficiency-module-3":{"unlocks":["efficiency-module-3"],"requires":["efficiency-module-2","production-science-pack"],"has_modifier":false},"electric-energy-accumulators":{"unlocks":["accumulator"],"requires":["electric-energy-distribution-1","battery"],"has_modifier":false},"electric-energy-distribution-1":{"unlocks":["medium-electric-pole","big-electric-pole","iron-stick"],"requires":["steel-processing","logistic-science-pack"],"has_modifier":false},"electric-energy-distribution-2":{"unlocks":["substation"],"requires":["electric-energy-distribution-1","chemical-science-pack"],"has_modifier":false},"electric-engine":{"unlocks":["electric-engine-unit"],"requires":["lubricant"],"has_modifier":false},"electric-mining-drill":{"unlocks":["electric-mining-drill"],"requires":["automation-science-pack"],"has_modifier":false},"electronics":{"unlocks":["copper-cable","electronic-circuit","lab","inserter","small-electric-pole"],"requires":{},"has_modifier":false},"energy-shield-equipment":{"unlocks":["energy-shield-equipment"],"requires":["solar-panel-equipment","military-science-pack"],"has_modifier":false},"energy-shield-mk2-equipment":{"unlocks":["energy-shield-mk2-equipment"],"requires":["energy-shield-equipment","military-3","low-density-structure","power-armor"],"has_modifier":false},"engine":{"unlocks":["engine-unit"],"requires":["steel-processing","logistic-science-pack"],"has_modifier":false},"exoskeleton-equipment":{"unlocks":["exoskeleton-equipment"],"requires":["processing-unit","electric-engine","solar-panel-equipment"],"has_modifier":false},"explosive-rocketry":{"unlocks":["explosive-rocket"],"requires":["rocketry","military-3"],"has_modifier":false},"explosives":{"unlocks":["explosives"],"requires":["sulfur-processing"],"has_modifier":false},"fast-inserter":{"unlocks":["fast-inserter"],"requires":["automation-science-pack"],"has_modifier":false},"fission-reactor-equipment":{"unlocks":["fission-reactor-equipment"],"requires":["utility-science-pack","power-armor","military-science-pack","nuclear-power"],"has_modifier":false},"flamethrower":{"unlocks":["flamethrower","flamethrower-ammo","flamethrower-turret"],"requires":["flammables","military-science-pack"],"has_modifier":false},"flammables":{"unlocks":{},"requires":["oil-processing"],"has_modifier":false},"fluid-handling":{"unlocks":["storage-tank","pump","barrel","water-barrel","empty-water-barrel","sulfuric-acid-barrel","empty-sulfuric-acid-barrel","crude-oil-barrel","empty-crude-oil-barrel","heavy-oil-barrel","empty-heavy-oil-barrel","light-oil-barrel","empty-light-oil-barrel","petroleum-gas-barrel","empty-petroleum-gas-barrel","lubricant-barrel","empty-lubricant-barrel"],"requires":["automation-2","engine"],"has_modifier":false},"fluid-wagon":{"unlocks":["fluid-wagon"],"requires":["railway","fluid-handling"],"has_modifier":false},"follower-robot-count-1":{"unlocks":{},"requires":["defender"],"has_modifier":true},"follower-robot-count-2":{"unlocks":{},"requires":["follower-robot-count-1"],"has_modifier":true},"follower-robot-count-3":{"unlocks":{},"requires":["follower-robot-count-2","chemical-science-pack"],"has_modifier":true},"follower-robot-count-4":{"unlocks":{},"requires":["follower-robot-count-3","destroyer"],"has_modifier":true},"gate":{"unlocks":["gate"],"requires":["stone-wall","military-2"],"has_modifier":false},"gun-turret":{"unlocks":["gun-turret"],"requires":["automation-science-pack"],"has_modifier":false},"heavy-armor":{"unlocks":["heavy-armor"],"requires":["military","steel-processing"],"has_modifier":false},"inserter-capacity-bonus-1":{"unlocks":{},"requires":["bulk-inserter"],"has_modifier":true},"inserter-capacity-bonus-2":{"unlocks":{},"requires":["inserter-capacity-bonus-1"],"has_modifier":true},"inserter-capacity-bonus-3":{"unlocks":{},"requires":["inserter-capacity-bonus-2","chemical-science-pack"],"has_modifier":true},"inserter-capacity-bonus-4":{"unlocks":{},"requires":["inserter-capacity-bonus-3","production-science-pack"],"has_modifier":true},"inserter-capacity-bonus-5":{"unlocks":{},"requires":["inserter-capacity-bonus-4"],"has_modifier":true},"inserter-capacity-bonus-6":{"unlocks":{},"requires":["inserter-capacity-bonus-5"],"has_modifier":true},"inserter-capacity-bonus-7":{"unlocks":{},"requires":["inserter-capacity-bonus-6","utility-science-pack"],"has_modifier":true},"kovarex-enrichment-process":{"unlocks":["kovarex-enrichment-process","nuclear-fuel"],"requires":["production-science-pack","uranium-processing","rocket-fuel"],"has_modifier":false},"lamp":{"unlocks":["small-lamp"],"requires":["automation-science-pack"],"has_modifier":false},"land-mine":{"unlocks":["land-mine"],"requires":["explosives","military-science-pack"],"has_modifier":false},"landfill":{"unlocks":["landfill"],"requires":["logistic-science-pack"],"has_modifier":false},"laser":{"unlocks":{},"requires":["battery","chemical-science-pack"],"has_modifier":false},"laser-shooting-speed-1":{"unlocks":{},"requires":["laser","military-science-pack"],"has_modifier":true},"laser-shooting-speed-2":{"unlocks":{},"requires":["laser-shooting-speed-1"],"has_modifier":true},"laser-shooting-speed-3":{"unlocks":{},"requires":["laser-shooting-speed-2"],"has_modifier":true},"laser-shooting-speed-4":{"unlocks":{},"requires":["laser-shooting-speed-3"],"has_modifier":true},"laser-shooting-speed-5":{"unlocks":{},"requires":["laser-shooting-speed-4","utility-science-pack"],"has_modifier":true},"laser-shooting-speed-6":{"unlocks":{},"requires":["laser-shooting-speed-5"],"has_modifier":true},"laser-shooting-speed-7":{"unlocks":{},"requires":["laser-shooting-speed-6"],"has_modifier":true},"laser-turret":{"unlocks":["laser-turret"],"requires":["laser","military-science-pack"],"has_modifier":false},"laser-weapons-damage-1":{"unlocks":{},"requires":["laser","military-science-pack"],"has_modifier":true},"laser-weapons-damage-2":{"unlocks":{},"requires":["laser-weapons-damage-1"],"has_modifier":true},"laser-weapons-damage-3":{"unlocks":{},"requires":["laser-weapons-damage-2"],"has_modifier":true},"laser-weapons-damage-4":{"unlocks":{},"requires":["laser-weapons-damage-3"],"has_modifier":true},"laser-weapons-damage-5":{"unlocks":{},"requires":["laser-weapons-damage-4","utility-science-pack"],"has_modifier":true},"laser-weapons-damage-6":{"unlocks":{},"requires":["laser-weapons-damage-5"],"has_modifier":true},"logistic-robotics":{"unlocks":["roboport","passive-provider-chest","storage-chest","logistic-robot"],"requires":["robotics"],"has_modifier":true},"logistic-science-pack":{"unlocks":["logistic-science-pack"],"requires":["automation-science-pack"],"has_modifier":false},"logistic-system":{"unlocks":["active-provider-chest","requester-chest","buffer-chest"],"requires":["utility-science-pack","logistic-robotics"],"has_modifier":true},"logistics":{"unlocks":["underground-belt","splitter"],"requires":["automation-science-pack"],"has_modifier":false},"logistics-2":{"unlocks":["fast-transport-belt","fast-underground-belt","fast-splitter"],"requires":["logistics","logistic-science-pack"],"has_modifier":false},"logistics-3":{"unlocks":["express-transport-belt","express-underground-belt","express-splitter"],"requires":["production-science-pack","lubricant"],"has_modifier":false},"low-density-structure":{"unlocks":["low-density-structure"],"requires":["advanced-material-processing","chemical-science-pack"],"has_modifier":false},"lubricant":{"unlocks":["lubricant"],"requires":["advanced-oil-processing"],"has_modifier":false},"military":{"unlocks":["submachine-gun","shotgun","shotgun-shell"],"requires":["automation-science-pack"],"has_modifier":false},"military-2":{"unlocks":["piercing-rounds-magazine","grenade"],"requires":["military","steel-processing","logistic-science-pack"],"has_modifier":false},"military-3":{"unlocks":["poison-capsule","slowdown-capsule","combat-shotgun"],"requires":["chemical-science-pack","military-science-pack"],"has_modifier":false},"military-4":{"unlocks":["piercing-shotgun-shell","cluster-grenade"],"requires":["military-3","utility-science-pack","explosives"],"has_modifier":false},"military-science-pack":{"unlocks":["military-science-pack"],"requires":["military-2","stone-wall"],"has_modifier":false},"mining-productivity-1":{"unlocks":{},"requires":["advanced-circuit"],"has_modifier":true},"mining-productivity-2":{"unlocks":{},"requires":["mining-productivity-1","chemical-science-pack"],"has_modifier":true},"mining-productivity-3":{"unlocks":{},"requires":["mining-productivity-2","production-science-pack","utility-science-pack"],"has_modifier":true},"modular-armor":{"unlocks":["modular-armor"],"requires":["heavy-armor","advanced-circuit"],"has_modifier":false},"modules":{"unlocks":{},"requires":["advanced-circuit"],"has_modifier":false},"night-vision-equipment":{"unlocks":["night-vision-equipment"],"requires":["solar-panel-equipment"],"has_modifier":false},"nuclear-fuel-reprocessing":{"unlocks":["nuclear-fuel-reprocessing"],"requires":["nuclear-power","production-science-pack"],"has_modifier":false},"nuclear-power":{"unlocks":["nuclear-reactor","heat-exchanger","heat-pipe","steam-turbine","uranium-fuel-cell"],"requires":["uranium-processing"],"has_modifier":false},"oil-gathering":{"unlocks":["pumpjack"],"requires":["fluid-handling"],"has_modifier":false},"oil-processing":{"unlocks":["oil-refinery","chemical-plant","basic-oil-processing","solid-fuel-from-petroleum-gas"],"requires":["oil-gathering"],"has_modifier":false},"personal-laser-defense-equipment":{"unlocks":["personal-laser-defense-equipment"],"requires":["laser-turret","military-3","low-density-structure","power-armor","solar-panel-equipment"],"has_modifier":false},"personal-roboport-equipment":{"unlocks":["personal-roboport-equipment"],"requires":["construction-robotics","solar-panel-equipment"],"has_modifier":false},"personal-roboport-mk2-equipment":{"unlocks":["personal-roboport-mk2-equipment"],"requires":["personal-roboport-equipment","utility-science-pack"],"has_modifier":false},"physical-projectile-damage-1":{"unlocks":{},"requires":["military"],"has_modifier":true},"physical-projectile-damage-2":{"unlocks":{},"requires":["physical-projectile-damage-1","logistic-science-pack"],"has_modifier":true},"physical-projectile-damage-3":{"unlocks":{},"requires":["physical-projectile-damage-2","military-science-pack"],"has_modifier":true},"physical-projectile-damage-4":{"unlocks":{},"requires":["physical-projectile-damage-3"],"has_modifier":true},"physical-projectile-damage-5":{"unlocks":{},"requires":["physical-projectile-damage-4","chemical-science-pack"],"has_modifier":true},"physical-projectile-damage-6":{"unlocks":{},"requires":["physical-projectile-damage-5","utility-science-pack"],"has_modifier":true},"plastics":{"unlocks":["plastic-bar"],"requires":["oil-processing"],"has_modifier":false},"power-armor":{"unlocks":["power-armor"],"requires":["modular-armor","electric-engine","processing-unit"],"has_modifier":false},"power-armor-mk2":{"unlocks":["power-armor-mk2"],"requires":["power-armor","military-4","speed-module-2","efficiency-module-2"],"has_modifier":false},"processing-unit":{"unlocks":["processing-unit"],"requires":["chemical-science-pack"],"has_modifier":false},"production-science-pack":{"unlocks":["production-science-pack"],"requires":["productivity-module","advanced-material-processing-2","railway"],"has_modifier":false},"productivity-module":{"unlocks":["productivity-module"],"requires":["modules"],"has_modifier":false},"productivity-module-2":{"unlocks":["productivity-module-2"],"requires":["productivity-module","processing-unit"],"has_modifier":false},"productivity-module-3":{"unlocks":["productivity-module-3"],"requires":["productivity-module-2","production-science-pack"],"has_modifier":false},"radar":{"unlocks":["radar"],"requires":["automation-science-pack"],"has_modifier":false},"railway":{"unlocks":["rail","locomotive","cargo-wagon","iron-stick"],"requires":["logistics-2","engine"],"has_modifier":false},"refined-flammables-1":{"unlocks":{},"requires":["flamethrower"],"has_modifier":true},"refined-flammables-2":{"unlocks":{},"requires":["refined-flammables-1"],"has_modifier":true},"refined-flammables-3":{"unlocks":{},"requires":["refined-flammables-2","chemical-science-pack"],"has_modifier":true},"refined-flammables-4":{"unlocks":{},"requires":["refined-flammables-3","utility-science-pack"],"has_modifier":true},"refined-flammables-5":{"unlocks":{},"requires":["refined-flammables-4"],"has_modifier":true},"refined-flammables-6":{"unlocks":{},"requires":["refined-flammables-5"],"has_modifier":true},"repair-pack":{"unlocks":["repair-pack"],"requires":["automation-science-pack"],"has_modifier":false},"research-speed-1":{"unlocks":{},"requires":["automation-2"],"has_modifier":true},"research-speed-2":{"unlocks":{},"requires":["research-speed-1"],"has_modifier":true},"research-speed-3":{"unlocks":{},"requires":["research-speed-2","chemical-science-pack"],"has_modifier":true},"research-speed-4":{"unlocks":{},"requires":["research-speed-3"],"has_modifier":true},"research-speed-5":{"unlocks":{},"requires":["research-speed-4","production-science-pack"],"has_modifier":true},"research-speed-6":{"unlocks":{},"requires":["research-speed-5","utility-science-pack"],"has_modifier":true},"robotics":{"unlocks":["flying-robot-frame"],"requires":["electric-engine","battery"],"has_modifier":false},"rocket-fuel":{"unlocks":["rocket-fuel"],"requires":["flammables","advanced-oil-processing"],"has_modifier":false},"rocket-silo":{"unlocks":["rocket-silo","rocket-part","cargo-landing-pad","satellite"],"requires":["concrete","rocket-fuel","electric-energy-accumulators","solar-energy","utility-science-pack","speed-module-3","productivity-module-3","radar"],"has_modifier":false},"rocketry":{"unlocks":["rocket-launcher","rocket"],"requires":["explosives","flammables","military-science-pack"],"has_modifier":false},"solar-energy":{"unlocks":["solar-panel"],"requires":["steel-processing","logistic-science-pack"],"has_modifier":false},"solar-panel-equipment":{"unlocks":["solar-panel-equipment"],"requires":["modular-armor","solar-energy"],"has_modifier":false},"space-science-pack":{"unlocks":{},"requires":["rocket-silo"],"has_modifier":false},"speed-module":{"unlocks":["speed-module"],"requires":["modules"],"has_modifier":false},"speed-module-2":{"unlocks":["speed-module-2"],"requires":["speed-module","processing-unit"],"has_modifier":false},"speed-module-3":{"unlocks":["speed-module-3"],"requires":["speed-module-2","production-science-pack"],"has_modifier":false},"spidertron":{"unlocks":["spidertron"],"requires":["military-4","exoskeleton-equipment","fission-reactor-equipment","rocketry","efficiency-module-3","radar"],"has_modifier":false},"steam-power":{"unlocks":["pipe","pipe-to-ground","offshore-pump","boiler","steam-engine"],"requires":{},"has_modifier":false},"steel-axe":{"unlocks":{},"requires":["steel-processing"],"has_modifier":true},"steel-processing":{"unlocks":["steel-plate","steel-chest"],"requires":["automation-science-pack"],"has_modifier":false},"stone-wall":{"unlocks":["stone-wall"],"requires":["automation-science-pack"],"has_modifier":false},"stronger-explosives-1":{"unlocks":{},"requires":["military-2"],"has_modifier":true},"stronger-explosives-2":{"unlocks":{},"requires":["stronger-explosives-1","military-science-pack"],"has_modifier":true},"stronger-explosives-3":{"unlocks":{},"requires":["stronger-explosives-2","chemical-science-pack"],"has_modifier":true},"stronger-explosives-4":{"unlocks":{},"requires":["stronger-explosives-3","utility-science-pack"],"has_modifier":true},"stronger-explosives-5":{"unlocks":{},"requires":["stronger-explosives-4"],"has_modifier":true},"stronger-explosives-6":{"unlocks":{},"requires":["stronger-explosives-5"],"has_modifier":true},"sulfur-processing":{"unlocks":["sulfuric-acid","sulfur"],"requires":["oil-processing"],"has_modifier":false},"tank":{"unlocks":["tank","cannon-shell","explosive-cannon-shell"],"requires":["automobilism","military-3","explosives"],"has_modifier":false},"toolbelt":{"unlocks":{},"requires":["logistic-science-pack"],"has_modifier":true},"uranium-ammo":{"unlocks":["uranium-rounds-magazine","uranium-cannon-shell","explosive-uranium-cannon-shell"],"requires":["uranium-processing","military-4","tank"],"has_modifier":false},"uranium-mining":{"unlocks":{},"requires":["chemical-science-pack","concrete"],"has_modifier":true},"uranium-processing":{"unlocks":["centrifuge","uranium-processing"],"requires":["uranium-mining"],"has_modifier":false},"utility-science-pack":{"unlocks":["utility-science-pack"],"requires":["robotics","processing-unit","low-density-structure"],"has_modifier":false},"weapon-shooting-speed-1":{"unlocks":{},"requires":["military"],"has_modifier":true},"weapon-shooting-speed-2":{"unlocks":{},"requires":["weapon-shooting-speed-1","logistic-science-pack"],"has_modifier":true},"weapon-shooting-speed-3":{"unlocks":{},"requires":["weapon-shooting-speed-2","military-science-pack"],"has_modifier":true},"weapon-shooting-speed-4":{"unlocks":{},"requires":["weapon-shooting-speed-3"],"has_modifier":true},"weapon-shooting-speed-5":{"unlocks":{},"requires":["weapon-shooting-speed-4","chemical-science-pack"],"has_modifier":true},"weapon-shooting-speed-6":{"unlocks":{},"requires":["weapon-shooting-speed-5","utility-science-pack"],"has_modifier":true},"worker-robots-speed-1":{"unlocks":{},"requires":["robotics"],"has_modifier":true},"worker-robots-speed-2":{"unlocks":{},"requires":["worker-robots-speed-1"],"has_modifier":true},"worker-robots-speed-3":{"unlocks":{},"requires":["worker-robots-speed-2","utility-science-pack"],"has_modifier":true},"worker-robots-speed-4":{"unlocks":{},"requires":["worker-robots-speed-3"],"has_modifier":true},"worker-robots-speed-5":{"unlocks":{},"requires":["worker-robots-speed-4","production-science-pack"],"has_modifier":true},"worker-robots-storage-1":{"unlocks":{},"requires":["robotics"],"has_modifier":true},"worker-robots-storage-2":{"unlocks":{},"requires":["worker-robots-storage-1","production-science-pack"],"has_modifier":true},"worker-robots-storage-3":{"unlocks":{},"requires":["worker-robots-storage-2","utility-science-pack"],"has_modifier":true}} \ No newline at end of file +{"advanced-circuit":{"unlocks":["advanced-circuit"],"requires":["plastics"],"infinite":false},"advanced-combinators":{"unlocks":["selector-combinator"],"requires":["circuit-network","chemical-science-pack"],"infinite":false},"advanced-material-processing":{"unlocks":["steel-furnace"],"requires":["steel-processing","logistic-science-pack"],"infinite":false},"advanced-material-processing-2":{"unlocks":["electric-furnace"],"requires":["advanced-material-processing","chemical-science-pack"],"infinite":false},"advanced-oil-processing":{"unlocks":["advanced-oil-processing","heavy-oil-cracking","light-oil-cracking","solid-fuel-from-heavy-oil","solid-fuel-from-light-oil"],"requires":["chemical-science-pack"],"infinite":false},"artillery":{"unlocks":["artillery-wagon","artillery-turret","artillery-shell"],"requires":["military-4","tank","concrete","radar"],"infinite":false},"atomic-bomb":{"unlocks":["atomic-bomb"],"requires":["military-4","kovarex-enrichment-process","rocketry"],"infinite":false},"automated-rail-transportation":{"unlocks":["train-stop","rail-signal","rail-chain-signal"],"requires":["railway"],"infinite":false},"automation":{"unlocks":["assembling-machine-1","long-handed-inserter"],"requires":["automation-science-pack"],"infinite":false},"automation-2":{"unlocks":["assembling-machine-2"],"requires":["automation","steel-processing","logistic-science-pack"],"infinite":false},"automation-3":{"unlocks":["assembling-machine-3"],"requires":["speed-module","production-science-pack","electric-engine"],"infinite":false},"automation-science-pack":{"unlocks":["automation-science-pack"],"requires":["steam-power","electronics"],"infinite":false},"automobilism":{"unlocks":["car"],"requires":["logistics-2","engine"],"infinite":false},"battery":{"unlocks":["battery"],"requires":["sulfur-processing"],"infinite":false},"battery-equipment":{"unlocks":["battery-equipment"],"requires":["battery","solar-panel-equipment"],"infinite":false},"battery-mk2-equipment":{"unlocks":["battery-mk2-equipment"],"requires":["battery-equipment","low-density-structure","power-armor"],"infinite":false},"belt-immunity-equipment":{"unlocks":["belt-immunity-equipment"],"requires":["solar-panel-equipment"],"infinite":false},"braking-force-1":{"unlocks":{},"requires":["railway","chemical-science-pack"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"braking-force-2":{"unlocks":{},"requires":["braking-force-1"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"braking-force-3":{"unlocks":{},"requires":["braking-force-2","production-science-pack"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"braking-force-4":{"unlocks":{},"requires":["braking-force-3"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"braking-force-5":{"unlocks":{},"requires":["braking-force-4"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"braking-force-6":{"unlocks":{},"requires":["braking-force-5","utility-science-pack"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"braking-force-7":{"unlocks":{},"requires":["braking-force-6"],"infinite":false,"modifiers":["train-braking-force-bonus"]},"bulk-inserter":{"unlocks":["bulk-inserter"],"requires":["fast-inserter","logistics-2","advanced-circuit"],"infinite":false,"modifiers":["bulk-inserter-capacity-bonus"]},"chemical-science-pack":{"unlocks":["chemical-science-pack"],"requires":["advanced-circuit","sulfur-processing"],"infinite":false},"circuit-network":{"unlocks":["arithmetic-combinator","decider-combinator","constant-combinator","power-switch","programmable-speaker","display-panel","iron-stick"],"requires":["logistic-science-pack"],"infinite":false,"modifiers":["unlock-circuit-network"]},"cliff-explosives":{"unlocks":["cliff-explosives"],"requires":["explosives","military-2"],"infinite":false,"modifiers":["cliff-deconstruction-enabled"]},"coal-liquefaction":{"unlocks":["coal-liquefaction"],"requires":["advanced-oil-processing","production-science-pack"],"infinite":false},"concrete":{"unlocks":["concrete","hazard-concrete","refined-concrete","refined-hazard-concrete","iron-stick"],"requires":["advanced-material-processing","automation-2"],"infinite":false},"construction-robotics":{"unlocks":["roboport","passive-provider-chest","storage-chest","construction-robot"],"requires":["robotics"],"infinite":false,"modifiers":["create-ghost-on-entity-death"]},"defender":{"unlocks":["defender-capsule"],"requires":["military-science-pack"],"infinite":false,"modifiers":["maximum-following-robots-count"]},"destroyer":{"unlocks":["destroyer-capsule"],"requires":["military-4","distractor","speed-module"],"infinite":false},"discharge-defense-equipment":{"unlocks":["discharge-defense-equipment"],"requires":["laser-turret","military-3","power-armor","solar-panel-equipment"],"infinite":false},"distractor":{"unlocks":["distractor-capsule"],"requires":["defender","military-3","laser"],"infinite":false},"effect-transmission":{"unlocks":["beacon"],"requires":["processing-unit","production-science-pack"],"infinite":false},"efficiency-module":{"unlocks":["efficiency-module"],"requires":["modules"],"infinite":false},"efficiency-module-2":{"unlocks":["efficiency-module-2"],"requires":["efficiency-module","processing-unit"],"infinite":false},"efficiency-module-3":{"unlocks":["efficiency-module-3"],"requires":["efficiency-module-2","production-science-pack"],"infinite":false},"electric-energy-accumulators":{"unlocks":["accumulator"],"requires":["electric-energy-distribution-1","battery"],"infinite":false},"electric-energy-distribution-1":{"unlocks":["medium-electric-pole","big-electric-pole","iron-stick"],"requires":["steel-processing","logistic-science-pack"],"infinite":false},"electric-energy-distribution-2":{"unlocks":["substation"],"requires":["electric-energy-distribution-1","chemical-science-pack"],"infinite":false},"electric-engine":{"unlocks":["electric-engine-unit"],"requires":["lubricant"],"infinite":false},"electric-mining-drill":{"unlocks":["electric-mining-drill"],"requires":["automation-science-pack"],"infinite":false},"electronics":{"unlocks":["copper-cable","electronic-circuit","lab","inserter","small-electric-pole"],"requires":{},"infinite":false},"energy-shield-equipment":{"unlocks":["energy-shield-equipment"],"requires":["solar-panel-equipment","military-science-pack"],"infinite":false},"energy-shield-mk2-equipment":{"unlocks":["energy-shield-mk2-equipment"],"requires":["energy-shield-equipment","military-3","low-density-structure","power-armor"],"infinite":false},"engine":{"unlocks":["engine-unit"],"requires":["steel-processing","logistic-science-pack"],"infinite":false},"exoskeleton-equipment":{"unlocks":["exoskeleton-equipment"],"requires":["processing-unit","electric-engine","solar-panel-equipment"],"infinite":false},"explosive-rocketry":{"unlocks":["explosive-rocket"],"requires":["rocketry","military-3"],"infinite":false},"explosives":{"unlocks":["explosives"],"requires":["sulfur-processing"],"infinite":false},"fast-inserter":{"unlocks":["fast-inserter"],"requires":["automation-science-pack"],"infinite":false},"fission-reactor-equipment":{"unlocks":["fission-reactor-equipment"],"requires":["utility-science-pack","power-armor","military-science-pack","nuclear-power"],"infinite":false},"flamethrower":{"unlocks":["flamethrower","flamethrower-ammo","flamethrower-turret"],"requires":["flammables","military-science-pack"],"infinite":false},"flammables":{"unlocks":{},"requires":["oil-processing"],"infinite":false},"fluid-handling":{"unlocks":["storage-tank","pump","barrel","water-barrel","empty-water-barrel","sulfuric-acid-barrel","empty-sulfuric-acid-barrel","crude-oil-barrel","empty-crude-oil-barrel","heavy-oil-barrel","empty-heavy-oil-barrel","light-oil-barrel","empty-light-oil-barrel","petroleum-gas-barrel","empty-petroleum-gas-barrel","lubricant-barrel","empty-lubricant-barrel"],"requires":["automation-2","engine"],"infinite":false},"fluid-wagon":{"unlocks":["fluid-wagon"],"requires":["railway","fluid-handling"],"infinite":false},"follower-robot-count-1":{"unlocks":{},"requires":["defender"],"infinite":false,"modifiers":["maximum-following-robots-count"]},"follower-robot-count-2":{"unlocks":{},"requires":["follower-robot-count-1"],"infinite":false,"modifiers":["maximum-following-robots-count"]},"follower-robot-count-3":{"unlocks":{},"requires":["follower-robot-count-2","chemical-science-pack"],"infinite":false,"modifiers":["maximum-following-robots-count"]},"follower-robot-count-4":{"unlocks":{},"requires":["follower-robot-count-3","destroyer"],"infinite":false,"modifiers":["maximum-following-robots-count"]},"gate":{"unlocks":["gate"],"requires":["stone-wall","military-2"],"infinite":false},"gun-turret":{"unlocks":["gun-turret"],"requires":["automation-science-pack"],"infinite":false},"heavy-armor":{"unlocks":["heavy-armor"],"requires":["military","steel-processing"],"infinite":false},"inserter-capacity-bonus-1":{"unlocks":{},"requires":["bulk-inserter"],"infinite":false,"modifiers":["bulk-inserter-capacity-bonus"]},"inserter-capacity-bonus-2":{"unlocks":{},"requires":["inserter-capacity-bonus-1"],"infinite":false,"modifiers":["inserter-stack-size-bonus","bulk-inserter-capacity-bonus"]},"inserter-capacity-bonus-3":{"unlocks":{},"requires":["inserter-capacity-bonus-2","chemical-science-pack"],"infinite":false,"modifiers":["bulk-inserter-capacity-bonus"]},"inserter-capacity-bonus-4":{"unlocks":{},"requires":["inserter-capacity-bonus-3","production-science-pack"],"infinite":false,"modifiers":["bulk-inserter-capacity-bonus"]},"inserter-capacity-bonus-5":{"unlocks":{},"requires":["inserter-capacity-bonus-4"],"infinite":false,"modifiers":["bulk-inserter-capacity-bonus"]},"inserter-capacity-bonus-6":{"unlocks":{},"requires":["inserter-capacity-bonus-5"],"infinite":false,"modifiers":["bulk-inserter-capacity-bonus"]},"inserter-capacity-bonus-7":{"unlocks":{},"requires":["inserter-capacity-bonus-6","utility-science-pack"],"infinite":false,"modifiers":["inserter-stack-size-bonus","bulk-inserter-capacity-bonus"]},"kovarex-enrichment-process":{"unlocks":["kovarex-enrichment-process","nuclear-fuel"],"requires":["production-science-pack","uranium-processing","rocket-fuel"],"infinite":false},"lamp":{"unlocks":["small-lamp"],"requires":["automation-science-pack"],"infinite":false},"land-mine":{"unlocks":["land-mine"],"requires":["explosives","military-science-pack"],"infinite":false},"landfill":{"unlocks":["landfill"],"requires":["logistic-science-pack"],"infinite":false},"laser":{"unlocks":{},"requires":["battery","chemical-science-pack"],"infinite":false},"laser-shooting-speed-1":{"unlocks":{},"requires":["laser","military-science-pack"],"infinite":false,"modifiers":["gun-speed"]},"laser-shooting-speed-2":{"unlocks":{},"requires":["laser-shooting-speed-1"],"infinite":false,"modifiers":["gun-speed"]},"laser-shooting-speed-3":{"unlocks":{},"requires":["laser-shooting-speed-2"],"infinite":false,"modifiers":["gun-speed"]},"laser-shooting-speed-4":{"unlocks":{},"requires":["laser-shooting-speed-3"],"infinite":false,"modifiers":["gun-speed"]},"laser-shooting-speed-5":{"unlocks":{},"requires":["laser-shooting-speed-4","utility-science-pack"],"infinite":false,"modifiers":["gun-speed"]},"laser-shooting-speed-6":{"unlocks":{},"requires":["laser-shooting-speed-5"],"infinite":false,"modifiers":["gun-speed"]},"laser-shooting-speed-7":{"unlocks":{},"requires":["laser-shooting-speed-6"],"infinite":false,"modifiers":["gun-speed"]},"laser-turret":{"unlocks":["laser-turret"],"requires":["laser","military-science-pack"],"infinite":false},"laser-weapons-damage-1":{"unlocks":{},"requires":["laser","military-science-pack"],"infinite":false,"modifiers":["ammo-damage"]},"laser-weapons-damage-2":{"unlocks":{},"requires":["laser-weapons-damage-1"],"infinite":false,"modifiers":["ammo-damage"]},"laser-weapons-damage-3":{"unlocks":{},"requires":["laser-weapons-damage-2"],"infinite":false,"modifiers":["ammo-damage"]},"laser-weapons-damage-4":{"unlocks":{},"requires":["laser-weapons-damage-3"],"infinite":false,"modifiers":["ammo-damage"]},"laser-weapons-damage-5":{"unlocks":{},"requires":["laser-weapons-damage-4","utility-science-pack"],"infinite":false,"modifiers":["ammo-damage","ammo-damage"]},"laser-weapons-damage-6":{"unlocks":{},"requires":["laser-weapons-damage-5"],"infinite":false,"modifiers":["ammo-damage","ammo-damage","ammo-damage"]},"logistic-robotics":{"unlocks":["roboport","passive-provider-chest","storage-chest","logistic-robot"],"requires":["robotics"],"infinite":false,"modifiers":["character-logistic-requests","character-logistic-trash-slots"]},"logistic-science-pack":{"unlocks":["logistic-science-pack"],"requires":["automation-science-pack"],"infinite":false},"logistic-system":{"unlocks":["active-provider-chest","requester-chest","buffer-chest"],"requires":["utility-science-pack","logistic-robotics"],"infinite":false,"modifiers":["vehicle-logistics"]},"logistics":{"unlocks":["underground-belt","splitter"],"requires":["automation-science-pack"],"infinite":false},"logistics-2":{"unlocks":["fast-transport-belt","fast-underground-belt","fast-splitter"],"requires":["logistics","logistic-science-pack"],"infinite":false},"logistics-3":{"unlocks":["express-transport-belt","express-underground-belt","express-splitter"],"requires":["production-science-pack","lubricant"],"infinite":false},"low-density-structure":{"unlocks":["low-density-structure"],"requires":["advanced-material-processing","chemical-science-pack"],"infinite":false},"lubricant":{"unlocks":["lubricant"],"requires":["advanced-oil-processing"],"infinite":false},"military":{"unlocks":["submachine-gun","shotgun","shotgun-shell"],"requires":["automation-science-pack"],"infinite":false},"military-2":{"unlocks":["piercing-rounds-magazine","grenade"],"requires":["military","steel-processing","logistic-science-pack"],"infinite":false},"military-3":{"unlocks":["poison-capsule","slowdown-capsule","combat-shotgun"],"requires":["chemical-science-pack","military-science-pack"],"infinite":false},"military-4":{"unlocks":["piercing-shotgun-shell","cluster-grenade"],"requires":["military-3","utility-science-pack","explosives"],"infinite":false},"military-science-pack":{"unlocks":["military-science-pack"],"requires":["military-2","stone-wall"],"infinite":false},"mining-productivity-1":{"unlocks":{},"requires":["advanced-circuit"],"infinite":false,"modifiers":["mining-drill-productivity-bonus"]},"mining-productivity-2":{"unlocks":{},"requires":["mining-productivity-1","chemical-science-pack"],"infinite":false,"modifiers":["mining-drill-productivity-bonus"]},"mining-productivity-3":{"unlocks":{},"requires":["mining-productivity-2","production-science-pack","utility-science-pack"],"infinite":false,"modifiers":["mining-drill-productivity-bonus"]},"modular-armor":{"unlocks":["modular-armor"],"requires":["heavy-armor","advanced-circuit"],"infinite":false},"modules":{"unlocks":{},"requires":["advanced-circuit"],"infinite":false},"night-vision-equipment":{"unlocks":["night-vision-equipment"],"requires":["solar-panel-equipment"],"infinite":false},"nuclear-fuel-reprocessing":{"unlocks":["nuclear-fuel-reprocessing"],"requires":["nuclear-power","production-science-pack"],"infinite":false},"nuclear-power":{"unlocks":["nuclear-reactor","heat-exchanger","heat-pipe","steam-turbine","uranium-fuel-cell"],"requires":["uranium-processing"],"infinite":false},"oil-gathering":{"unlocks":["pumpjack"],"requires":["fluid-handling"],"infinite":false},"oil-processing":{"unlocks":["oil-refinery","chemical-plant","basic-oil-processing","solid-fuel-from-petroleum-gas"],"requires":["oil-gathering"],"infinite":false},"personal-laser-defense-equipment":{"unlocks":["personal-laser-defense-equipment"],"requires":["laser-turret","military-3","low-density-structure","power-armor","solar-panel-equipment"],"infinite":false},"personal-roboport-equipment":{"unlocks":["personal-roboport-equipment"],"requires":["construction-robotics","solar-panel-equipment"],"infinite":false},"personal-roboport-mk2-equipment":{"unlocks":["personal-roboport-mk2-equipment"],"requires":["personal-roboport-equipment","utility-science-pack"],"infinite":false},"physical-projectile-damage-1":{"unlocks":{},"requires":["military"],"infinite":false,"modifiers":["ammo-damage","turret-attack","ammo-damage"]},"physical-projectile-damage-2":{"unlocks":{},"requires":["physical-projectile-damage-1","logistic-science-pack"],"infinite":false,"modifiers":["ammo-damage","turret-attack","ammo-damage"]},"physical-projectile-damage-3":{"unlocks":{},"requires":["physical-projectile-damage-2","military-science-pack"],"infinite":false,"modifiers":["ammo-damage","turret-attack","ammo-damage"]},"physical-projectile-damage-4":{"unlocks":{},"requires":["physical-projectile-damage-3"],"infinite":false,"modifiers":["ammo-damage","turret-attack","ammo-damage"]},"physical-projectile-damage-5":{"unlocks":{},"requires":["physical-projectile-damage-4","chemical-science-pack"],"infinite":false,"modifiers":["ammo-damage","turret-attack","ammo-damage","ammo-damage"]},"physical-projectile-damage-6":{"unlocks":{},"requires":["physical-projectile-damage-5","utility-science-pack"],"infinite":false,"modifiers":["ammo-damage","turret-attack","ammo-damage","ammo-damage"]},"plastics":{"unlocks":["plastic-bar"],"requires":["oil-processing"],"infinite":false},"power-armor":{"unlocks":["power-armor"],"requires":["modular-armor","electric-engine","processing-unit"],"infinite":false},"power-armor-mk2":{"unlocks":["power-armor-mk2"],"requires":["power-armor","military-4","speed-module-2","efficiency-module-2"],"infinite":false},"processing-unit":{"unlocks":["processing-unit"],"requires":["chemical-science-pack"],"infinite":false},"production-science-pack":{"unlocks":["production-science-pack"],"requires":["productivity-module","advanced-material-processing-2","railway"],"infinite":false},"productivity-module":{"unlocks":["productivity-module"],"requires":["modules"],"infinite":false},"productivity-module-2":{"unlocks":["productivity-module-2"],"requires":["productivity-module","processing-unit"],"infinite":false},"productivity-module-3":{"unlocks":["productivity-module-3"],"requires":["productivity-module-2","production-science-pack"],"infinite":false},"radar":{"unlocks":["radar"],"requires":["automation-science-pack"],"infinite":false},"railway":{"unlocks":["rail","locomotive","cargo-wagon","iron-stick"],"requires":["logistics-2","engine"],"infinite":false},"refined-flammables-1":{"unlocks":{},"requires":["flamethrower"],"infinite":false,"modifiers":["ammo-damage","turret-attack"]},"refined-flammables-2":{"unlocks":{},"requires":["refined-flammables-1"],"infinite":false,"modifiers":["ammo-damage","turret-attack"]},"refined-flammables-3":{"unlocks":{},"requires":["refined-flammables-2","chemical-science-pack"],"infinite":false,"modifiers":["ammo-damage","turret-attack"]},"refined-flammables-4":{"unlocks":{},"requires":["refined-flammables-3","utility-science-pack"],"infinite":false,"modifiers":["ammo-damage","turret-attack"]},"refined-flammables-5":{"unlocks":{},"requires":["refined-flammables-4"],"infinite":false,"modifiers":["ammo-damage","turret-attack"]},"refined-flammables-6":{"unlocks":{},"requires":["refined-flammables-5"],"infinite":false,"modifiers":["ammo-damage","turret-attack"]},"repair-pack":{"unlocks":["repair-pack"],"requires":["automation-science-pack"],"infinite":false},"research-speed-1":{"unlocks":{},"requires":["automation-2"],"infinite":false,"modifiers":["laboratory-speed"]},"research-speed-2":{"unlocks":{},"requires":["research-speed-1"],"infinite":false,"modifiers":["laboratory-speed"]},"research-speed-3":{"unlocks":{},"requires":["research-speed-2","chemical-science-pack"],"infinite":false,"modifiers":["laboratory-speed"]},"research-speed-4":{"unlocks":{},"requires":["research-speed-3"],"infinite":false,"modifiers":["laboratory-speed"]},"research-speed-5":{"unlocks":{},"requires":["research-speed-4","production-science-pack"],"infinite":false,"modifiers":["laboratory-speed"]},"research-speed-6":{"unlocks":{},"requires":["research-speed-5","utility-science-pack"],"infinite":false,"modifiers":["laboratory-speed"]},"robotics":{"unlocks":["flying-robot-frame"],"requires":["electric-engine","battery"],"infinite":false},"rocket-fuel":{"unlocks":["rocket-fuel"],"requires":["flammables","advanced-oil-processing"],"infinite":false},"rocket-silo":{"unlocks":["rocket-silo","rocket-part","cargo-landing-pad","satellite"],"requires":["concrete","rocket-fuel","electric-energy-accumulators","solar-energy","utility-science-pack","speed-module-3","productivity-module-3","radar"],"infinite":false},"rocketry":{"unlocks":["rocket-launcher","rocket"],"requires":["explosives","flammables","military-science-pack"],"infinite":false},"solar-energy":{"unlocks":["solar-panel"],"requires":["steel-processing","logistic-science-pack"],"infinite":false},"solar-panel-equipment":{"unlocks":["solar-panel-equipment"],"requires":["modular-armor","solar-energy"],"infinite":false},"space-science-pack":{"unlocks":{},"requires":["rocket-silo"],"infinite":false},"speed-module":{"unlocks":["speed-module"],"requires":["modules"],"infinite":false},"speed-module-2":{"unlocks":["speed-module-2"],"requires":["speed-module","processing-unit"],"infinite":false},"speed-module-3":{"unlocks":["speed-module-3"],"requires":["speed-module-2","production-science-pack"],"infinite":false},"spidertron":{"unlocks":["spidertron"],"requires":["military-4","exoskeleton-equipment","fission-reactor-equipment","rocketry","efficiency-module-3","radar"],"infinite":false},"steam-power":{"unlocks":["pipe","pipe-to-ground","offshore-pump","boiler","steam-engine"],"requires":{},"infinite":false},"steel-axe":{"unlocks":{},"requires":["steel-processing"],"infinite":false,"modifiers":["character-mining-speed"]},"steel-processing":{"unlocks":["steel-plate","steel-chest"],"requires":["automation-science-pack"],"infinite":false},"stone-wall":{"unlocks":["stone-wall"],"requires":["automation-science-pack"],"infinite":false},"stronger-explosives-1":{"unlocks":{},"requires":["military-2"],"infinite":false,"modifiers":["ammo-damage"]},"stronger-explosives-2":{"unlocks":{},"requires":["stronger-explosives-1","military-science-pack"],"infinite":false,"modifiers":["ammo-damage","ammo-damage"]},"stronger-explosives-3":{"unlocks":{},"requires":["stronger-explosives-2","chemical-science-pack"],"infinite":false,"modifiers":["ammo-damage","ammo-damage","ammo-damage"]},"stronger-explosives-4":{"unlocks":{},"requires":["stronger-explosives-3","utility-science-pack"],"infinite":false,"modifiers":["ammo-damage","ammo-damage","ammo-damage"]},"stronger-explosives-5":{"unlocks":{},"requires":["stronger-explosives-4"],"infinite":false,"modifiers":["ammo-damage","ammo-damage","ammo-damage"]},"stronger-explosives-6":{"unlocks":{},"requires":["stronger-explosives-5"],"infinite":false,"modifiers":["ammo-damage","ammo-damage","ammo-damage"]},"sulfur-processing":{"unlocks":["sulfuric-acid","sulfur"],"requires":["oil-processing"],"infinite":false},"tank":{"unlocks":["tank","cannon-shell","explosive-cannon-shell"],"requires":["automobilism","military-3","explosives"],"infinite":false},"toolbelt":{"unlocks":{},"requires":["logistic-science-pack"],"infinite":false,"modifiers":["character-inventory-slots-bonus"]},"uranium-ammo":{"unlocks":["uranium-rounds-magazine","uranium-cannon-shell","explosive-uranium-cannon-shell"],"requires":["uranium-processing","military-4","tank"],"infinite":false},"uranium-mining":{"unlocks":{},"requires":["chemical-science-pack","concrete"],"infinite":false,"modifiers":["mining-with-fluid"]},"uranium-processing":{"unlocks":["centrifuge","uranium-processing"],"requires":["uranium-mining"],"infinite":false},"utility-science-pack":{"unlocks":["utility-science-pack"],"requires":["robotics","processing-unit","low-density-structure"],"infinite":false},"weapon-shooting-speed-1":{"unlocks":{},"requires":["military"],"infinite":false,"modifiers":["gun-speed","gun-speed"]},"weapon-shooting-speed-2":{"unlocks":{},"requires":["weapon-shooting-speed-1","logistic-science-pack"],"infinite":false,"modifiers":["gun-speed","gun-speed"]},"weapon-shooting-speed-3":{"unlocks":{},"requires":["weapon-shooting-speed-2","military-science-pack"],"infinite":false,"modifiers":["gun-speed","gun-speed","gun-speed"]},"weapon-shooting-speed-4":{"unlocks":{},"requires":["weapon-shooting-speed-3"],"infinite":false,"modifiers":["gun-speed","gun-speed","gun-speed"]},"weapon-shooting-speed-5":{"unlocks":{},"requires":["weapon-shooting-speed-4","chemical-science-pack"],"infinite":false,"modifiers":["gun-speed","gun-speed","gun-speed","gun-speed"]},"weapon-shooting-speed-6":{"unlocks":{},"requires":["weapon-shooting-speed-5","utility-science-pack"],"infinite":false,"modifiers":["gun-speed","gun-speed","gun-speed","gun-speed"]},"worker-robots-speed-1":{"unlocks":{},"requires":["robotics"],"infinite":false,"modifiers":["worker-robot-speed"]},"worker-robots-speed-2":{"unlocks":{},"requires":["worker-robots-speed-1"],"infinite":false,"modifiers":["worker-robot-speed"]},"worker-robots-speed-3":{"unlocks":{},"requires":["worker-robots-speed-2","utility-science-pack"],"infinite":false,"modifiers":["worker-robot-speed"]},"worker-robots-speed-4":{"unlocks":{},"requires":["worker-robots-speed-3"],"infinite":false,"modifiers":["worker-robot-speed"]},"worker-robots-speed-5":{"unlocks":{},"requires":["worker-robots-speed-4","production-science-pack"],"infinite":false,"modifiers":["worker-robot-speed"]},"worker-robots-storage-1":{"unlocks":{},"requires":["robotics"],"infinite":false,"modifiers":["worker-robot-storage"]},"worker-robots-storage-2":{"unlocks":{},"requires":["worker-robots-storage-1","production-science-pack"],"infinite":false,"modifiers":["worker-robot-storage"]},"worker-robots-storage-3":{"unlocks":{},"requires":["worker-robots-storage-2","utility-science-pack"],"infinite":false,"modifiers":["worker-robot-storage"]}} \ No newline at end of file From a29ba4a6c447bb3945817e0eb9fdcf75a92a1791 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 7 Jan 2025 16:11:26 -0600 Subject: [PATCH 03/21] The Messenger: reduce strictness of output path check (#4442) --- worlds/messenger/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py index 59e724d3fb..043be455bc 100644 --- a/worlds/messenger/__init__.py +++ b/worlds/messenger/__init__.py @@ -381,7 +381,7 @@ class MessengerWorld(World): return # the messenger client calls into AP with specific args, so check the out path matches what the client sends out_path = output_path(multiworld.get_out_file_name_base(1) + ".aptm") - if "The Messenger\\Archipelago\\output" not in out_path: + if "Messenger\\Archipelago\\output" not in out_path: return import orjson data = { From d3ed40cd4dc7af86b0b083e7b4e05381f6e287e3 Mon Sep 17 00:00:00 2001 From: agilbert1412 Date: Wed, 8 Jan 2025 02:13:32 -0500 Subject: [PATCH 04/21] Stardew Valley: Hide the Mods from the simple options page (#4446) --- worlds/stardew_valley/options/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/stardew_valley/options/options.py b/worlds/stardew_valley/options/options.py index 5d3b25b4da..db94971883 100644 --- a/worlds/stardew_valley/options/options.py +++ b/worlds/stardew_valley/options/options.py @@ -772,6 +772,7 @@ if 'unittest' in sys.modules.keys() or 'pytest' in sys.modules.keys(): class Mods(OptionSet): """List of mods that will be included in the shuffling.""" + visibility = Visibility.all & ~Visibility.simple_ui internal_name = "mods" display_name = "Mods" valid_keys = {ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack, From 874197d940def1edb9509a294916892096b8b558 Mon Sep 17 00:00:00 2001 From: ruby0b <106119328+ruby0b@users.noreply.github.com> Date: Fri, 10 Jan 2025 01:27:49 +0100 Subject: [PATCH 05/21] Linux: move the user home Archipelago dir to $XDG_DATA_HOME (#4347) This affects builds with non-writable installation directories. Instead of saving data in ~/Archipelago we now use $XDG_DATA_HOME/Archipelago (defaulting to ~/.local/share/Archipelago). If ~/Archipelago still exists we move it to the new location and link ~/Archipelago to it. Motivation: This follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/) to at least some degree and doesn't clutter the user's home directory. --- Utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Utils.py b/Utils.py index 574c006b50..43b3ef9c8f 100644 --- a/Utils.py +++ b/Utils.py @@ -152,8 +152,15 @@ def home_path(*path: str) -> str: if hasattr(home_path, 'cached_path'): pass elif sys.platform.startswith('linux'): - home_path.cached_path = os.path.expanduser('~/Archipelago') - os.makedirs(home_path.cached_path, 0o700, exist_ok=True) + xdg_data_home = os.getenv('XDG_DATA_HOME', os.path.expanduser('~/.local/share')) + home_path.cached_path = xdg_data_home + '/Archipelago' + if not os.path.isdir(home_path.cached_path): + legacy_home_path = os.path.expanduser('~/Archipelago') + if os.path.isdir(legacy_home_path): + os.renames(legacy_home_path, home_path.cached_path) + os.symlink(home_path.cached_path, legacy_home_path) + else: + os.makedirs(home_path.cached_path, 0o700, exist_ok=True) else: # not implemented home_path.cached_path = local_path() # this will generate the same exceptions we got previously From 894a8571ee1a3bbbdc16bb6a64192819779ba7b3 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 10 Jan 2025 20:21:02 +0100 Subject: [PATCH 06/21] kvui: add autocompleting new hint text input (#3535) Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com> --- data/client.kv | 5 ++++ kvui.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/data/client.kv b/data/client.kv index 3455f2a236..f0f31769e4 100644 --- a/data/client.kv +++ b/data/client.kv @@ -147,3 +147,8 @@ rectangle: self.x-2, self.y-2, self.width+4, self.height+4 : pos_hint: {'center_y': 0.5, 'center_x': 0.5} + + size_hint_y: None + height: dp(30) + multiline: False + write_tab: False diff --git a/kvui.py b/kvui.py index b2ab004e27..f47e45b93c 100644 --- a/kvui.py +++ b/kvui.py @@ -40,7 +40,7 @@ from kivy.core.image import ImageLoader, ImageLoaderBase, ImageData from kivy.base import ExceptionHandler, ExceptionManager from kivy.clock import Clock from kivy.factory import Factory -from kivy.properties import BooleanProperty, ObjectProperty +from kivy.properties import BooleanProperty, ObjectProperty, NumericProperty from kivy.metrics import dp from kivy.effects.scroll import ScrollEffect from kivy.uix.widget import Widget @@ -64,6 +64,7 @@ from kivy.uix.recycleboxlayout import RecycleBoxLayout from kivy.uix.recycleview.layout import LayoutSelectionBehavior from kivy.animation import Animation from kivy.uix.popup import Popup +from kivy.uix.dropdown import DropDown from kivy.uix.image import AsyncImage fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25) @@ -305,6 +306,50 @@ class SelectableLabel(RecycleDataViewBehavior, TooltipLabel): """ Respond to the selection of items in the view. """ self.selected = is_selected + +class AutocompleteHintInput(TextInput): + min_chars = NumericProperty(3) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.dropdown = DropDown() + self.dropdown.bind(on_select=lambda instance, x: setattr(self, 'text', x)) + self.bind(on_text_validate=self.on_message) + + def on_message(self, instance): + App.get_running_app().commandprocessor("!hint "+instance.text) + + def on_text(self, instance, value): + if len(value) >= self.min_chars: + self.dropdown.clear_widgets() + ctx: context_type = App.get_running_app().ctx + if not ctx.game: + return + item_names = ctx.item_names._game_store[ctx.game].values() + + def on_press(button: Button): + split_text = MarkupLabel(text=button.text).markup + return self.dropdown.select("".join(text_frag for text_frag in split_text + if not text_frag.startswith("["))) + lowered = value.lower() + for item_name in item_names: + try: + index = item_name.lower().index(lowered) + except ValueError: + pass # substring not found + else: + text = escape_markup(item_name) + text = text[:index] + "[b]" + text[index:index+len(value)]+"[/b]"+text[index+len(value):] + btn = Button(text=text, size_hint_y=None, height=dp(30), markup=True) + btn.bind(on_release=on_press) + self.dropdown.add_widget(btn) + if not self.dropdown.attach_to: + self.dropdown.open(self) + else: + self.dropdown.dismiss() + + class HintLabel(RecycleDataViewBehavior, BoxLayout): selected = BooleanProperty(False) striped = BooleanProperty(False) @@ -570,8 +615,10 @@ class GameManager(App): # show Archipelago tab if other logging is present self.tabs.add_widget(panel) - hint_panel = self.add_client_tab("Hints", HintLog(self.json_to_kivy_parser)) + hint_panel = self.add_client_tab("Hints", HintLayout()) + self.hint_log = HintLog(self.json_to_kivy_parser) self.log_panels["Hints"] = hint_panel.content + hint_panel.content.add_widget(self.hint_log) if len(self.logging_pairs) == 1: self.tabs.default_tab_text = "Archipelago" @@ -698,7 +745,7 @@ class GameManager(App): def update_hints(self): hints = self.ctx.stored_data.get(f"_read_hints_{self.ctx.team}_{self.ctx.slot}", []) - self.log_panels["Hints"].refresh_hints(hints) + self.hint_log.refresh_hints(hints) # default F1 keybind, opens a settings menu, that seems to break the layout engine once closed def open_settings(self, *largs): @@ -753,6 +800,17 @@ class UILog(RecycleView): element.height = element.texture_size[1] +class HintLayout(BoxLayout): + orientation = "vertical" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + boxlayout = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(30)) + boxlayout.add_widget(Label(text="New Hint:", size_hint_x=None, size_hint_y=None, height=dp(30))) + boxlayout.add_widget(AutocompleteHintInput()) + self.add_widget(boxlayout) + + status_names: typing.Dict[HintStatus, str] = { HintStatus.HINT_FOUND: "Found", HintStatus.HINT_UNSPECIFIED: "Unspecified", @@ -769,6 +827,7 @@ status_colors: typing.Dict[HintStatus, str] = { } + class HintLog(RecycleView): header = { "receiving": {"text": "[u]Receiving Player[/u]"}, From 043ba418ecbe73420cb0b8ebcc4960e1702db96d Mon Sep 17 00:00:00 2001 From: lordlou <87331798+lordlou@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:46:17 -0500 Subject: [PATCH 07/21] SM generate without rom (#3460) * - SM now displays message when getting an item outside for someone else (fills ROM item table) This is dependant on modifications done to sm_randomizer_rom project * First working MultiWorld SM * some missing things: - player name inject in ROM and get in client - end game get from ROM in client - send self item to server - add player names table in ROM * replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better) * - reenabled balancing * post rebase fixes * updated SmClient.py * + added VariaRandomizer LICENSE * + added sm_randomizer_rom project (which builds sm.ips) * Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning * properly revert change made to CollectionState and more cleaning * Fixed multiworld support patch not working with VariaRandomizer's * missing file commit * Fixed syntax error in unused code to satisfy Linter * Revert "Fixed multiworld support patch not working with VariaRandomizer's" This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b. * many fixes and improovement - fixed seeded generation - fixed broken logic when more than one SM world - added missing rules for inter-area transitions - added basic patch presence for logic - added DoorManager init call to reflect present patches for logic - moved CollectionState addition out of BaseClasses into SM world - added condition to apply progitempool presorting only if SM world is present - set Bosses item id to None to prevent them going into multidata - now use get_game_players * first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions) * first working single-world randomized SM rom patches * - SM now displays message when getting an item outside for someone else (fills ROM item table) This is dependant on modifications done to sm_randomizer_rom project * First working MultiWorld SM * some missing things: - player name inject in ROM and get in client - end game get from ROM in client - send self item to server - add player names table in ROM * replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better) * - reenabled balancing * post rebase fixes * updated SmClient.py * + added VariaRandomizer LICENSE * + added sm_randomizer_rom project (which builds sm.ips) * Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning * properly revert change made to CollectionState and more cleaning * Fixed multiworld support patch not working with VariaRandomizer's * missing file commit * Fixed syntax error in unused code to satisfy Linter * Revert "Fixed multiworld support patch not working with VariaRandomizer's" This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b. * many fixes and improovement - fixed seeded generation - fixed broken logic when more than one SM world - added missing rules for inter-area transitions - added basic patch presence for logic - added DoorManager init call to reflect present patches for logic - moved CollectionState addition out of BaseClasses into SM world - added condition to apply progitempool presorting only if SM world is present - set Bosses item id to None to prevent them going into multidata - now use get_game_players * Fixed multiworld support patch not working with VariaRandomizer's Added stage_fill_hook to set morph first in progitempool Added back VariaRandomizer's standard patches * + added missing files from variaRandomizer project * + added missing variaRandomizer files (custom sprites) + started integrating VariaRandomizer options (WIP) * Some fixes for player and server name display - fixed player name of 16 characters reading too far in SM client - fixed 12 bytes SM player name limit (now 16) - fixed server name not being displayed in SM when using server cheat ( now displays RECEIVED FROM ARCHIPELAGO) - request: temporarly changed default seed names displayed in SM main menu to OWTCH * Fixed Goal completion not triggering in smClient * integrated VariaRandomizer's options into AP (WIP) - startAP is working - door rando is working - skillset is working * - fixed itemsounds.ips crash by always including nofanfare.ips into multiworld.ips (itemsounds is now always applied and "itemsounds" preset must always be "off") * skillset are now instanced per player instead of being a singleton class * RomPatches are now instanced per player instead of being a singleton class * DoorManager is now instanced per player instead of being a singleton class * - fixed the last bugs that prevented generation of >1 SM world * fixed crash when no skillset preset is specified in randoPreset (default to "casual") * maxDifficulty support and itemsounds removal - added support for maxDifficulty - removed itemsounds patch as its always applied from multiworld patch for now * Fixed bad merge * Post merge adaptation * fixed player name length fix that got lost with the merge * fixed generation with other game type than SM * added default randoPreset json for SM in playerSettings.yaml * fixed broken SM client following merge * beautified json skillset presets * Fixed ArchipelagoSmClient not building * Fixed conflict between mutliworld patch and beam_doors_plms patch - doorsColorsRando now working * SM generation now outputs APBP - Fixed paths for patches and presets when frozen * added missing file and fixed multithreading issue * temporarily set data_version = 0 * more work - added support for AP starting items - fixed client crash with gamemode being None - patch.py "compatible_version" is now 3 * commited missing asm files fixed start item reserve breaking game (was using bad write offset when patching) * Nothing item are now handled game-side. the game will now skip displaying a message box for received Nothing item (but the client will still receive it). fixed crash in SMClient when loosing connection to SNI * fixed No Energy Item missing its ID fixed Plando * merge post fixes * fixed start item Grapple, XRay and Reserve HUD, as well as graphic beams (except ice palette color) * fixed freeze in blue brinstar caused by Varia's custom PLM not being filled with proper Multiworld PLM address (altLocsAddresses) * fixed start item x-ray HUD display * Fixed start items being sent by the server (is all handled in ROM) Start items are now not removed from itempool anymore Nothing Item is now local_items so no player will ever pickup Nothing. Doing so reduces contribution of this world to the Multiworld the more Nothing there is though. Fixed crash (and possibly passing but broken) at generation where the static list of IPSPatches used by all SM worlds was being modified * fixed settings that could be applied to any SM players * fixed auth to server only using player name (now does as ALTTP to authenticate) * - fixed End Credits broken text * added non SM item name display * added all supported SM options in playerSettings.yaml * fixed locations needing a list of parent regions (now generate a region for each location with one-way exits to each (previously) parent region did some cleaning (mainly reverts on unnecessary core classes * minor setting fixes and tweaks - merged Area and lightArea settings - made missileQty, superQty and powerBombQty use value from 10 to 90 and divide value by float(10) when generating - fixed inverted layoutPatch setting * added option start_inventory_removes_from_pool fixed option names formatting fixed lint errors small code and repo cleanup * Hopefully fixed ROR2 that could not send any items * - fixed missing required change to ROR2 * fixed 0 hp when respawning without having ever saved (start items were not updating the save checksum) * fixed typo with doors_colors_rando * fixed checksum * added custom sprites for off-world items (progression or not) the original AP sprite was made with PierRoulette's SM Item Sprite Utility by ijwu * - added missing change following upstream merge - changed patch filename extension from apbp to apm3 so patch can be used with the new client * added morph placement options: early means local and sphere 1 * fixed failing unit tests * - fixed broken custom_preset options * - big cleanup to remove unnecessary or unsupported features * - more cleanup * - moved sm_randomizer_rom and all always applied patches into an external project that outputs basepatch.ips - small cleanup * - added comment to refer to project for generating basepatch.ips (https://github.com/lordlou/SMBasepatch) * fixed g4_skip patch that can be not applied if hud is enabled * - fixed off world sprite that can have broken graphics (restricted to use only first 2 palette) * - updated basepatch to reflect g4_skip removal - moved more asm files to SMBasepatch project * - tourian grey doors at baby metroid are now always flashing (allowing to go back if needed) * fixed wrong path if using built as exe * - cleaned exposed maxDifficulty options - removed always enabled Knows * Merged LttPClient and SMClient into SNIClient * added varia_custom Preset Option that fetch a preset (read from a new varia_custom_preset Option) from varia's web service * small doc precision * - added death_link support - fixed broken Goal Completion - post merge fix * - removed now useless presets * - fixed bad internal mapping with maxDiff - increases maxDiff if only Bosses is preventing beating the game * - added support for lowercase custom preset sections (knows, settings and controller) - fixed controller settings not applying to ROM * - fixed death loop when dying with Door rando, bomb or speed booster as starting items - varia's backup save should now be usable (automatically enabled when doing door rando) * -added docstring for generated yaml * fixed bad merge * fixed broken infinity max difficulty * commented debug prints * adjusted credits to mark progression speed and difficulty as Non Available * added support for more than 255 players (will print Archipelago for higher player number) * fixed missing cleanup * added support for 65535 different player names in ROM * fixed generations failing when only bosses are unreachable * - replaced setting maxDiff to infinity with a bool only affecting boss logics if only bosses are left to finish * fixed failling generations when using 'fun' settings Accessibility checks are forced to 'items' if restricted locations are used by VARIA following usage of 'fun' settings * fixed debug logger * removed unsupported "suits_restriction" option * fixed generations failing when only bosses are unreachable (using a less intrusive approach for AP) * - fixed deathlink emptying reserves - added death_link_survive option that lets player survive when receiving a deathlink if the have non-empty reserves * - merged death_link and death_link_survive options * fixed death_link * added a fallback default starting location instead of failing generation if an invalid one was chosen * added Nothing and NoEnergy as hint blacklist added missing NoEnergy as local items and removed it from progression * SM Varia can now generate without ROM * removed stage_assert_generate --- worlds/sm/Rom.py | 45 ++++++++++++++- worlds/sm/__init__.py | 30 ++++------ worlds/sm/variaRandomizer/randomizer.py | 26 +++------ worlds/sm/variaRandomizer/rom/ips.py | 19 ++++++- worlds/sm/variaRandomizer/rom/rom.py | 62 ++++++++++++++++++++- worlds/sm/variaRandomizer/rom/rompatcher.py | 9 +-- 6 files changed, 142 insertions(+), 49 deletions(-) diff --git a/worlds/sm/Rom.py b/worlds/sm/Rom.py index ac516ae48b..c5b6645ed8 100644 --- a/worlds/sm/Rom.py +++ b/worlds/sm/Rom.py @@ -4,18 +4,59 @@ import os import json import Utils from Utils import read_snes_rom -from worlds.Files import APDeltaPatch +from worlds.Files import APPatchExtension, APProcedurePatch, APTokenMixin, APTokenTypes from .variaRandomizer.utils.utils import openFile SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' SM_ROM_MAX_PLAYERID = 65535 SM_ROM_PLAYERDATA_COUNT = 202 -class SMDeltaPatch(APDeltaPatch): +class SMPatchExtensions(APPatchExtension): + game = "Super Metroid" + + @staticmethod + def write_crc(caller: APProcedurePatch, rom: bytes) -> bytes: + def checksum_mirror_sum(start, length, mask = 0x800000): + while not(length & mask) and mask: + mask >>= 1 + + part1 = sum(start[:mask]) & 0xFFFF + part2 = 0 + + next_length = length - mask + if next_length: + part2 = checksum_mirror_sum(start[mask:], next_length, mask >> 1) + + while (next_length < mask): + next_length += next_length + part2 += part2 + + return (part1 + part2) & 0xFFFF + + def write_bytes(buffer, startaddress: int, values): + buffer[startaddress:startaddress + len(values)] = values + + buffer = bytearray(rom) + crc = checksum_mirror_sum(buffer, len(buffer)) + inv = crc ^ 0xFFFF + write_bytes(buffer, 0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF]) + return bytes(buffer) + +class SMProcedurePatch(APProcedurePatch, APTokenMixin): hash = SMJUHASH game = "Super Metroid" patch_file_ending = ".apsm" + procedure = [ + ("apply_tokens", ["token_data.bin"]), + ("write_crc", []) + ] + + def write_tokens(self, patches): + for addr, data in patches.items(): + self.write_token(APTokenTypes.WRITE, addr, bytes(data)) + self.write_file("token_data.bin", self.get_token_binary()) + @classmethod def get_source_data(cls) -> bytes: return get_base_rom_bytes() diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 160b7e4ec7..5d53270d61 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -17,7 +17,7 @@ logger = logging.getLogger("Super Metroid") from .Options import SMOptions from .Client import SMSNIClient -from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols +from .Rom import SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMProcedurePatch, get_sm_symbols import Utils from .variaRandomizer.logic.smboolmanager import SMBoolManager @@ -40,7 +40,7 @@ class SMSettings(settings.Group): """File name of the v1.0 J rom""" description = "Super Metroid (JU) ROM" copy_to = "Super Metroid (JU).sfc" - md5s = [SMDeltaPatch.hash] + md5s = [SMProcedurePatch.hash] rom_file: RomFile = RomFile(RomFile.copy_to) @@ -120,12 +120,6 @@ class SMWorld(World): self.locations = {} super().__init__(world, player) - @classmethod - def stage_assert_generate(cls, multiworld: MultiWorld): - rom_file = get_base_rom_path() - if not os.path.exists(rom_file): - raise FileNotFoundError(rom_file) - def generate_early(self): Logic.factory('vanilla') @@ -802,23 +796,19 @@ class SMWorld(World): romPatcher.end() def generate_output(self, output_directory: str): - self.variaRando.args.rom = get_base_rom_path() - outfilebase = self.multiworld.get_out_file_name_base(self.player) - outputFilename = os.path.join(output_directory, f"{outfilebase}.sfc") - try: - self.variaRando.PatchRom(outputFilename, self.APPrePatchRom, self.APPostPatchRom) - self.write_crc(outputFilename) + patcher = self.variaRando.PatchRom(self.APPrePatchRom, self.APPostPatchRom) self.rom_name = self.romName + + patch = SMProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player]) + patch.write_tokens(patcher.romFile.getPatchDict()) + rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}" + f"{patch.patch_file_ending}") + patch.write(rom_path) + except: raise - else: - patch = SMDeltaPatch(os.path.splitext(outputFilename)[0] + SMDeltaPatch.patch_file_ending, player=self.player, - player_name=self.multiworld.player_name[self.player], patched_path=outputFilename) - patch.write() finally: - if os.path.exists(outputFilename): - os.unlink(outputFilename) self.rom_name_available_event.set() # make sure threading continues and errors are collected def checksum_mirror_sum(self, start, length, mask = 0x800000): diff --git a/worlds/sm/variaRandomizer/randomizer.py b/worlds/sm/variaRandomizer/randomizer.py index 8a7a2ea0e2..22712aa442 100644 --- a/worlds/sm/variaRandomizer/randomizer.py +++ b/worlds/sm/variaRandomizer/randomizer.py @@ -680,7 +680,7 @@ class VariaRandomizer: #dumpErrorMsg(args.output, self.randoExec.errorMsg) raise Exception("Can't generate " + self.fileName + " with the given parameters: {}".format(self.randoExec.errorMsg)) - def PatchRom(self, outputFilename, customPrePatchApply = None, customPostPatchApply = None): + def PatchRom(self, customPrePatchApply = None, customPostPatchApply = None) -> RomPatcher: args = self.args optErrMsgs = self.optErrMsgs @@ -758,9 +758,9 @@ class VariaRandomizer: # args.output is not None: generate local json named args.output if args.rom is not None: # patch local rom - romFileName = args.rom - shutil.copyfile(romFileName, outputFilename) - romPatcher = RomPatcher(settings=patcherSettings, romFileName=outputFilename, magic=args.raceMagic, player=self.player) + # romFileName = args.rom + # shutil.copyfile(romFileName, outputFilename) + romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, player=self.player) else: romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic) @@ -779,24 +779,12 @@ class VariaRandomizer: #msg = randoExec.errorMsg msg = '' - if args.rom is None: # web mode - data = romPatcher.romFile.data - self.fileName = '{}.sfc'.format(self.fileName) - data["fileName"] = self.fileName - # error msg in json to be displayed by the web site - data["errorMsg"] = msg - # replaced parameters to update stats in database - if len(self.forcedArgs) > 0: - data["forcedArgs"] = self.forcedArgs - with open(outputFilename, 'w') as jsonFile: - json.dump(data, jsonFile) - else: # CLI mode - if msg != "": - print(msg) + return romPatcher + except Exception as e: import traceback traceback.print_exc(file=sys.stdout) - raise Exception("Error patching {}: ({}: {})".format(outputFilename, type(e).__name__, e)) + raise Exception("Error patching: ({}: {})".format(type(e).__name__, e)) #dumpErrorMsg(args.output, msg) # if stuck == True: diff --git a/worlds/sm/variaRandomizer/rom/ips.py b/worlds/sm/variaRandomizer/rom/ips.py index dd3f30a3ac..add187a86a 100644 --- a/worlds/sm/variaRandomizer/rom/ips.py +++ b/worlds/sm/variaRandomizer/rom/ips.py @@ -21,10 +21,23 @@ class IPS_Patch(object): def toDict(self): ret = {} for record in self.records: - if 'rle_count' in record: - ret[record['address']] = [int.from_bytes(record['data'],'little')]*record['rle_count'] + if record['address'] in ret.keys(): + if 'rle_count' in record: + if len(ret[record['address']]) > record['rle_count']: + ret[record['address']][:record['rle_count']] = [int.from_bytes(record['data'],'little')]*record['rle_count'] + else: + ret[record['address']] = [int.from_bytes(record['data'],'little')]*record['rle_count'] + else: + size = len(record['data']) + if len(ret[record['address']]) > size: + ret[record['address']][:size] = [int(b) for b in record['data']] + else: + ret[record['address']] = [int(b) for b in record['data']] else: - ret[record['address']] = [int(b) for b in record['data']] + if 'rle_count' in record: + ret[record['address']] = [int.from_bytes(record['data'],'little')]*record['rle_count'] + else: + ret[record['address']] = [int(b) for b in record['data']] return ret @staticmethod diff --git a/worlds/sm/variaRandomizer/rom/rom.py b/worlds/sm/variaRandomizer/rom/rom.py index 37c15698a2..f0f37b76a3 100644 --- a/worlds/sm/variaRandomizer/rom/rom.py +++ b/worlds/sm/variaRandomizer/rom/rom.py @@ -86,7 +86,67 @@ class ROM(object): self.seek(self.maxAddress + BANK_SIZE - off - 1) self.writeByte(0xff) assert (self.maxAddress % BANK_SIZE) == 0 - + +class FakeROM(ROM): + # to have the same code for real ROM and the webservice + def __init__(self, data={}): + super(FakeROM, self).__init__() + self.data = data + self.ipsPatches = [] + + def write(self, bytes): + for byte in bytes: + self.data[self.address] = byte + self.inc() + + def read(self, byteCount): + bytes = [] + for i in range(byteCount): + bytes.append(self.data[self.address]) + self.inc() + + return bytes + + def ipsPatch(self, ipsPatches): + self.ipsPatches += ipsPatches + + # generate ips from self data + def ips(self): + groupedData = {} + startAddress = -1 + prevAddress = -1 + curData = [] + for address in sorted(self.data): + if address == prevAddress + 1: + curData.append(self.data[address]) + prevAddress = address + else: + if len(curData) > 0: + groupedData[startAddress] = curData + startAddress = address + prevAddress = address + curData = [self.data[startAddress]] + if startAddress != -1: + groupedData[startAddress] = curData + + return IPS_Patch(groupedData) + + # generate final IPS for web patching with first the IPS patches, then written data + def close(self): + self.mergedIPS = IPS_Patch() + for ips in self.ipsPatches: + self.mergedIPS.append(ips) + self.mergedIPS.append(self.ips()) + #patchData = mergedIPS.encode() + #self.data = {} + #self.data["ips"] = base64.b64encode(patchData).decode() + #if mergedIPS.truncate_length is not None: + # self.data["truncate_length"] = mergedIPS.truncate_length + #self.data["max_size"] = mergedIPS.max_size + + def getPatchDict(self): + return self.mergedIPS.toDict() + class RealROM(ROM): def __init__(self, name): super(RealROM, self).__init__() diff --git a/worlds/sm/variaRandomizer/rom/rompatcher.py b/worlds/sm/variaRandomizer/rom/rompatcher.py index 2dcf554a00..a350764a9c 100644 --- a/worlds/sm/variaRandomizer/rom/rompatcher.py +++ b/worlds/sm/variaRandomizer/rom/rompatcher.py @@ -7,7 +7,7 @@ from ..utils.doorsmanager import DoorsManager, IndicatorFlag from ..utils.objectives import Objectives from ..graph.graph_utils import GraphUtils, getAccessPoint, locIdsByAreaAddresses, graphAreas from ..logic.logic import Logic -from ..rom.rom import RealROM, snes_to_pc, pc_to_snes +from ..rom.rom import FakeROM, snes_to_pc, pc_to_snes from ..rom.addresses import Addresses from ..rom.rom_patches import RomPatches from ..patches.patchaccess import PatchAccess @@ -52,10 +52,10 @@ class RomPatcher: def __init__(self, settings=None, romFileName=None, magic=None, player=0): self.log = log.get('RomPatcher') self.settings = settings - self.romFileName = romFileName + #self.romFileName = romFileName self.patchAccess = PatchAccess() self.race = None - self.romFile = RealROM(romFileName) + self.romFile = FakeROM() #if magic is not None: # from rom.race_mode import RaceModePatcher # self.race = RaceModePatcher(self, magic) @@ -312,7 +312,7 @@ class RomPatcher: self.applyStartAP(self.settings["startLocation"], plms, doors) self.applyPLMs(plms) except Exception as e: - raise Exception("Error patching {}. ({})".format(self.romFileName, e)) + raise Exception("Error patching. ({})".format(e)) def applyIPSPatch(self, patchName, patchDict=None, ipsDir=None): if patchDict is None: @@ -493,6 +493,7 @@ class RomPatcher: def commitIPS(self): self.romFile.ipsPatch(self.ipsPatches) + self.ipsPatches = [] def writeSeed(self, seed): random.seed(seed) From 258ea10c529e3ed1b5bfecd19c8aae9e1a54dbf8 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 10 Jan 2025 15:49:13 -0500 Subject: [PATCH 08/21] TUNIC: Modify UT support to make a better pattern (#3860) * Modify UT support to make a better pattern * Handle keyerror for logic_rules option * Missed self.passthrough value setting * Less laziness for passthrough * Remove extra newline * Fix missing using_ut = True, also remove now unnecessary try except since 0.5.1 is out * New UT thing, it goes in this PR because it's been open for 5 months for a very very tiny change --- worlds/tunic/__init__.py | 49 ++++++++++++++++++--------------- worlds/tunic/er_scripts.py | 55 ++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 8525a3fc43..1c326f78bd 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -90,6 +90,10 @@ class TunicWorld(World): item_link_locations: Dict[int, Dict[str, List[Tuple[int, str]]]] = {} player_item_link_locations: Dict[str, List[Location]] + using_ut: bool # so we can check if we're using UT only once + passthrough: Dict[str, Any] + ut_can_gen_without_yaml = True # class var that tells it to ignore the player yaml + def generate_early(self) -> None: if self.options.logic_rules >= LogicRules.option_no_major_glitches: self.options.laurels_zips.value = LaurelsZips.option_true @@ -113,23 +117,28 @@ class TunicWorld(World): # Universal tracker stuff, shouldn't do anything in standard gen if hasattr(self.multiworld, "re_gen_passthrough"): if "TUNIC" in self.multiworld.re_gen_passthrough: - passthrough = self.multiworld.re_gen_passthrough["TUNIC"] - self.options.start_with_sword.value = passthrough["start_with_sword"] - self.options.keys_behind_bosses.value = passthrough["keys_behind_bosses"] - self.options.sword_progression.value = passthrough["sword_progression"] - self.options.ability_shuffling.value = passthrough["ability_shuffling"] - self.options.laurels_zips.value = passthrough["laurels_zips"] - self.options.ice_grappling.value = passthrough["ice_grappling"] - self.options.ladder_storage.value = passthrough["ladder_storage"] - self.options.ladder_storage_without_items = passthrough["ladder_storage_without_items"] - self.options.lanternless.value = passthrough["lanternless"] - self.options.maskless.value = passthrough["maskless"] - self.options.hexagon_quest.value = passthrough["hexagon_quest"] - self.options.entrance_rando.value = passthrough["entrance_rando"] - self.options.shuffle_ladders.value = passthrough["shuffle_ladders"] + self.using_ut = True + self.passthrough = self.multiworld.re_gen_passthrough["TUNIC"] + self.options.start_with_sword.value = self.passthrough["start_with_sword"] + self.options.keys_behind_bosses.value = self.passthrough["keys_behind_bosses"] + self.options.sword_progression.value = self.passthrough["sword_progression"] + self.options.ability_shuffling.value = self.passthrough["ability_shuffling"] + self.options.laurels_zips.value = self.passthrough["laurels_zips"] + self.options.ice_grappling.value = self.passthrough["ice_grappling"] + self.options.ladder_storage.value = self.passthrough["ladder_storage"] + self.options.ladder_storage_without_items = self.passthrough["ladder_storage_without_items"] + self.options.lanternless.value = self.passthrough["lanternless"] + self.options.maskless.value = self.passthrough["maskless"] + self.options.hexagon_quest.value = self.passthrough["hexagon_quest"] + self.options.entrance_rando.value = self.passthrough["entrance_rando"] + self.options.shuffle_ladders.value = self.passthrough["shuffle_ladders"] self.options.fixed_shop.value = self.options.fixed_shop.option_false self.options.laurels_location.value = self.options.laurels_location.option_anywhere - self.options.combat_logic.value = passthrough["combat_logic"] + self.options.combat_logic.value = self.passthrough["combat_logic"] + else: + self.using_ut = False + else: + self.using_ut = False @classmethod def stage_generate_early(cls, multiworld: MultiWorld) -> None: @@ -331,12 +340,10 @@ class TunicWorld(World): self.ability_unlocks = randomize_ability_unlocks(self.random, self.options) # stuff for universal tracker support, can be ignored for standard gen - if hasattr(self.multiworld, "re_gen_passthrough"): - if "TUNIC" in self.multiworld.re_gen_passthrough: - passthrough = self.multiworld.re_gen_passthrough["TUNIC"] - self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"] - self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"] - self.ability_unlocks["Pages 52-53 (Icebolt)"] = passthrough["Hexagon Quest Icebolt"] + if self.using_ut: + self.ability_unlocks["Pages 24-25 (Prayer)"] = self.passthrough["Hexagon Quest Prayer"] + self.ability_unlocks["Pages 42-43 (Holy Cross)"] = self.passthrough["Hexagon Quest Holy Cross"] + self.ability_unlocks["Pages 52-53 (Icebolt)"] = self.passthrough["Hexagon Quest Icebolt"] # Ladders and Combat Logic uses ER rules with vanilla connections for easier maintenance if self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic: diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index aa5833b4db..ed9fc0120d 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -177,7 +177,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal logic_tricks: Tuple[bool, int, int] = (laurels_zips, ice_grappling, ladder_storage) # marking that you don't immediately have laurels - if laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"): + if laurels_location == "10_fairies" and not world.using_ut: has_laurels = False shop_count = 6 @@ -191,9 +191,8 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal break # If using Universal Tracker, restore portal_map. Could be cleaner, but it does not matter for UT even a little bit - if hasattr(world.multiworld, "re_gen_passthrough"): - if "TUNIC" in world.multiworld.re_gen_passthrough: - portal_map = portal_mapping.copy() + if world.using_ut: + portal_map = portal_mapping.copy() # create separate lists for dead ends and non-dead ends for portal in portal_map: @@ -232,25 +231,24 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal plando_connections = world.seed_groups[world.options.entrance_rando.value]["plando"] # universal tracker support stuff, don't need to care about region dependency - if hasattr(world.multiworld, "re_gen_passthrough"): - if "TUNIC" in world.multiworld.re_gen_passthrough: - plando_connections.clear() - # universal tracker stuff, won't do anything in normal gen - for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items(): - portal_name1 = "" - portal_name2 = "" + if world.using_ut: + plando_connections.clear() + # universal tracker stuff, won't do anything in normal gen + for portal1, portal2 in world.passthrough["Entrance Rando"].items(): + portal_name1 = "" + portal_name2 = "" - for portal in portal_mapping: - if portal.scene_destination() == portal1: - portal_name1 = portal.name - # connected_regions.update(add_dependent_regions(portal.region, logic_rules)) - if portal.scene_destination() == portal2: - portal_name2 = portal.name - # connected_regions.update(add_dependent_regions(portal.region, logic_rules)) - # shops have special handling - if not portal_name2 and portal2 == "Shop, Previous Region_": - portal_name2 = "Shop Portal" - plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both")) + for portal in portal_mapping: + if portal.scene_destination() == portal1: + portal_name1 = portal.name + # connected_regions.update(add_dependent_regions(portal.region, logic_rules)) + if portal.scene_destination() == portal2: + portal_name2 = portal.name + # connected_regions.update(add_dependent_regions(portal.region, logic_rules)) + # shops have special handling + if not portal_name2 and portal2 == "Shop, Previous Region_": + portal_name2 = "Shop Portal" + plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both")) non_dead_end_regions = set() for region_name, region_info in world.er_regions.items(): @@ -362,7 +360,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal # if we have plando connections, our connected regions may change somewhat connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_tricks) - if fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"): + if fixed_shop and not world.using_ut: portal1 = None for portal in two_plus: if portal.scene_destination() == "Overworld Redux, Windmill_": @@ -392,7 +390,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal fail_count = 0 while len(connected_regions) < len(non_dead_end_regions): # if this is universal tracker, just break immediately and move on - if hasattr(world.multiworld, "re_gen_passthrough"): + if world.using_ut: break # if the connected regions length stays unchanged for too long, it's stuck in a loop # should, hopefully, only ever occur if someone plandos connections poorly @@ -445,9 +443,8 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal random_object.shuffle(two_plus) # for universal tracker, we want to skip shop gen - if hasattr(world.multiworld, "re_gen_passthrough"): - if "TUNIC" in world.multiworld.re_gen_passthrough: - shop_count = 0 + if world.using_ut: + shop_count = 0 for i in range(shop_count): portal1 = two_plus.pop() @@ -462,7 +459,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal # connect dead ends to random non-dead ends # none of the key events are in dead ends, so we don't need to do gate_before_switch while len(dead_ends) > 0: - if hasattr(world.multiworld, "re_gen_passthrough"): + if world.using_ut: break portal1 = two_plus.pop() portal2 = dead_ends.pop() @@ -470,7 +467,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal # then randomly connect the remaining portals to each other # every region is accessible, so gate_before_switch is not necessary while len(two_plus) > 1: - if hasattr(world.multiworld, "re_gen_passthrough"): + if world.using_ut: break portal1 = two_plus.pop() portal2 = two_plus.pop() From 96b500679d263859dc4efb2d3b56385bc65fd84b Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:40:50 -0500 Subject: [PATCH 09/21] LTTP: Add missing GT Pre-Moldorm Bomb Wall Logic (#4440) --- worlds/alttp/Rules.py | 4 ++-- worlds/alttp/test/dungeons/TestGanonsTower.py | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index a664f8ac9b..386e0b0e9e 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -592,9 +592,9 @@ def global_rules(multiworld: MultiWorld, player: int): lambda state: can_kill_most_things(state, player, 8) and has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state)) set_rule(multiworld.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1)) set_rule(multiworld.get_location('Ganons Tower - Pre-Moldorm Chest', player), - lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7)) + lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Ganons Tower Moldorm Door', player), - lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8)) + lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) and can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Ganons Tower Moldorm Gap', player), lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state)) set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player)) diff --git a/worlds/alttp/test/dungeons/TestGanonsTower.py b/worlds/alttp/test/dungeons/TestGanonsTower.py index 08274d0fe7..4b8fc4c295 100644 --- a/worlds/alttp/test/dungeons/TestGanonsTower.py +++ b/worlds/alttp/test/dungeons/TestGanonsTower.py @@ -130,19 +130,21 @@ class TestGanonsTower(TestDungeon): ["Ganons Tower - Pre-Moldorm Chest", False, []], ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Progressive Bow']], + ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Bomb Upgrade (50)']], ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Big Key (Ganons Tower)']], ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Lamp', 'Fire Rod']], - ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], - ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], + ["Ganons Tower - Pre-Moldorm Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], + ["Ganons Tower - Pre-Moldorm Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], ["Ganons Tower - Validation Chest", False, []], ["Ganons Tower - Validation Chest", False, [], ['Hookshot']], ["Ganons Tower - Validation Chest", False, [], ['Progressive Bow']], + ["Ganons Tower - Validation Chest", False, [], ['Bomb Upgrade (50)']], ["Ganons Tower - Validation Chest", False, [], ['Big Key (Ganons Tower)']], ["Ganons Tower - Validation Chest", False, [], ['Lamp', 'Fire Rod']], ["Ganons Tower - Validation Chest", False, [], ['Progressive Sword', 'Hammer']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']], + ["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']], + ["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']], + ["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']], + ["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']], ]) \ No newline at end of file From 112bfe0933742e45c5c423e303235a9b8a433440 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 10 Jan 2025 16:48:15 -0500 Subject: [PATCH 10/21] TUNIC: Logic for Beneath the Vault Bridge Switch #4432 --- worlds/tunic/er_rules.py | 2 +- worlds/tunic/rules.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 6e9ae551db..f7568df81e 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -1675,7 +1675,7 @@ def set_er_location_rules(world: "TunicWorld") -> None: # Beneath the Vault set_rule(world.get_location("Beneath the Fortress - Bridge"), - lambda state: has_melee(state, player) or state.has_any({laurels, fire_wand}, player)) + lambda state: has_melee(state, player) or state.has_any((laurels, fire_wand, ice_dagger, gun), player)) # Quarry set_rule(world.get_location("Quarry - [Central] Above Ladder Dash Chest"), diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index 30b7cee9d0..959376787d 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -323,7 +323,7 @@ def set_location_rules(world: "TunicWorld") -> None: # Beneath the Vault set_rule(world.get_location("Beneath the Fortress - Bridge"), - lambda state: has_melee(state, player) or state.has_any({laurels, fire_wand}, player)) + lambda state: has_melee(state, player) or state.has_any((laurels, fire_wand, ice_dagger, gun), player)) set_rule(world.get_location("Beneath the Fortress - Obscured Behind Waterfall"), lambda state: has_melee(state, player) and has_lantern(state, world)) From c2bd9df0f721cf71b154abe25e128bb9b3b8865e Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 10 Jan 2025 23:28:38 +0100 Subject: [PATCH 11/21] Subnautica: fix typo and remove no longer used logger (#4456) --- worlds/subnautica/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index c3cf40a7c0..850c23c7dd 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import itertools from typing import List, Dict, Any, cast @@ -10,13 +9,11 @@ from . import items from . import locations from . import creatures from . import options -from .items import item_table, group_items, items_by_type, ItemType +from .items import item_table, group_items from .rules import set_rules -logger = logging.getLogger("Subnautica") - -class SubnaticaWeb(WebWorld): +class SubnauticaWeb(WebWorld): tutorials = [Tutorial( "Multiworld Setup Guide", "A guide to setting up the Subnautica randomizer connected to an Archipelago Multiworld", @@ -38,7 +35,7 @@ class SubnauticaWorld(World): You must find a cure for yourself, build an escape rocket, and leave the planet. """ game = "Subnautica" - web = SubnaticaWeb() + web = SubnauticaWeb() item_name_to_id = {data.name: item_id for item_id, data in items.item_table.items()} location_name_to_id = all_locations From d97ee5d209245e436f141d4aae042c454ef631f0 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 10 Jan 2025 23:28:57 +0100 Subject: [PATCH 12/21] Core: update certifi (#4453) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 946546cb69..cd045b874b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ schema>=0.7.7 kivy>=2.3.0 bsdiff4>=1.2.4 platformdirs>=4.2.2 -certifi>=2024.8.30 +certifi>=2024.12.14 cython>=3.0.11 cymem>=2.0.8 orjson>=3.10.7 From 29b34ca9fde17cbf697b691dd8136c55db322cc9 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:31:29 -0500 Subject: [PATCH 13/21] =?UTF-8?q?Pok=C3=A9mon=20R/B:=20Fix=20Route=2011-E?= =?UTF-8?q?=20to=20Route-12-W=20logic=20(#4435)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- worlds/pokemon_rb/regions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index 575f4a61ca..d4b8a8f506 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -1718,7 +1718,7 @@ def create_regions(world): connect(multiworld, player, "Vermilion City", "Vermilion City-Dock", lambda state: state.has("S.S. Ticket", player)) connect(multiworld, player, "Vermilion City", "Route 11") connect(multiworld, player, "Route 12-N", "Route 12-S", lambda state: logic.can_surf(state, world, player)) - connect(multiworld, player, "Route 12-W", "Route 11-E", lambda state: state.has("Poke Flute", player)) + connect(multiworld, player, "Route 12-W", "Route 11-E") connect(multiworld, player, "Route 12-W", "Route 12-N", lambda state: state.has("Poke Flute", player)) connect(multiworld, player, "Route 12-W", "Route 12-S", lambda state: state.has("Poke Flute", player)) connect(multiworld, player, "Route 12-S", "Route 12-Grass", lambda state: logic.can_cut(state, world, player), one_way=True) From adcb2f59ca94bc472cd82f2eb3c96a53ce639cba Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 11 Jan 2025 22:16:01 +0100 Subject: [PATCH 14/21] MultiServer: Correct tying of Context.groups (#4460) --- MultiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiServer.py b/MultiServer.py index 0601e17915..8aabdea3e2 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -444,7 +444,7 @@ class Context: self.slot_info = decoded_obj["slot_info"] self.games = {slot: slot_info.game for slot, slot_info in self.slot_info.items()} - self.groups = {slot: slot_info.group_members for slot, slot_info in self.slot_info.items() + self.groups = {slot: set(slot_info.group_members) for slot, slot_info in self.slot_info.items() if slot_info.type == SlotType.group} self.clients = {0: {}} From 70942eda8cec0f3a92bc1ebcb8dba2401f7ed90a Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Sat, 11 Jan 2025 22:54:48 -0800 Subject: [PATCH 15/21] BizHawkClient: Fix version warning not falling through to regular execution (#4463) --- data/lua/connector_bizhawk_generic.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/lua/connector_bizhawk_generic.lua b/data/lua/connector_bizhawk_generic.lua index 00021b241f..6c5af87e1a 100644 --- a/data/lua/connector_bizhawk_generic.lua +++ b/data/lua/connector_bizhawk_generic.lua @@ -613,9 +613,11 @@ end) if bizhawk_major < 2 or (bizhawk_major == 2 and bizhawk_minor < 7) then print("Must use BizHawk 2.7.0 or newer") -elseif bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 9) then - print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.9.") else + if bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 10) then + print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.10.") + end + if emu.getsystemid() == "NULL" then print("No ROM is loaded. Please load a ROM.") while emu.getsystemid() == "NULL" do From 4edca0ce541daf9941a659290145213ce31bf0e9 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Sat, 11 Jan 2025 23:03:31 -0800 Subject: [PATCH 16/21] BizHawkClient: Add command to get size of memory domain (#4439) * Mega Man 2: Remove mm2 commands from client if rom size too small --- data/lua/connector_bizhawk_generic.lua | 23 +++++++++++++++++++++++ worlds/_bizhawk/__init__.py | 12 +++++++++++- worlds/mm2/client.py | 11 ++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/data/lua/connector_bizhawk_generic.lua b/data/lua/connector_bizhawk_generic.lua index 6c5af87e1a..c2e8f91c0d 100644 --- a/data/lua/connector_bizhawk_generic.lua +++ b/data/lua/connector_bizhawk_generic.lua @@ -121,6 +121,14 @@ Response: Expected Response Type: `HASH_RESPONSE` +- `MEMORY_SIZE` + Returns the size in bytes of the specified memory domain. + + Expected Response Type: `MEMORY_SIZE_RESPONSE` + + Additional Fields: + - `domain` (`string`): The name of the memory domain to check + - `GUARD` Checks a section of memory against `expected_data`. If the bytes starting at `address` do not match `expected_data`, the response will have `value` @@ -216,6 +224,12 @@ Response: Additional Fields: - `value` (`string`): The returned hash +- `MEMORY_SIZE_RESPONSE` + Contains the size in bytes of the specified memory domain. + + Additional Fields: + - `value` (`number`): The size of the domain in bytes + - `GUARD_RESPONSE` The result of an attempted `GUARD` request. @@ -376,6 +390,15 @@ request_handlers = { return res end, + ["MEMORY_SIZE"] = function (req) + local res = {} + + res["type"] = "MEMORY_SIZE_RESPONSE" + res["value"] = memory.getmemorydomainsize(req["domain"]) + + return res + end, + ["GUARD"] = function (req) local res = {} local expected_data = base64.decode(req["expected_data"]) diff --git a/worlds/_bizhawk/__init__.py b/worlds/_bizhawk/__init__.py index 3627f385c2..e7b8edc0b6 100644 --- a/worlds/_bizhawk/__init__.py +++ b/worlds/_bizhawk/__init__.py @@ -151,7 +151,7 @@ async def ping(ctx: BizHawkContext) -> None: async def get_hash(ctx: BizHawkContext) -> str: - """Gets the system name for the currently loaded ROM""" + """Gets the hash value of the currently loaded ROM""" res = (await send_requests(ctx, [{"type": "HASH"}]))[0] if res["type"] != "HASH_RESPONSE": @@ -160,6 +160,16 @@ async def get_hash(ctx: BizHawkContext) -> str: return res["value"] +async def get_memory_size(ctx: BizHawkContext, domain: str) -> int: + """Gets the size in bytes of the specified memory domain""" + res = (await send_requests(ctx, [{"type": "MEMORY_SIZE", "domain": domain}]))[0] + + if res["type"] != "MEMORY_SIZE_RESPONSE": + raise SyncError(f"Expected response of type MEMORY_SIZE_RESPONSE but got {res['type']}") + + return res["value"] + + async def get_system(ctx: BizHawkContext) -> str: """Gets the system name for the currently loaded ROM""" res = (await send_requests(ctx, [{"type": "SYSTEM"}]))[0] diff --git a/worlds/mm2/client.py b/worlds/mm2/client.py index aaa0813c76..96c477757d 100644 --- a/worlds/mm2/client.py +++ b/worlds/mm2/client.py @@ -214,10 +214,19 @@ class MegaMan2Client(BizHawkClient): last_wily: Optional[int] = None # default to wily 1 async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: - from worlds._bizhawk import RequestFailedError, read + from worlds._bizhawk import RequestFailedError, read, get_memory_size from . import MM2World try: + if (await get_memory_size(ctx.bizhawk_ctx, "PRG ROM")) < 0x3FFB0: + if "pool" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("pool") + if "request" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("request") + if "autoheal" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("autoheal") + return False + game_name, version = (await read(ctx.bizhawk_ctx, [(0x3FFB0, 21, "PRG ROM"), (0x3FFC8, 3, "PRG ROM")])) if game_name[:3] != b"MM2" or version != bytes(MM2World.world_version): From 0fc722cb28b17b155de2d16baef487eaf711766b Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 12 Jan 2025 11:01:02 -0500 Subject: [PATCH 17/21] Stardew Valley: Remove seasonal farming event, use regions instead (#4379) --- worlds/stardew_valley/__init__.py | 28 +++---------------- worlds/stardew_valley/logic/farming_logic.py | 15 +++++----- .../strings/ap_names/event_names.py | 4 --- worlds/stardew_valley/test/rules/TestTools.py | 5 +--- 4 files changed, 12 insertions(+), 40 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 6ba0e35e0a..9da650520f 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -5,29 +5,23 @@ from typing import Dict, Any, Iterable, Optional, Union, List, TextIO from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld -from . import rules from .bundles.bundle_room import BundleRoom from .bundles.bundles import get_all_bundles -from .content import content_packs, StardewContent, unpack_content, create_content +from .content import StardewContent, create_content from .early_items import setup_early_items from .items import item_table, create_items, ItemData, Group, items_by_group, get_all_filler_items, remove_limited_amount_packs from .locations import location_table, create_locations, LocationData, locations_by_tag -from .logic.bundle_logic import BundleLogic from .logic.logic import StardewLogic -from .logic.time_logic import MAX_MONTHS from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, EnabledFillerBuffs, NumberOfMovementBuffs, \ - BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType, Walnutsanity + BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType from .options.forced_options import force_change_options_if_incompatible from .options.option_groups import sv_option_groups from .options.presets import sv_options_presets from .regions import create_regions from .rules import set_rules -from .stardew_rule import True_, StardewRule, HasProgressionPercent, true_ +from .stardew_rule import True_, StardewRule, HasProgressionPercent from .strings.ap_names.event_names import Event -from .strings.entrance_names import Entrance as EntranceName from .strings.goal_names import Goal as GoalName -from .strings.metal_names import Ore -from .strings.region_names import Region as RegionName, LogicRegion logger = logging.getLogger(__name__) @@ -159,7 +153,7 @@ class StardewValleyWorld(World): self.multiworld.itempool += created_items setup_early_items(self.multiworld, self.options, self.content, self.player, self.random) - self.setup_player_events() + self.setup_logic_events() self.setup_victory() # This is really a best-effort to get the total progression items count. It is mostly used to spread grinds across spheres are push back locations that @@ -199,20 +193,6 @@ class StardewValleyWorld(World): if self.options.farm_type == FarmType.option_meadowlands and self.options.building_progression & BuildingProgression.option_progressive: self.multiworld.push_precollected(self.create_starting_item("Progressive Coop")) - def setup_player_events(self): - self.setup_action_events() - self.setup_logic_events() - - def setup_action_events(self): - spring_farming = LocationData(None, LogicRegion.spring_farming, Event.spring_farming) - self.create_event_location(spring_farming, true_, Event.spring_farming) - summer_farming = LocationData(None, LogicRegion.summer_farming, Event.summer_farming) - self.create_event_location(summer_farming, true_, Event.summer_farming) - fall_farming = LocationData(None, LogicRegion.fall_farming, Event.fall_farming) - self.create_event_location(fall_farming, true_, Event.fall_farming) - winter_farming = LocationData(None, LogicRegion.winter_farming, Event.winter_farming) - self.create_event_location(winter_farming, true_, Event.winter_farming) - def setup_logic_events(self): def register_event(name: str, region: str, rule: StardewRule): event_location = LocationData(None, region, name) diff --git a/worlds/stardew_valley/logic/farming_logic.py b/worlds/stardew_valley/logic/farming_logic.py index 88523bb85d..cb8a55e6b4 100644 --- a/worlds/stardew_valley/logic/farming_logic.py +++ b/worlds/stardew_valley/logic/farming_logic.py @@ -10,17 +10,16 @@ from .season_logic import SeasonLogicMixin from .tool_logic import ToolLogicMixin from .. import options from ..stardew_rule import StardewRule, True_, false_ -from ..strings.ap_names.event_names import Event from ..strings.fertilizer_names import Fertilizer -from ..strings.region_names import Region +from ..strings.region_names import Region, LogicRegion from ..strings.season_names import Season from ..strings.tool_names import Tool -farming_event_by_season = { - Season.spring: Event.spring_farming, - Season.summer: Event.summer_farming, - Season.fall: Event.fall_farming, - Season.winter: Event.winter_farming, +farming_region_by_season = { + Season.spring: LogicRegion.spring_farming, + Season.summer: LogicRegion.summer_farming, + Season.fall: LogicRegion.fall_farming, + Season.winter: LogicRegion.winter_farming, } @@ -54,7 +53,7 @@ class FarmingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogi if isinstance(seasons, str): seasons = (seasons,) - return self.logic.or_(*(self.logic.received(farming_event_by_season[season]) for season in seasons)) + return self.logic.or_(*(self.logic.region.can_reach(farming_region_by_season[season]) for season in seasons)) def has_island_farm(self) -> StardewRule: if self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_false: diff --git a/worlds/stardew_valley/strings/ap_names/event_names.py b/worlds/stardew_valley/strings/ap_names/event_names.py index 449bb67209..b7881b3bfd 100644 --- a/worlds/stardew_valley/strings/ap_names/event_names.py +++ b/worlds/stardew_valley/strings/ap_names/event_names.py @@ -8,9 +8,5 @@ def event(name: str): class Event: victory = event("Victory") - spring_farming = event("Spring Farming") - summer_farming = event("Summer Farming") - fall_farming = event("Fall Farming") - winter_farming = event("Winter Farming") received_walnuts = event("Received Walnuts") diff --git a/worlds/stardew_valley/test/rules/TestTools.py b/worlds/stardew_valley/test/rules/TestTools.py index 5b8975f4e7..31dd581916 100644 --- a/worlds/stardew_valley/test/rules/TestTools.py +++ b/worlds/stardew_valley/test/rules/TestTools.py @@ -1,7 +1,7 @@ from collections import Counter from .. import SVTestBase -from ... import Event, options +from ... import options from ...options import ToolProgression, SeasonRandomization from ...strings.entrance_names import Entrance from ...strings.region_names import Region @@ -74,12 +74,10 @@ class TestProgressiveToolsLogic(SVTestBase): self.assert_rule_true(rule, self.multiworld.state) self.remove(fall) - self.remove(self.create_item(Event.fall_farming)) self.assert_rule_false(rule, self.multiworld.state) self.remove(tuesday) green_house = self.create_item("Greenhouse") - self.collect(self.create_item(Event.fall_farming)) self.multiworld.state.collect(green_house) self.assert_rule_false(rule, self.multiworld.state) @@ -88,7 +86,6 @@ class TestProgressiveToolsLogic(SVTestBase): self.assertTrue(self.multiworld.get_location("Old Master Cannoli", 1).access_rule(self.multiworld.state)) self.remove(green_house) - self.remove(self.create_item(Event.fall_farming)) self.assert_rule_false(rule, self.multiworld.state) self.remove(friday) From 9928639ce2c64414fd1a96a1d85d63d33d70e073 Mon Sep 17 00:00:00 2001 From: qwint Date: Sun, 12 Jan 2025 11:01:42 -0500 Subject: [PATCH 18/21] Docs: Fix Typo in Rich Text Options Flag Documentation (#4462) --- Options.py | 2 +- docs/options api.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Options.py b/Options.py index f4724e9747..135a1dcb53 100644 --- a/Options.py +++ b/Options.py @@ -137,7 +137,7 @@ class Option(typing.Generic[T], metaclass=AssembleOptions): If this is False, the docstring is instead interpreted as plain text, and displayed as-is on the WebHost with whitespace preserved. - If this is None, it inherits the value of `World.rich_text_options_doc`. For + If this is None, it inherits the value of `WebWorld.rich_text_options_doc`. For backwards compatibility, this defaults to False, but worlds are encouraged to set it to True and use reStructuredText for their Option documentation. diff --git a/docs/options api.md b/docs/options api.md index d48a56d6c7..453cbc7e2d 100644 --- a/docs/options api.md +++ b/docs/options api.md @@ -95,7 +95,7 @@ user hovers over the yellow "(?)" icon, and included in the YAML templates gener The WebHost can display Option documentation either as plain text with all whitespace preserved (other than the base indentation), or as HTML generated from the standard Python [reStructuredText] format. Although plain text is the default for backwards compatibility, world authors are encouraged to write their Option documentation as -reStructuredText and enable rich text rendering by setting `World.rich_text_options_doc = True`. +reStructuredText and enable rich text rendering by setting `WebWorld.rich_text_options_doc = True`. [reStructuredText]: https://docutils.sourceforge.io/rst.html From 3f935aac13251d3e6780c127592b7542cc65f770 Mon Sep 17 00:00:00 2001 From: Justus Lind Date: Mon, 13 Jan 2025 03:59:16 +1000 Subject: [PATCH 19/21] Muse Dash: Change Data storage from a .txt file to a .py file and Filter Webhost Song Lists correctly (#4234) --- worlds/musedash/Items.py | 1 + worlds/musedash/MuseDashCollection.py | 98 +-- worlds/musedash/MuseDashData.py | 615 +++++++++++++++++++ worlds/musedash/MuseDashData.txt | 597 ------------------ worlds/musedash/Options.py | 29 +- worlds/musedash/__init__.py | 9 +- worlds/musedash/test/TestDifficultyRanges.py | 12 +- 7 files changed, 656 insertions(+), 705 deletions(-) create mode 100644 worlds/musedash/MuseDashData.py delete mode 100644 worlds/musedash/MuseDashData.txt diff --git a/worlds/musedash/Items.py b/worlds/musedash/Items.py index 63fd3aa51b..027a9002d5 100644 --- a/worlds/musedash/Items.py +++ b/worlds/musedash/Items.py @@ -6,6 +6,7 @@ class SongData(NamedTuple): """Special data container to contain the metadata of each song to make filtering work.""" code: Optional[int] + uid: str album: str streamer_mode: bool easy: Optional[int] diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py index 9e8c9214a1..64aa6ca49a 100644 --- a/worlds/musedash/MuseDashCollection.py +++ b/worlds/musedash/MuseDashCollection.py @@ -1,13 +1,9 @@ -from .Items import SongData, AlbumData -from typing import Dict, List, Set, Optional +from .Items import SongData +from .MuseDashData import SONG_DATA +from typing import Dict, List, Set from collections import ChainMap -def load_text_file(name: str) -> str: - import pkgutil - return pkgutil.get_data(__name__, name).decode() - - class MuseDashCollections: """Contains all the data of Muse Dash, loaded from MuseDashData.txt.""" STARTING_CODE = 2900000 @@ -33,15 +29,6 @@ class MuseDashCollections: "Rin Len's Mirrorland", # Paid DLC not included in Muse Plus ] - DIFF_OVERRIDES: List[str] = [ - "MuseDash ka nanika hi", - "Rush-Hour", - "Find this Month's Featured Playlist", - "PeroPero in the Universe", - "umpopoff", - "P E R O P E R O Brother Dance", - ] - REMOVED_SONGS = [ "CHAOS Glitch", "FM 17314 SUGAR RADIO", @@ -50,9 +37,7 @@ class MuseDashCollections: "Tsukuyomi Ni Naru Replaced", ] - album_items: Dict[str, AlbumData] = {} - album_locations: Dict[str, int] = {} - song_items: Dict[str, SongData] = {} + song_items = SONG_DATA song_locations: Dict[str, int] = {} trap_items: Dict[str, int] = { @@ -65,7 +50,7 @@ class MuseDashCollections: "Gray Scale Trap": STARTING_CODE + 7, "Nyaa SFX Trap": STARTING_CODE + 8, "Error SFX Trap": STARTING_CODE + 9, - "Focus Line Trap": STARTING_CODE + 10, + "Focus Line Trap": STARTING_CODE + 10, } sfx_trap_items: List[str] = [ @@ -85,65 +70,13 @@ class MuseDashCollections: "Extra Life": 1, } - item_names_to_id: ChainMap = ChainMap({}, filler_items, trap_items) - location_names_to_id: ChainMap = ChainMap(song_locations, album_locations) + item_names_to_id: ChainMap = ChainMap({k: v.code for k, v in SONG_DATA.items()}, filler_items, trap_items) + location_names_to_id: ChainMap = ChainMap(song_locations) def __init__(self) -> None: self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE - item_id_index = self.STARTING_CODE + 50 - full_file = load_text_file("MuseDashData.txt") - seen_albums = set() - for line in full_file.splitlines(): - line = line.strip() - sections = line.split("|") - - album = sections[2] - if album not in seen_albums: - seen_albums.add(album) - self.album_items[album] = AlbumData(item_id_index) - item_id_index += 1 - - # Data is in the format 'Song|UID|Album|StreamerMode|EasyDiff|HardDiff|MasterDiff|SecretDiff' - song_name = sections[0] - # [1] is used in the client copy to make sure item id's match. - steamer_mode = sections[3] == "True" - - if song_name in self.DIFF_OVERRIDES: - # These songs use non-standard difficulty values. Which are being overriden with standard values. - # But also avoid filling any missing difficulties (i.e. 0s) with a difficulty value. - if sections[4] != '0': - diff_of_easy = 4 - else: - diff_of_easy = None - - if sections[5] != '0': - diff_of_hard = 7 - else: - diff_of_hard = None - - if sections[6] != '0': - diff_of_master = 10 - else: - diff_of_master = None - else: - diff_of_easy = self.parse_song_difficulty(sections[4]) - diff_of_hard = self.parse_song_difficulty(sections[5]) - diff_of_master = self.parse_song_difficulty(sections[6]) - - self.song_items[song_name] = SongData(item_id_index, album, steamer_mode, - diff_of_easy, diff_of_hard, diff_of_master) - item_id_index += 1 - - self.item_names_to_id.update({name: data.code for name, data in self.song_items.items()}) - self.item_names_to_id.update({name: data.code for name, data in self.album_items.items()}) - location_id_index = self.STARTING_CODE - for name in self.album_items.keys(): - self.album_locations[f"{name}-0"] = location_id_index - self.album_locations[f"{name}-1"] = location_id_index + 1 - location_id_index += 2 - for name in self.song_items.keys(): self.song_locations[f"{name}-0"] = location_id_index self.song_locations[f"{name}-1"] = location_id_index + 1 @@ -157,7 +90,7 @@ class MuseDashCollections: for songKey, songData in self.song_items.items(): if not self.song_matches_dlc_filter(songData, dlc_songs): continue - + if songKey in self.REMOVED_SONGS: continue @@ -193,18 +126,3 @@ class MuseDashCollections: return True return False - - def parse_song_difficulty(self, difficulty: str) -> Optional[int]: - """Attempts to parse the song difficulty.""" - if len(difficulty) <= 0 or difficulty == "?" or difficulty == "¿": - return None - - # 0 is used as a filler and no songs actually have a 0 difficulty song. - if difficulty == "0": - return None - - # Curse the 2023 april fools update. Used on 3rd Avenue. - if difficulty == "〇": - return 10 - - return int(difficulty) diff --git a/worlds/musedash/MuseDashData.py b/worlds/musedash/MuseDashData.py new file mode 100644 index 0000000000..1700f956aa --- /dev/null +++ b/worlds/musedash/MuseDashData.py @@ -0,0 +1,615 @@ +from .Items import SongData +from typing import Dict + + +# Auto Generated +SONG_DATA: Dict[str, SongData] = { + "Magical Wonderland": SongData(2900051, "0-48", "Default Music", True, 1, 3, None), + "Iyaiya": SongData(2900052, "0-0", "Default Music", True, 1, 4, None), + "Wonderful Pain": SongData(2900053, "0-2", "Default Music", False, 1, 3, None), + "Breaking Dawn": SongData(2900054, "0-3", "Default Music", True, 2, 4, None), + "One-Way Subway": SongData(2900055, "0-4", "Default Music", True, 1, 4, None), + "Frost Land": SongData(2900056, "0-1", "Default Music", False, 1, 3, 6), + "Heart-Pounding Flight": SongData(2900057, "0-5", "Default Music", True, 2, 5, None), + "Pancake is Love": SongData(2900058, "0-29", "Default Music", True, 2, 4, 7), + "Shiguang Tuya": SongData(2900059, "0-6", "Default Music", True, 2, 5, None), + "Evolution": SongData(2900060, "0-37", "Default Music", False, 2, 4, 7), + "Dolphin and Broadcast": SongData(2900061, "0-7", "Default Music", True, 2, 5, None), + "Yuki no Shizuku Ame no Oto": SongData(2900062, "0-8", "Default Music", True, 2, 4, 6), + "Best One feat.tooko": SongData(2900063, "0-43", "Default Music", False, 3, 5, None), + "Candy-coloured Love Theory": SongData(2900064, "0-31", "Default Music", False, 2, 4, 6), + "Night Wander": SongData(2900065, "0-38", "Default Music", False, 3, 5, 7), + "Dohna Dohna no Uta": SongData(2900066, "0-46", "Default Music", False, 2, 4, 6), + "Spring Carnival": SongData(2900067, "0-9", "Default Music", False, 2, 4, 7), + "DISCO NIGHT": SongData(2900068, "0-30", "Default Music", True, 2, 4, 7), + "Koi no Moonlight": SongData(2900069, "0-49", "Default Music", False, 2, 5, 8), + "Lian Ai Audio Navigation": SongData(2900070, "0-10", "Default Music", False, 3, 5, 7), + "Lights of Muse": SongData(2900071, "0-11", "Default Music", True, 4, 6, 8), + "midstream jam": SongData(2900072, "0-12", "Default Music", False, 2, 5, 8), + "Nihao": SongData(2900073, "0-40", "Default Music", False, 3, 5, 7), + "Confession": SongData(2900074, "0-13", "Default Music", False, 3, 5, 8), + "Galaxy Striker": SongData(2900075, "0-32", "Default Music", False, 4, 7, 9), + "Departure Road": SongData(2900076, "0-14", "Default Music", True, 2, 5, 8), + "Bass Telekinesis": SongData(2900077, "0-15", "Default Music", False, 2, 5, 8), + "Cage of Almeria": SongData(2900078, "0-16", "Default Music", True, 3, 5, 7), + "Ira": SongData(2900079, "0-17", "Default Music", True, 4, 6, 8), + "Blackest Luxury Car": SongData(2900080, "0-18", "Default Music", True, 3, 6, 8), + "Medicine of Sing": SongData(2900081, "0-19", "Default Music", False, 3, 6, 8), + "irregulyze": SongData(2900082, "0-20", "Default Music", True, 3, 6, 8), + "I don't care about Christmas though": SongData(2900083, "0-47", "Default Music", False, 4, 6, 8), + "Imaginary World": SongData(2900084, "0-21", "Default Music", True, 4, 6, 8), + "Dysthymia": SongData(2900085, "0-22", "Default Music", True, 4, 7, 9), + "From the New World": SongData(2900086, "0-42", "Default Music", False, 2, 5, 7), + "NISEGAO": SongData(2900087, "0-33", "Default Music", True, 4, 7, 9), + "Say! Fanfare!": SongData(2900088, "0-44", "Default Music", False, 4, 6, 9), + "Star Driver": SongData(2900089, "0-34", "Default Music", True, 5, 7, 9), + "Formation": SongData(2900090, "0-23", "Default Music", True, 4, 6, 9), + "Shinsou Masui": SongData(2900091, "0-24", "Default Music", True, 4, 6, 10), + "Mezame Eurythmics": SongData(2900092, "0-50", "Default Music", False, 4, 6, 9), + "Shenri Kuaira -repeat-": SongData(2900093, "0-51", "Default Music", False, 5, 7, 9), + "Latitude": SongData(2900094, "0-25", "Default Music", True, 3, 6, 9), + "Aqua Stars": SongData(2900095, "0-39", "Default Music", False, 5, 7, 10), + "Funkotsu Saishin Casino": SongData(2900096, "0-26", "Default Music", False, 5, 7, 10), + "Clock Room & Spiritual World": SongData(2900097, "0-27", "Default Music", True, 4, 6, 9), + "INTERNET OVERDOSE": SongData(2900098, "0-52", "Default Music", False, 3, 6, 9), + "Tu Hua": SongData(2900099, "0-35", "Default Music", True, 4, 7, 9), + "Mujinku-Vacuum": SongData(2900100, "0-28", "Default Music", False, 5, 7, 11), + "MilK": SongData(2900101, "0-36", "Default Music", False, 5, 7, 9), + "umpopoff": SongData(2900102, "0-41", "Default Music", False, None, 7, None), + "Mopemope": SongData(2900103, "0-45", "Default Music", False, 4, 7, 9), + "The Happycore Idol": SongData(2900105, "43-0", "MD Plus Project", True, 2, 5, 7), + "Amatsumikaboshi": SongData(2900106, "43-1", "MD Plus Project", True, 4, 6, 8), + "ARIGA THESIS": SongData(2900107, "43-2", "MD Plus Project", True, 3, 6, 10), + "Night of Nights": SongData(2900108, "43-3", "MD Plus Project", False, 4, 7, 10), + "#Psychedelic_Meguro_River": SongData(2900109, "43-4", "MD Plus Project", False, 3, 6, 8), + "can you feel it": SongData(2900110, "43-5", "MD Plus Project", False, 4, 6, 8), + "Midnight O'clock": SongData(2900111, "43-6", "MD Plus Project", True, 3, 6, 8), + "Rin": SongData(2900112, "43-7", "MD Plus Project", True, 5, 7, 10), + "Smile-mileS": SongData(2900113, "43-8", "MD Plus Project", False, 6, 8, 10), + "Believing and Being": SongData(2900114, "43-9", "MD Plus Project", True, 4, 6, 9), + "Catalyst": SongData(2900115, "43-10", "MD Plus Project", False, 5, 7, 9), + "don't!stop!eroero!": SongData(2900116, "43-11", "MD Plus Project", True, 5, 7, 9), + "pa pi pu pi pu pi pa": SongData(2900117, "43-12", "MD Plus Project", False, 6, 8, 10), + "Sand Maze": SongData(2900118, "43-13", "MD Plus Project", True, 6, 8, 10), + "Diffraction": SongData(2900119, "43-14", "MD Plus Project", True, 5, 8, 10), + "AKUMU": SongData(2900120, "43-15", "MD Plus Project", False, 4, 6, 8), + "Queen Aluett": SongData(2900121, "43-16", "MD Plus Project", True, 7, 9, 11), + "DROPS": SongData(2900122, "43-17", "MD Plus Project", False, 2, 5, 8), + "Frightfully-insane Flan-chan's frightful song": SongData(2900123, "43-18", "MD Plus Project", False, 5, 7, 10), + "snooze": SongData(2900124, "43-19", "MD Plus Project", False, 5, 7, 10), + "Kuishinbo Hacker feat.Kuishinbo Akachan": SongData(2900125, "43-20", "MD Plus Project", True, 5, 7, 9), + "Inu no outa": SongData(2900126, "43-21", "MD Plus Project", True, 3, 5, 7), + "Prism Fountain": SongData(2900127, "43-22", "MD Plus Project", True, 7, 9, 11), + "Gospel": SongData(2900128, "43-23", "MD Plus Project", False, 4, 6, 9), + "East Ai Li Lovely": SongData(2900130, "62-0", "Happy Otaku Pack Vol.17", False, 2, 4, 7), + "Mori Umi no Fune": SongData(2900131, "62-1", "Happy Otaku Pack Vol.17", True, 5, 7, 9), + "Ooi": SongData(2900132, "62-2", "Happy Otaku Pack Vol.17", True, 5, 7, 10), + "Numatta!!": SongData(2900133, "62-3", "Happy Otaku Pack Vol.17", True, 5, 7, 9), + "SATELLITE": SongData(2900134, "62-4", "Happy Otaku Pack Vol.17", False, 5, 7, 9), + "Fantasia Sonata Colorful feat. V!C": SongData(2900135, "62-5", "Happy Otaku Pack Vol.17", True, 6, 8, 11), + "MuseDash ka nanika hi": SongData(2900137, "61-0", "Ola Dash", True, 4, 7, 10), + "Aleph-0": SongData(2900138, "61-1", "Ola Dash", True, 7, 9, 11), + "Buttoba Supernova": SongData(2900139, "61-2", "Ola Dash", False, 5, 7, 10), + "Rush-Hour": SongData(2900140, "61-3", "Ola Dash", False, 4, 7, 10), + "3rd Avenue": SongData(2900141, "61-4", "Ola Dash", False, 3, 5, 10), + "WORLDINVADER": SongData(2900142, "61-5", "Ola Dash", True, 5, 8, 10), + "N3V3R G3T OV3R": SongData(2900144, "60-0", "maimai DX Limited-time Suite", True, 4, 7, 10), + "Oshama Scramble!": SongData(2900145, "60-1", "maimai DX Limited-time Suite", True, 5, 7, 10), + "Valsqotch": SongData(2900146, "60-2", "maimai DX Limited-time Suite", True, 5, 9, 11), + "Paranormal My Mind": SongData(2900147, "60-3", "maimai DX Limited-time Suite", True, 5, 7, 9), + "Flower, snow and Drum'n'bass.": SongData(2900148, "60-4", "maimai DX Limited-time Suite", True, 5, 8, 10), + "Amenohoakari": SongData(2900149, "60-5", "maimai DX Limited-time Suite", True, 6, 8, 10), + "Boiling Blood": SongData(2900151, "59-0", "MSR Anthology", True, 5, 8, 10), + "ManiFesto": SongData(2900152, "59-1", "MSR Anthology", True, 4, 6, 9), + "Operation Blade": SongData(2900153, "59-2", "MSR Anthology", True, 3, 5, 7), + "Radiant": SongData(2900154, "59-3", "MSR Anthology", True, 3, 5, 8), + "Renegade": SongData(2900155, "59-4", "MSR Anthology", True, 3, 5, 8), + "Speed of Light": SongData(2900156, "59-5", "MSR Anthology", False, 1, 4, 7), + "Dossoles Holiday": SongData(2900157, "59-6", "MSR Anthology", True, 5, 7, 9), + "Autumn Moods": SongData(2900158, "59-7", "MSR Anthology", True, 3, 5, 7), + "People People": SongData(2900160, "58-0", "Nanahira Paradise", True, 5, 7, 9), + "Endless Error Loop": SongData(2900161, "58-1", "Nanahira Paradise", True, 4, 7, 9), + "Forbidden Pizza!": SongData(2900162, "58-2", "Nanahira Paradise", True, 5, 7, 9), + "Don't Make the Vocalist do Anything Insane": SongData(2900163, "58-3", "Nanahira Paradise", True, 5, 8, 9), + "Tokimeki*Meteostrike": SongData(2900165, "57-0", "Happy Otaku Pack Vol.16", True, 3, 6, 8), + "Down Low": SongData(2900166, "57-1", "Happy Otaku Pack Vol.16", True, 4, 6, 8), + "LOUDER MACHINE": SongData(2900167, "57-2", "Happy Otaku Pack Vol.16", True, 5, 7, 9), + "Sorewa mo Lovechu": SongData(2900168, "57-3", "Happy Otaku Pack Vol.16", True, 5, 7, 10), + "Rave_Tech": SongData(2900169, "57-4", "Happy Otaku Pack Vol.16", True, 5, 8, 10), + "Brilliant & Shining!": SongData(2900170, "57-5", "Happy Otaku Pack Vol.16", False, 5, 8, 10), + "Psyched Fevereiro": SongData(2900172, "56-0", "Give Up TREATMENT Vol.11", False, 5, 8, 10), + "Inferno City": SongData(2900173, "56-1", "Give Up TREATMENT Vol.11", False, 6, 8, 10), + "Paradigm Shift": SongData(2900174, "56-2", "Give Up TREATMENT Vol.11", False, 4, 7, 10), + "Snapdragon": SongData(2900175, "56-3", "Give Up TREATMENT Vol.11", False, 5, 7, 10), + "Prestige and Vestige": SongData(2900176, "56-4", "Give Up TREATMENT Vol.11", True, 6, 8, 11), + "Tiny Fate": SongData(2900177, "56-5", "Give Up TREATMENT Vol.11", False, 7, 9, 11), + "Tsuki ni Murakumo Hana ni Kaze": SongData(2900179, "55-0", "Touhou Mugakudan -II-", False, 3, 5, 7), + "Patchouli's - Best Hit GSK": SongData(2900180, "55-1", "Touhou Mugakudan -II-", False, 3, 5, 8), + "Monosugoi Space Shuttle de Koishi ga Monosugoi uta": SongData(2900181, "55-2", "Touhou Mugakudan -II-", False, 3, 5, 7), + "Kakoinaki Yo wa Ichigo no Tsukikage": SongData(2900182, "55-3", "Touhou Mugakudan -II-", False, 3, 6, 8), + "Psychedelic Kizakura Doumei": SongData(2900183, "55-4", "Touhou Mugakudan -II-", False, 4, 7, 10), + "Mischievous Sensation": SongData(2900184, "55-5", "Touhou Mugakudan -II-", False, 5, 7, 9), + "White Canvas": SongData(2900186, "54-0", "MEGAREX THE FUTURE", False, 3, 6, 8), + "Gloomy Flash": SongData(2900187, "54-1", "MEGAREX THE FUTURE", False, 5, 8, 10), + "Find this Month's Featured Playlist": SongData(2900188, "54-2", "MEGAREX THE FUTURE", False, 4, 7, 10), + "Sunday Night": SongData(2900189, "54-3", "MEGAREX THE FUTURE", False, 3, 6, 9), + "Goodbye Goodnight": SongData(2900190, "54-4", "MEGAREX THE FUTURE", False, 4, 6, 9), + "ENDLESS CIDER": SongData(2900191, "54-5", "MEGAREX THE FUTURE", False, 4, 6, 8), + "On And On!!": SongData(2900193, "53-0", "Happy Otaku Pack Vol.15", True, 4, 7, 9), + "Trip!": SongData(2900194, "53-1", "Happy Otaku Pack Vol.15", True, 3, 5, 7), + "Hoshi no otoshimono": SongData(2900195, "53-2", "Happy Otaku Pack Vol.15", False, 5, 7, 9), + "Plucky Race": SongData(2900196, "53-3", "Happy Otaku Pack Vol.15", True, 5, 8, 10), + "Fantasia Sonata Destiny": SongData(2900197, "53-4", "Happy Otaku Pack Vol.15", True, 3, 7, 10), + "Run through": SongData(2900198, "53-5", "Happy Otaku Pack Vol.15", False, 5, 8, 10), + "marooned night": SongData(2900200, "52-0", "MUSE RADIO FM103", False, 2, 4, 6), + "daydream girl": SongData(2900201, "52-1", "MUSE RADIO FM103", False, 3, 6, 8), + "Not Ornament": SongData(2900202, "52-2", "MUSE RADIO FM103", True, 3, 5, 8), + "Baby Pink": SongData(2900203, "52-3", "MUSE RADIO FM103", False, 3, 5, 8), + "I'm Here": SongData(2900204, "52-4", "MUSE RADIO FM103", False, 4, 6, 8), + "Masquerade Diary": SongData(2900206, "51-0", "Virtual Idol Production", True, 2, 5, 8), + "Reminiscence": SongData(2900207, "51-1", "Virtual Idol Production", True, 5, 7, 9), + "DarakuDatenshi": SongData(2900208, "51-2", "Virtual Idol Production", True, 3, 6, 9), + "D.I.Y.": SongData(2900209, "51-3", "Virtual Idol Production", False, 4, 6, 9), + "Boys in Virtual Land": SongData(2900210, "51-4", "Virtual Idol Production", False, 4, 7, 9), + "kui": SongData(2900211, "51-5", "Virtual Idol Production", True, 5, 7, 9), + "Nyan Cat": SongData(2900213, "50-0", "Nyanya Universe!", False, 4, 7, 9), + "PeroPero in the Universe": SongData(2900214, "50-1", "Nyanya Universe!", True, 4, 7, 10), + "In-kya Yo-kya Onmyoji": SongData(2900215, "50-2", "Nyanya Universe!", False, 6, 8, 10), + "KABOOOOOM!!!!": SongData(2900216, "50-3", "Nyanya Universe!", True, 4, 6, 8), + "Doppelganger": SongData(2900217, "50-4", "Nyanya Universe!", True, 5, 7, 9), + "Pray a LOVE": SongData(2900219, "49-0", "DokiDoki! Valentine!", False, 2, 5, 8), + "Love-Avoidance Addiction": SongData(2900220, "49-1", "DokiDoki! Valentine!", False, 3, 5, 7), + "Daisuki Dayo feat.Wotoha": SongData(2900221, "49-2", "DokiDoki! Valentine!", False, 5, 7, 10), + "glory day": SongData(2900223, "48-0", "DJMAX Reflect", False, 2, 5, 7), + "Bright Dream": SongData(2900224, "48-1", "DJMAX Reflect", False, 2, 4, 7), + "Groovin Up": SongData(2900225, "48-2", "DJMAX Reflect", False, 4, 6, 8), + "I Want You": SongData(2900226, "48-3", "DJMAX Reflect", False, 3, 6, 8), + "OBLIVION": SongData(2900227, "48-4", "DJMAX Reflect", False, 3, 6, 9), + "Elastic STAR": SongData(2900228, "48-5", "DJMAX Reflect", False, 4, 6, 8), + "U.A.D": SongData(2900229, "48-6", "DJMAX Reflect", False, 4, 6, 8), + "Jealousy": SongData(2900230, "48-7", "DJMAX Reflect", False, 3, 5, 7), + "Memory of Beach": SongData(2900231, "48-8", "DJMAX Reflect", False, 3, 6, 8), + "Don't Die": SongData(2900232, "48-9", "DJMAX Reflect", False, 6, 8, 10), + "Y CE Ver.": SongData(2900233, "48-10", "DJMAX Reflect", False, 4, 6, 9), + "Fancy Night": SongData(2900234, "48-11", "DJMAX Reflect", False, 4, 6, 8), + "Can We Talk": SongData(2900235, "48-12", "DJMAX Reflect", False, 4, 6, 8), + "Give Me 5": SongData(2900236, "48-13", "DJMAX Reflect", False, 2, 6, 8), + "Nightmare": SongData(2900237, "48-14", "DJMAX Reflect", False, 7, 9, 11), + "Haze of Autumn": SongData(2900239, "47-0", "Arcaea", True, 3, 6, 9), + "GIMME DA BLOOD": SongData(2900240, "47-1", "Arcaea", False, 3, 6, 9), + "Libertas": SongData(2900241, "47-2", "Arcaea", False, 4, 7, 10), + "Cyaegha": SongData(2900242, "47-3", "Arcaea", False, 5, 7, 9), + "Bang!!": SongData(2900244, "46-0", "Happy Otaku Pack Vol.14", False, 4, 6, 8), + "Paradise 2": SongData(2900245, "46-1", "Happy Otaku Pack Vol.14", False, 4, 6, 8), + "Symbol": SongData(2900246, "46-2", "Happy Otaku Pack Vol.14", False, 5, 7, 9), + "Nekojarashi": SongData(2900247, "46-3", "Happy Otaku Pack Vol.14", False, 5, 8, 10), + "A Philosophical Wanderer": SongData(2900248, "46-4", "Happy Otaku Pack Vol.14", False, 4, 6, 10), + "Isouten": SongData(2900249, "46-5", "Happy Otaku Pack Vol.14", True, 6, 8, 10), + "ONOMATO Pairing!!!": SongData(2900251, "45-0", "WACCA Horizon", False, 4, 6, 9), + "with U": SongData(2900252, "45-1", "WACCA Horizon", False, 6, 8, 10), + "Chariot": SongData(2900253, "45-2", "WACCA Horizon", False, 3, 6, 9), + "GASHATT": SongData(2900254, "45-3", "WACCA Horizon", False, 5, 7, 10), + "LIN NE KRO NE feat. lasah": SongData(2900255, "45-4", "WACCA Horizon", False, 6, 8, 10), + "ANGEL HALO": SongData(2900256, "45-5", "WACCA Horizon", False, 5, 8, 11), + "Party in the HOLLOWood": SongData(2900258, "44-0", "Happy Otaku Pack Vol.13", False, 3, 6, 8), + "Ying Ying da Zuozhan": SongData(2900259, "44-1", "Happy Otaku Pack Vol.13", True, 5, 7, 9), + "Howlin' Pumpkin": SongData(2900260, "44-2", "Happy Otaku Pack Vol.13", True, 4, 6, 8), + "Bad Apple!! feat. Nomico": SongData(2900262, "42-0", "Touhou Mugakudan -I-", False, 1, 3, 6), + "Iro wa Nioedo, Chirinuru wo": SongData(2900263, "42-1", "Touhou Mugakudan -I-", False, 2, 4, 7), + "Cirno's Perfect Math Class": SongData(2900264, "42-2", "Touhou Mugakudan -I-", False, 4, 7, 9), + "Hiiro Gekka Kyousai no Zetsu": SongData(2900265, "42-3", "Touhou Mugakudan -I-", False, 4, 6, 8), + "Flowery Moonlit Night": SongData(2900266, "42-4", "Touhou Mugakudan -I-", False, 3, 6, 8), + "Unconscious Requiem": SongData(2900267, "42-5", "Touhou Mugakudan -I-", False, 3, 6, 8), + "Super Battleworn Insomniac": SongData(2900269, "41-0", "7th Beat Games", True, 4, 7, 9), + "Bomb-Sniffing Pomeranian": SongData(2900270, "41-1", "7th Beat Games", True, 4, 6, 8), + "Rollerdisco Rumble": SongData(2900271, "41-2", "7th Beat Games", True, 4, 6, 9), + "Rose Garden": SongData(2900272, "41-3", "7th Beat Games", False, 5, 8, 9), + "EMOMOMO": SongData(2900273, "41-4", "7th Beat Games", True, 4, 7, 10), + "Heracles": SongData(2900274, "41-5", "7th Beat Games", False, 6, 8, 10), + "Rush-More": SongData(2900276, "40-0", "Happy Otaku Pack Vol.12", False, 4, 7, 9), + "Kill My Fortune": SongData(2900277, "40-1", "Happy Otaku Pack Vol.12", False, 5, 7, 10), + "Yosari Tsukibotaru Suminoborite": SongData(2900278, "40-2", "Happy Otaku Pack Vol.12", False, 5, 7, 9), + "JUMP! HardCandy": SongData(2900279, "40-3", "Happy Otaku Pack Vol.12", False, 3, 6, 8), + "Hibari": SongData(2900280, "40-4", "Happy Otaku Pack Vol.12", False, 3, 5, 8), + "OCCHOCO-REST-LESS": SongData(2900281, "40-5", "Happy Otaku Pack Vol.12", True, 4, 7, 9), + "See-Saw Day": SongData(2900283, "39-0", "MUSE RADIO FM102", True, 1, 3, 6), + "happy hour": SongData(2900284, "39-1", "MUSE RADIO FM102", True, 2, 4, 7), + "Seikimatsu no Natsu": SongData(2900285, "39-2", "MUSE RADIO FM102", True, 4, 6, 8), + "twinkle night": SongData(2900286, "39-3", "MUSE RADIO FM102", False, 3, 6, 8), + "ARUYA HARERUYA": SongData(2900287, "39-4", "MUSE RADIO FM102", False, 2, 5, 7), + "Blush": SongData(2900288, "39-5", "MUSE RADIO FM102", False, 2, 4, 7), + "Naked Summer": SongData(2900289, "39-6", "MUSE RADIO FM102", True, 4, 6, 8), + "BLESS ME": SongData(2900290, "39-7", "MUSE RADIO FM102", True, 2, 5, 7), + "FM 17314 SUGAR RADIO": SongData(2900291, "39-8", "MUSE RADIO FM102", True, None, None, None), + "NO ONE YES MAN": SongData(2900293, "38-0", "Phigros", False, 5, 7, 9), + "Snowfall, Merry Christmas": SongData(2900294, "38-1", "Phigros", False, 5, 8, 10), + "Igallta": SongData(2900295, "38-2", "Phigros", False, 6, 8, 10), + "Colored Glass": SongData(2900297, "37-0", "Cute Is Everything Vol.7", False, 1, 4, 7), + "Neonlights": SongData(2900298, "37-1", "Cute Is Everything Vol.7", False, 4, 7, 9), + "Hope for the flowers": SongData(2900299, "37-2", "Cute Is Everything Vol.7", False, 4, 7, 9), + "Seaside Cycling on May 30": SongData(2900300, "37-3", "Cute Is Everything Vol.7", False, 3, 6, 8), + "SKY HIGH": SongData(2900301, "37-4", "Cute Is Everything Vol.7", False, 2, 4, 6), + "Mousou Chu!!": SongData(2900302, "37-5", "Cute Is Everything Vol.7", False, 4, 7, 8), + "NightTheater": SongData(2900304, "36-0", "Give Up TREATMENT Vol.10", True, 6, 8, 11), + "Cutter": SongData(2900305, "36-1", "Give Up TREATMENT Vol.10", False, 4, 7, 10), + "bamboo": SongData(2900306, "36-2", "Give Up TREATMENT Vol.10", False, 6, 8, 10), + "enchanted love": SongData(2900307, "36-3", "Give Up TREATMENT Vol.10", False, 2, 6, 9), + "c.s.q.n.": SongData(2900308, "36-4", "Give Up TREATMENT Vol.10", False, 5, 8, 11), + "Booouncing!!": SongData(2900309, "36-5", "Give Up TREATMENT Vol.10", False, 5, 7, 10), + "PeroPeroGames goes Bankrupt": SongData(2900311, "35-0", "Happy Otaku Pack SP", True, 6, 8, 10), + "MARENOL": SongData(2900312, "35-1", "Happy Otaku Pack SP", False, 4, 7, 10), + "I am really good at Japanese style": SongData(2900313, "35-2", "Happy Otaku Pack SP", True, 6, 8, 10), + "Rush B": SongData(2900314, "35-3", "Happy Otaku Pack SP", True, 4, 7, 9), + "DataErr0r": SongData(2900315, "35-4", "Happy Otaku Pack SP", False, 5, 7, 9), + "Burn": SongData(2900316, "35-5", "Happy Otaku Pack SP", True, 4, 7, 9), + "ALiVE": SongData(2900318, "34-0", "HARDCORE TANO*C", False, 5, 7, 10), + "BATTLE NO.1": SongData(2900319, "34-1", "HARDCORE TANO*C", False, 5, 8, 10), + "Cthugha": SongData(2900320, "34-2", "HARDCORE TANO*C", False, 6, 8, 10), + "TWINKLE*MAGIC": SongData(2900321, "34-3", "HARDCORE TANO*C", False, 4, 7, 10), + "Comet Coaster": SongData(2900322, "34-4", "HARDCORE TANO*C", False, 6, 8, 10), + "XODUS": SongData(2900323, "34-5", "HARDCORE TANO*C", False, 7, 9, 11), + "Fireflies": SongData(2900325, "33-0", "cyTus", True, 1, 4, 7), + "Light up my love!!": SongData(2900326, "33-1", "cyTus", True, 3, 5, 7), + "Happiness Breeze": SongData(2900327, "33-2", "cyTus", True, 4, 6, 8), + "Chrome VOX": SongData(2900328, "33-3", "cyTus", True, 6, 8, 10), + "CHAOS": SongData(2900329, "33-4", "cyTus", True, 3, 6, 9), + "Saika": SongData(2900330, "33-5", "cyTus", True, 3, 5, 8), + "Standby for Action": SongData(2900331, "33-6", "cyTus", True, 4, 6, 8), + "Hydrangea": SongData(2900332, "33-7", "cyTus", True, 5, 7, 9), + "Amenemhat": SongData(2900333, "33-8", "cyTus", True, 6, 8, 10), + "Santouka": SongData(2900334, "33-9", "cyTus", True, 2, 5, 8), + "HEXENNACHTROCK-katashihaya-": SongData(2900335, "33-10", "cyTus", True, 4, 8, 10), + "Blah!!": SongData(2900336, "33-11", "cyTus", True, 5, 8, 11), + "CHAOS Glitch": SongData(2900337, "33-12", "cyTus", True, None, None, None), + "Preparara": SongData(2900339, "32-0", "Let's Do Bad Things Together", False, 1, 4, 6), + "Whatcha;Whatcha Doin'": SongData(2900340, "32-1", "Let's Do Bad Things Together", False, 3, 6, 9), + "Madara": SongData(2900341, "32-2", "Let's Do Bad Things Together", False, 4, 6, 9), + "pICARESq": SongData(2900342, "32-3", "Let's Do Bad Things Together", False, 4, 6, 8), + "Desastre": SongData(2900343, "32-4", "Let's Do Bad Things Together", False, 4, 6, 8), + "Shoot for the Moon": SongData(2900344, "32-5", "Let's Do Bad Things Together", False, 2, 5, 8), + "The 90's Decision": SongData(2900346, "31-0", "Happy Otaku Pack Vol.11", True, 5, 7, 9), + "Medusa": SongData(2900347, "31-1", "Happy Otaku Pack Vol.11", False, 4, 6, 8), + "Final Step!": SongData(2900348, "31-2", "Happy Otaku Pack Vol.11", False, 5, 7, 10), + "MAGENTA POTION": SongData(2900349, "31-3", "Happy Otaku Pack Vol.11", False, 4, 7, 9), + "Cross Ray": SongData(2900350, "31-4", "Happy Otaku Pack Vol.11", False, 3, 6, 9), + "Square Lake": SongData(2900351, "31-5", "Happy Otaku Pack Vol.11", False, 6, 8, 9), + "Girly Cupid": SongData(2900353, "30-0", "Cute Is Everything Vol.6", False, 3, 6, 8), + "sheep in the light": SongData(2900354, "30-1", "Cute Is Everything Vol.6", False, 2, 5, 8), + "Breaker city": SongData(2900355, "30-2", "Cute Is Everything Vol.6", False, 4, 6, 9), + "heterodoxy": SongData(2900356, "30-3", "Cute Is Everything Vol.6", False, 4, 6, 8), + "Computer Music Girl": SongData(2900357, "30-4", "Cute Is Everything Vol.6", False, 3, 5, 7), + "Focus Point": SongData(2900358, "30-5", "Cute Is Everything Vol.6", True, 2, 5, 7), + "Groove Prayer": SongData(2900360, "29-0", "Let' s GROOVE!", True, 3, 5, 7), + "FUJIN Rumble": SongData(2900361, "29-1", "Let' s GROOVE!", True, 5, 7, 10), + "Marry me, Nightmare": SongData(2900362, "29-2", "Let' s GROOVE!", False, 6, 8, 11), + "HG Makaizou Polyvinyl Shounen": SongData(2900363, "29-3", "Let' s GROOVE!", True, 4, 7, 9), + "Seizya no Ibuki": SongData(2900364, "29-4", "Let' s GROOVE!", True, 6, 8, 10), + "ouroboros -twin stroke of the end-": SongData(2900365, "29-5", "Let' s GROOVE!", True, 4, 6, 9), + "Heisha Onsha": SongData(2900367, "28-0", "Happy Otaku Pack Vol.10", False, 4, 6, 8), + "Ginevra": SongData(2900368, "28-1", "Happy Otaku Pack Vol.10", True, 5, 7, 10), + "Paracelestia": SongData(2900369, "28-2", "Happy Otaku Pack Vol.10", False, 5, 8, 10), + "un secret": SongData(2900370, "28-3", "Happy Otaku Pack Vol.10", False, 2, 4, 6), + "Good Life": SongData(2900371, "28-4", "Happy Otaku Pack Vol.10", False, 4, 6, 8), + "nini-nini-": SongData(2900372, "28-5", "Happy Otaku Pack Vol.10", False, 4, 7, 9), + "Can I friend you on Bassbook? lol": SongData(2900374, "27-0", "Nanahira Festival", False, 3, 6, 8), + "Gaming*Everything": SongData(2900375, "27-1", "Nanahira Festival", False, 5, 8, 11), + "Renji de haochi": SongData(2900376, "27-2", "Nanahira Festival", False, 5, 7, 9), + "You Make My Life 1UP": SongData(2900377, "27-3", "Nanahira Festival", False, 4, 6, 8), + "Newbies, Geeks, Internets": SongData(2900378, "27-4", "Nanahira Festival", False, 6, 8, 10), + "Onegai!Kon kon Oinarisama": SongData(2900379, "27-5", "Nanahira Festival", False, 3, 6, 9), + "Legend of Eastern Rabbit -SKY DEFENDER-": SongData(2900381, "26-0", "Give Up TREATMENT Vol.9", False, 4, 6, 9), + "ENERGY SYNERGY MATRIX": SongData(2900382, "26-1", "Give Up TREATMENT Vol.9", False, 6, 8, 10), + "Punai Punai Genso": SongData(2900383, "26-2", "Give Up TREATMENT Vol.9", False, 2, 7, 11), + "Better Graphic Animation": SongData(2900384, "26-3", "Give Up TREATMENT Vol.9", False, 5, 8, 11), + "Variant Cross": SongData(2900385, "26-4", "Give Up TREATMENT Vol.9", False, 4, 7, 10), + "Ultra Happy Miracle Bazoooooka!!": SongData(2900386, "26-5", "Give Up TREATMENT Vol.9", False, 7, 9, 11), + "tape/stop/night": SongData(2900388, "25-0", "MUSE RADIO FM101", True, 3, 5, 7), + "Pixel Galaxy": SongData(2900389, "25-1", "MUSE RADIO FM101", False, 2, 5, 8), + "Notice": SongData(2900390, "25-2", "MUSE RADIO FM101", False, 4, 7, 10), + "Strawberry Godzilla": SongData(2900391, "25-3", "MUSE RADIO FM101", True, 2, 5, 7), + "OKIMOCHI EXPRESSION": SongData(2900392, "25-4", "MUSE RADIO FM101", False, 4, 6, 10), + "Kimi to pool disco": SongData(2900393, "25-5", "MUSE RADIO FM101", False, 4, 6, 8), + "The Last Page": SongData(2900395, "24-0", "Happy Otaku Pack Vol.9", False, 3, 5, 7), + "IKAROS": SongData(2900396, "24-1", "Happy Otaku Pack Vol.9", False, 4, 7, 10), + "Tsukuyomi": SongData(2900397, "24-2", "Happy Otaku Pack Vol.9", False, 3, 6, 9), + "Future Stream": SongData(2900398, "24-3", "Happy Otaku Pack Vol.9", False, 4, 6, 8), + "FULi AUTO SHOOTER": SongData(2900399, "24-4", "Happy Otaku Pack Vol.9", True, 4, 7, 9), + "GOODFORTUNE": SongData(2900400, "24-5", "Happy Otaku Pack Vol.9", False, 5, 7, 9), + "The Dessert After Rain": SongData(2900402, "23-0", "Cute Is Everything Vol.5", True, 2, 4, 6), + "Confession Support Formula": SongData(2900403, "23-1", "Cute Is Everything Vol.5", False, 3, 5, 7), + "Omatsuri": SongData(2900404, "23-2", "Cute Is Everything Vol.5", False, 1, 3, 6), + "FUTUREPOP": SongData(2900405, "23-3", "Cute Is Everything Vol.5", True, 2, 5, 7), + "The Breeze": SongData(2900406, "23-4", "Cute Is Everything Vol.5", False, 1, 4, 6), + "I LOVE LETTUCE FRIED RICE!!": SongData(2900407, "23-5", "Cute Is Everything Vol.5", False, 3, 7, 9), + "The NightScape": SongData(2900409, "22-0", "Give Up TREATMENT Vol.8", False, 4, 7, 9), + "FREEDOM DiVE": SongData(2900410, "22-1", "Give Up TREATMENT Vol.8", False, 6, 8, 10), + "Phi": SongData(2900411, "22-2", "Give Up TREATMENT Vol.8", False, 5, 8, 10), + "Lueur de la nuit": SongData(2900412, "22-3", "Give Up TREATMENT Vol.8", False, 6, 8, 11), + "Creamy Sugary OVERDRIVE!!!": SongData(2900413, "22-4", "Give Up TREATMENT Vol.8", True, 4, 7, 10), + "Disorder": SongData(2900414, "22-5", "Give Up TREATMENT Vol.8", False, 5, 7, 11), + "Glimmer": SongData(2900416, "21-0", "Budget Is Burning: Nano Core", False, 2, 5, 8), + "EXIST": SongData(2900417, "21-1", "Budget Is Burning: Nano Core", False, 3, 5, 8), + "Irreplaceable": SongData(2900418, "21-2", "Budget Is Burning: Nano Core", False, 4, 6, 8), + "Moonlight Banquet": SongData(2900420, "20-0", "Happy Otaku Pack Vol.8", True, 2, 5, 8), + "Flashdance": SongData(2900421, "20-1", "Happy Otaku Pack Vol.8", False, 3, 6, 9), + "INFiNiTE ENERZY -Overdoze-": SongData(2900422, "20-2", "Happy Otaku Pack Vol.8", False, 4, 7, 9), + "One Way Street": SongData(2900423, "20-3", "Happy Otaku Pack Vol.8", False, 3, 6, 10), + "This Club is Not 4 U": SongData(2900424, "20-4", "Happy Otaku Pack Vol.8", False, 4, 7, 9), + "ULTRA MEGA HAPPY PARTY!!!": SongData(2900425, "20-5", "Happy Otaku Pack Vol.8", False, 5, 7, 10), + "INFINITY": SongData(2900427, "19-0", "Give Up TREATMENT Vol.7", True, 5, 8, 10), + "Punai Punai Senso": SongData(2900428, "19-1", "Give Up TREATMENT Vol.7", False, 2, 7, 11), + "Maxi": SongData(2900429, "19-2", "Give Up TREATMENT Vol.7", False, 5, 8, 10), + "YInMn Blue": SongData(2900430, "19-3", "Give Up TREATMENT Vol.7", False, 6, 8, 10), + "Plumage": SongData(2900431, "19-4", "Give Up TREATMENT Vol.7", False, 4, 7, 10), + "Dr.Techro": SongData(2900432, "19-5", "Give Up TREATMENT Vol.7", False, 7, 9, 11), + "SWEETSWEETSWEET": SongData(2900434, "18-0", "Cute Is Everything Vol.4", True, 2, 5, 7), + "Deep Blue and the Breaths of the Night": SongData(2900435, "18-1", "Cute Is Everything Vol.4", True, 2, 4, 6), + "Joy Connection": SongData(2900436, "18-2", "Cute Is Everything Vol.4", False, 3, 6, 8), + "Self Willed Girl Ver.B": SongData(2900437, "18-3", "Cute Is Everything Vol.4", True, 4, 6, 8), + "Just Disobedient": SongData(2900438, "18-4", "Cute Is Everything Vol.4", False, 3, 6, 8), + "Holy Sh*t Grass Snake": SongData(2900439, "18-5", "Cute Is Everything Vol.4", False, 2, 6, 9), + "Cotton Candy Wonderland": SongData(2900441, "17-0", "Happy Otaku Pack Vol.7", False, 2, 5, 8), + "Punai Punai Taiso": SongData(2900442, "17-1", "Happy Otaku Pack Vol.7", False, 2, 7, 10), + "Fly High": SongData(2900443, "17-2", "Happy Otaku Pack Vol.7", False, 3, 5, 7), + "prejudice": SongData(2900444, "17-3", "Happy Otaku Pack Vol.7", True, 4, 6, 9), + "The 89's Momentum": SongData(2900445, "17-4", "Happy Otaku Pack Vol.7", True, 5, 7, 9), + "energy night": SongData(2900446, "17-5", "Happy Otaku Pack Vol.7", True, 5, 7, 10), + "Future Dive": SongData(2900448, "16-0", "Give Up TREATMENT Vol.6", True, 4, 6, 9), + "Re End of a Dream": SongData(2900449, "16-1", "Give Up TREATMENT Vol.6", False, 5, 8, 11), + "Etude -Storm-": SongData(2900450, "16-2", "Give Up TREATMENT Vol.6", True, 6, 8, 10), + "Unlimited Katharsis": SongData(2900451, "16-3", "Give Up TREATMENT Vol.6", False, 4, 6, 10), + "Magic Knight Girl": SongData(2900452, "16-4", "Give Up TREATMENT Vol.6", False, 4, 7, 9), + "Eeliaas": SongData(2900453, "16-5", "Give Up TREATMENT Vol.6", False, 6, 9, 11), + "Magic Spell": SongData(2900455, "15-0", "Cute Is Everything Vol.3", True, 2, 5, 7), + "Colorful Star, Colored Drawing, Travel Poem": SongData(2900456, "15-1", "Cute Is Everything Vol.3", False, 3, 4, 6), + "Satell Knight": SongData(2900457, "15-2", "Cute Is Everything Vol.3", False, 3, 6, 8), + "Black River Feat.Mes": SongData(2900458, "15-3", "Cute Is Everything Vol.3", True, 1, 4, 6), + "I am sorry": SongData(2900459, "15-4", "Cute Is Everything Vol.3", False, 2, 5, 8), + "Ueta Tori Tachi": SongData(2900460, "15-5", "Cute Is Everything Vol.3", False, 3, 6, 8), + "Elysion's Old Mans": SongData(2900462, "14-0", "Happy Otaku Pack Vol.6", False, 3, 5, 8), + "AXION": SongData(2900463, "14-1", "Happy Otaku Pack Vol.6", False, 4, 5, 8), + "Amnesia": SongData(2900464, "14-2", "Happy Otaku Pack Vol.6", True, 3, 6, 9), + "Onsen Dai Sakusen": SongData(2900465, "14-3", "Happy Otaku Pack Vol.6", True, 4, 6, 8), + "Gleam stone": SongData(2900466, "14-4", "Happy Otaku Pack Vol.6", False, 4, 7, 9), + "GOODWORLD": SongData(2900467, "14-5", "Happy Otaku Pack Vol.6", False, 4, 7, 10), + "Instant Soluble Neon": SongData(2900469, "13-0", "Cute Is Everything Vol.2", True, 2, 4, 7), + "Retrospective Poem on the Planet": SongData(2900470, "13-1", "Cute Is Everything Vol.2", False, 3, 5, 7), + "I'm Gonna Buy! Buy! Buy!": SongData(2900471, "13-2", "Cute Is Everything Vol.2", True, 4, 6, 8), + "Dating Manifesto": SongData(2900472, "13-3", "Cute Is Everything Vol.2", True, 2, 4, 6), + "First Snow": SongData(2900473, "13-4", "Cute Is Everything Vol.2", True, 2, 3, 6), + "Xin Shang Huahai": SongData(2900474, "13-5", "Cute Is Everything Vol.2", False, 3, 6, 8), + "Gaikan Chrysalis": SongData(2900476, "12-0", "Give Up TREATMENT Vol.5", False, 4, 6, 8), + "Sterelogue": SongData(2900477, "12-1", "Give Up TREATMENT Vol.5", True, 5, 7, 10), + "Cheshire's Dance": SongData(2900478, "12-2", "Give Up TREATMENT Vol.5", True, 4, 7, 10), + "Skrik": SongData(2900479, "12-3", "Give Up TREATMENT Vol.5", True, 5, 7, 11), + "Soda Pop Canva5!": SongData(2900480, "12-4", "Give Up TREATMENT Vol.5", False, 5, 8, 10), + "RUBY LINTe": SongData(2900481, "12-5", "Give Up TREATMENT Vol.5", False, 5, 8, 11), + "Brave My Heart": SongData(2900483, "11-0", "Happy Otaku Pack Vol.5", True, 3, 5, 7), + "Sakura Fubuki": SongData(2900484, "11-1", "Happy Otaku Pack Vol.5", False, 4, 7, 10), + "8bit Adventurer": SongData(2900485, "11-2", "Happy Otaku Pack Vol.5", False, 6, 8, 10), + "Suffering of screw": SongData(2900486, "11-3", "Happy Otaku Pack Vol.5", False, 3, 5, 8), + "tiny lady": SongData(2900487, "11-4", "Happy Otaku Pack Vol.5", True, 4, 6, 9), + "Power Attack": SongData(2900488, "11-5", "Happy Otaku Pack Vol.5", False, 5, 7, 10), + "Destr0yer": SongData(2900490, "10-0", "Give Up TREATMENT Vol.4", False, 4, 7, 9), + "Noel": SongData(2900491, "10-1", "Give Up TREATMENT Vol.4", False, 5, 8, 10), + "Kyoukiranbu": SongData(2900492, "10-2", "Give Up TREATMENT Vol.4", False, 7, 9, 11), + "Two Phace": SongData(2900493, "10-3", "Give Up TREATMENT Vol.4", True, 4, 7, 10), + "Fly Again": SongData(2900494, "10-4", "Give Up TREATMENT Vol.4", False, 5, 7, 10), + "ouroVoros": SongData(2900495, "10-5", "Give Up TREATMENT Vol.4", False, 7, 9, 11), + "Leave It Alone": SongData(2900497, "9-0", "Happy Otaku Pack Vol.4", True, 2, 5, 8), + "Tsubasa no Oreta Tenshitachi no Requiem": SongData(2900498, "9-1", "Happy Otaku Pack Vol.4", False, 4, 7, 9), + "Chronomia": SongData(2900499, "9-2", "Happy Otaku Pack Vol.4", False, 5, 7, 10), + "Dandelion's Daydream": SongData(2900500, "9-3", "Happy Otaku Pack Vol.4", True, 5, 7, 8), + "Lorikeet Flat design": SongData(2900501, "9-4", "Happy Otaku Pack Vol.4", True, 5, 7, 10), + "GOODRAGE": SongData(2900502, "9-5", "Happy Otaku Pack Vol.4", False, 6, 9, 11), + "Altale": SongData(2900504, "8-0", "Give Up TREATMENT Vol.3", False, 3, 5, 7), + "Brain Power": SongData(2900505, "8-1", "Give Up TREATMENT Vol.3", False, 4, 7, 10), + "Berry Go!!": SongData(2900506, "8-2", "Give Up TREATMENT Vol.3", False, 3, 6, 9), + "Sweet* Witch* Girl*": SongData(2900507, "8-3", "Give Up TREATMENT Vol.3", False, 6, 8, 10), + "trippers feeling!": SongData(2900508, "8-4", "Give Up TREATMENT Vol.3", True, 5, 7, 9), + "Lilith ambivalence lovers": SongData(2900509, "8-5", "Give Up TREATMENT Vol.3", False, 5, 8, 10), + "Brave My Soul": SongData(2900511, "7-0", "Give Up TREATMENT Vol.2", False, 4, 6, 8), + "Halcyon": SongData(2900512, "7-1", "Give Up TREATMENT Vol.2", False, 4, 7, 10), + "Crimson Nightingale": SongData(2900513, "7-2", "Give Up TREATMENT Vol.2", True, 4, 7, 10), + "Invader": SongData(2900514, "7-3", "Give Up TREATMENT Vol.2", True, 3, 7, 11), + "Lyrith": SongData(2900515, "7-4", "Give Up TREATMENT Vol.2", False, 5, 7, 10), + "GOODBOUNCE": SongData(2900516, "7-5", "Give Up TREATMENT Vol.2", False, 4, 6, 9), + "Out of Sense": SongData(2900518, "6-0", "Budget Is Burning Vol.1", False, 3, 5, 8), + "My Life Is For You": SongData(2900519, "6-1", "Budget Is Burning Vol.1", False, 2, 4, 7), + "Etude -Sunset-": SongData(2900520, "6-2", "Budget Is Burning Vol.1", True, 5, 7, 9), + "Goodbye Boss": SongData(2900521, "6-3", "Budget Is Burning Vol.1", False, 4, 6, 8), + "Stargazer": SongData(2900522, "6-4", "Budget Is Burning Vol.1", True, 2, 5, 8), + "Lys Tourbillon": SongData(2900523, "6-5", "Budget Is Burning Vol.1", True, 4, 6, 8), + "Thirty Million Persona": SongData(2900525, "5-0", "Happy Otaku Pack Vol.3", False, 2, 4, 6), + "conflict": SongData(2900526, "5-1", "Happy Otaku Pack Vol.3", False, 2, 6, 9), + "Enka Dance Music": SongData(2900527, "5-2", "Happy Otaku Pack Vol.3", False, 3, 5, 7), + "XING": SongData(2900528, "5-3", "Happy Otaku Pack Vol.3", True, 4, 6, 8), + "Amakakeru Soukyuu no Serenade": SongData(2900529, "5-4", "Happy Otaku Pack Vol.3", False, 3, 6, 9), + "Gift box": SongData(2900530, "5-5", "Happy Otaku Pack Vol.3", False, 5, 7, 10), + "MUSEDASH!!!!": SongData(2900532, "4-0", "Happy Otaku Pack Vol.2", False, 2, 6, 9), + "Imprinting": SongData(2900533, "4-1", "Happy Otaku Pack Vol.2", False, 3, 6, 9), + "Skyward": SongData(2900534, "4-2", "Happy Otaku Pack Vol.2", True, 4, 7, 10), + "La nuit de vif": SongData(2900535, "4-3", "Happy Otaku Pack Vol.2", True, 2, 5, 8), + "Bit-alize": SongData(2900536, "4-4", "Happy Otaku Pack Vol.2", False, 3, 6, 8), + "GOODTEK": SongData(2900537, "4-5", "Happy Otaku Pack Vol.2", False, 4, 6, 9), + "Maharajah": SongData(2900539, "3-0", "Happy Otaku Pack Vol.1", False, 1, 3, 6), + "keep on running": SongData(2900540, "3-1", "Happy Otaku Pack Vol.1", False, 5, 7, 9), + "Kafig": SongData(2900541, "3-2", "Happy Otaku Pack Vol.1", True, 4, 6, 8), + "-+": SongData(2900542, "3-3", "Happy Otaku Pack Vol.1", True, 4, 6, 8), + "Tenri Kaku Jou": SongData(2900543, "3-4", "Happy Otaku Pack Vol.1", True, 3, 6, 9), + "Adjudicatorz-DanZai-": SongData(2900544, "3-5", "Happy Otaku Pack Vol.1", False, 3, 7, 10), + "Oriens": SongData(2900546, "2-0", "Give Up TREATMENT Vol.1", True, 3, 7, 9), + "PUPA": SongData(2900547, "2-1", "Give Up TREATMENT Vol.1", False, 6, 8, 11), + "Luna Express 2032": SongData(2900548, "2-2", "Give Up TREATMENT Vol.1", False, 4, 6, 8), + "Ukiyoe Yokochou": SongData(2900549, "2-3", "Give Up TREATMENT Vol.1", False, 6, 7, 9), + "Alice in Misanthrope": SongData(2900550, "2-4", "Give Up TREATMENT Vol.1", False, 5, 7, 10), + "GOODMEN": SongData(2900551, "2-5", "Give Up TREATMENT Vol.1", False, 5, 7, 10), + "Sunshine and Rainbow after August Rain": SongData(2900553, "1-0", "Cute Is Everything Vol.1", False, 2, 5, 8), + "Magical Number": SongData(2900554, "1-1", "Cute Is Everything Vol.1", False, 2, 5, 8), + "Dreaming Girl": SongData(2900555, "1-2", "Cute Is Everything Vol.1", False, 2, 5, 6), + "Daruma-san Fell Over": SongData(2900556, "1-3", "Cute Is Everything Vol.1", False, 3, 4, 6), + "Different": SongData(2900557, "1-4", "Cute Is Everything Vol.1", False, 1, 3, 6), + "The Future of the Phantom": SongData(2900558, "1-5", "Cute Is Everything Vol.1", False, 1, 3, 5), + "Doki Doki Jump!": SongData(2900560, "63-0", "MUSE RADIO FM104", True, 3, 5, 7), + "Centennial Streamers High": SongData(2900561, "63-1", "MUSE RADIO FM104", False, 4, 7, 9), + "Love Patrol": SongData(2900562, "63-2", "MUSE RADIO FM104", True, 3, 5, 7), + "Mahorova": SongData(2900563, "63-3", "MUSE RADIO FM104", True, 3, 5, 8), + "Yoru no machi": SongData(2900564, "63-4", "MUSE RADIO FM104", True, 1, 4, 7), + "INTERNET YAMERO": SongData(2900565, "63-5", "MUSE RADIO FM104", True, 6, 8, 10), + "Abracadabra": SongData(2900566, "43-24", "MD Plus Project", False, 6, 8, 10), + "Squalldecimator feat. EZ-Ven": SongData(2900567, "43-25", "MD Plus Project", True, 5, 7, 9), + "Amateras Rhythm": SongData(2900568, "43-26", "MD Plus Project", True, 6, 8, 11), + "Record one's Dream": SongData(2900569, "43-27", "MD Plus Project", False, 4, 7, 10), + "Lunatic": SongData(2900570, "43-28", "MD Plus Project", True, 5, 8, 10), + "Jiumeng": SongData(2900571, "43-29", "MD Plus Project", True, 3, 6, 8), + "The Day We Become Family": SongData(2900572, "43-30", "MD Plus Project", True, 3, 5, 8), + "Sutori ma FIRE!?!?": SongData(2900574, "64-0", "COSMIC RADIO PEROLIST", True, 3, 5, 8), + "Tanuki Step": SongData(2900575, "64-1", "COSMIC RADIO PEROLIST", True, 5, 7, 10), + "Space Stationery": SongData(2900576, "64-2", "COSMIC RADIO PEROLIST", True, 5, 7, 10), + "Songs Are Judged 90% by Chorus feat. Mameko": SongData(2900577, "64-3", "COSMIC RADIO PEROLIST", True, 6, 8, 10), + "Kawai Splendid Space Thief": SongData(2900578, "64-4", "COSMIC RADIO PEROLIST", False, 6, 8, 10), + "Night City Runway": SongData(2900579, "64-5", "COSMIC RADIO PEROLIST", True, 4, 6, 8), + "Chaos Shotgun feat. ChumuNote": SongData(2900580, "64-6", "COSMIC RADIO PEROLIST", True, 6, 8, 10), + "mew mew magical summer": SongData(2900581, "64-7", "COSMIC RADIO PEROLIST", False, 5, 8, 10), + "BrainDance": SongData(2900583, "65-0", "NeonAbyss", True, 3, 6, 9), + "My Focus!": SongData(2900584, "65-1", "NeonAbyss", True, 5, 7, 10), + "ABABABA BURST": SongData(2900585, "65-2", "NeonAbyss", True, 5, 7, 9), + "ULTRA HIGHER": SongData(2900586, "65-3", "NeonAbyss", True, 4, 7, 10), + "Silver Bullet": SongData(2900587, "43-31", "MD Plus Project", True, 5, 7, 10), + "Random": SongData(2900588, "43-32", "MD Plus Project", True, 4, 7, 9), + "OTOGE-BOSS-KYOKU-CHAN": SongData(2900589, "43-33", "MD Plus Project", False, 6, 8, 10), + "Crow Rabbit": SongData(2900590, "43-34", "MD Plus Project", True, 7, 9, 11), + "SyZyGy": SongData(2900591, "43-35", "MD Plus Project", True, 6, 8, 10), + "Mermaid Radio": SongData(2900592, "43-36", "MD Plus Project", True, 3, 5, 7), + "Helixir": SongData(2900593, "43-37", "MD Plus Project", False, 6, 8, 10), + "Highway Cruisin'": SongData(2900594, "43-38", "MD Plus Project", False, 3, 5, 8), + "JACK PT BOSS": SongData(2900595, "43-39", "MD Plus Project", False, 6, 8, 10), + "Time Capsule": SongData(2900596, "43-40", "MD Plus Project", False, 7, 9, 11), + "39 Music!": SongData(2900598, "66-0", "Miku in Museland", False, 3, 5, 8), + "Hand in Hand": SongData(2900599, "66-1", "Miku in Museland", False, 1, 3, 6), + "Cynical Night Plan": SongData(2900600, "66-2", "Miku in Museland", False, 4, 6, 8), + "God-ish": SongData(2900601, "66-3", "Miku in Museland", False, 4, 7, 10), + "Darling Dance": SongData(2900602, "66-4", "Miku in Museland", False, 4, 7, 9), + "Hatsune Creation Myth": SongData(2900603, "66-5", "Miku in Museland", False, 6, 8, 10), + "The Vampire": SongData(2900604, "66-6", "Miku in Museland", False, 4, 6, 9), + "Future Eve": SongData(2900605, "66-7", "Miku in Museland", False, 4, 8, 11), + "Unknown Mother Goose": SongData(2900606, "66-8", "Miku in Museland", False, 4, 8, 10), + "Shun-ran": SongData(2900607, "66-9", "Miku in Museland", False, 4, 7, 9), + "NICE TYPE feat. monii": SongData(2900608, "43-41", "MD Plus Project", True, 3, 6, 8), + "Rainy Angel": SongData(2900610, "67-0", "Happy Otaku Pack Vol.18", True, 4, 6, 9), + "Gullinkambi": SongData(2900611, "67-1", "Happy Otaku Pack Vol.18", True, 4, 7, 10), + "RakiRaki Rebuilders!!!": SongData(2900612, "67-2", "Happy Otaku Pack Vol.18", True, 5, 7, 10), + "Laniakea": SongData(2900613, "67-3", "Happy Otaku Pack Vol.18", False, 5, 8, 10), + "OTTAMA GAZER": SongData(2900614, "67-4", "Happy Otaku Pack Vol.18", True, 5, 8, 10), + "Sleep Tight feat.Macoto": SongData(2900615, "67-5", "Happy Otaku Pack Vol.18", True, 3, 5, 8), + "New York Back Raise": SongData(2900617, "68-0", "Gambler's Tricks", True, 6, 8, 10), + "slic.hertz": SongData(2900618, "68-1", "Gambler's Tricks", True, 5, 7, 9), + "Fuzzy-Navel": SongData(2900619, "68-2", "Gambler's Tricks", True, 6, 8, 10), + "Swing Edge": SongData(2900620, "68-3", "Gambler's Tricks", True, 4, 8, 10), + "Twisted Escape": SongData(2900621, "68-4", "Gambler's Tricks", True, 5, 8, 10), + "Swing Sweet Twee Dance": SongData(2900622, "68-5", "Gambler's Tricks", False, 4, 7, 10), + "Sanyousei SAY YA!!!": SongData(2900623, "43-42", "MD Plus Project", False, 4, 6, 8), + "YUKEMURI TAMAONSEN II": SongData(2900624, "43-43", "MD Plus Project", False, 3, 6, 9), + "Samayoi no mei Amatsu": SongData(2900626, "69-0", "Touhou Mugakudan -III-", False, 4, 6, 9), + "INTERNET SURVIVOR": SongData(2900627, "69-1", "Touhou Mugakudan -III-", False, 5, 8, 10), + "Shuki*RaiRai": SongData(2900628, "69-2", "Touhou Mugakudan -III-", False, 5, 7, 9), + "HELLOHELL": SongData(2900629, "69-3", "Touhou Mugakudan -III-", False, 4, 7, 10), + "Calamity Fortune": SongData(2900630, "69-4", "Touhou Mugakudan -III-", True, 6, 8, 10), + "Tsurupettan": SongData(2900631, "69-5", "Touhou Mugakudan -III-", True, 2, 5, 8), + "Twilight Poems": SongData(2900632, "43-44", "MD Plus Project", True, 3, 6, 8), + "All My Friends feat. RANASOL": SongData(2900633, "43-45", "MD Plus Project", True, 4, 7, 9), + "Heartache": SongData(2900634, "43-46", "MD Plus Project", True, 5, 7, 10), + "Blue Lemonade": SongData(2900635, "43-47", "MD Plus Project", True, 3, 6, 8), + "Haunted Dance": SongData(2900636, "43-48", "MD Plus Project", False, 6, 9, 11), + "Hey Vincent.": SongData(2900637, "43-49", "MD Plus Project", True, 6, 8, 10), + "Meteor feat. TEA": SongData(2900638, "43-50", "MD Plus Project", True, 3, 6, 9), + "Narcissism Angel": SongData(2900639, "43-51", "MD Plus Project", True, 1, 3, 6), + "AlterLuna": SongData(2900640, "43-52", "MD Plus Project", True, 6, 8, 11), + "Niki Tousen": SongData(2900641, "43-53", "MD Plus Project", True, 6, 8, 10), + "Rettou Joutou": SongData(2900643, "70-0", "Rin Len's Mirrorland", False, 4, 7, 9), + "Telecaster B-Boy": SongData(2900644, "70-1", "Rin Len's Mirrorland", False, 5, 7, 10), + "Iya Iya Iya": SongData(2900645, "70-2", "Rin Len's Mirrorland", False, 2, 4, 7), + "Nee Nee Nee": SongData(2900646, "70-3", "Rin Len's Mirrorland", False, 4, 6, 8), + "Chaotic Love Revolution": SongData(2900647, "70-4", "Rin Len's Mirrorland", False, 4, 6, 8), + "Dance of the Corpses": SongData(2900648, "70-5", "Rin Len's Mirrorland", False, 2, 5, 8), + "Bitter Choco Decoration": SongData(2900649, "70-6", "Rin Len's Mirrorland", False, 3, 6, 9), + "Dance Robot Dance": SongData(2900650, "70-7", "Rin Len's Mirrorland", False, 4, 7, 10), + "Sweet Devil": SongData(2900651, "70-8", "Rin Len's Mirrorland", False, 5, 7, 9), + "Someday'z Coming": SongData(2900652, "70-9", "Rin Len's Mirrorland", False, 5, 7, 9), + "Yume Ou Mono Yo Secret": SongData(2900653, "0-53", "Default Music", True, 6, 8, 10), + "Yume Ou Mono Yo": SongData(2900654, "0-54", "Default Music", True, 1, 4, None), + "Sweet Dream VIVINOS": SongData(2900656, "71-0", "Valentine Stage", False, 1, 4, 7), + "Ruler Of My Heart VIVINOS": SongData(2900657, "71-1", "Valentine Stage", False, 2, 4, 6), + "Reality Show": SongData(2900658, "71-2", "Valentine Stage", False, 5, 7, 10), + "SIG feat.Tobokegao": SongData(2900659, "71-3", "Valentine Stage", True, 3, 6, 8), + "Rose Love": SongData(2900660, "71-4", "Valentine Stage", True, 2, 4, 7), + "Euphoria": SongData(2900661, "71-5", "Valentine Stage", True, 1, 3, 6), + "P E R O P E R O Brother Dance": SongData(2900663, "72-0", "Legends of Muse Warriors", True, None, 7, None), + "PA PPA PANIC": SongData(2900664, "72-1", "Legends of Muse Warriors", False, 4, 8, 10), + "How To Make Music Game Song!": SongData(2900665, "72-2", "Legends of Muse Warriors", True, 6, 8, 10), + "Re Re": SongData(2900666, "72-3", "Legends of Muse Warriors", True, 7, 9, 11), + "Marmalade Twins": SongData(2900667, "72-4", "Legends of Muse Warriors", True, 5, 8, 10), + "DOMINATOR": SongData(2900668, "72-5", "Legends of Muse Warriors", True, 7, 9, 11), + "Teshikani TESHiKANi": SongData(2900669, "72-6", "Legends of Muse Warriors", True, 5, 7, 9), + "Urban Magic": SongData(2900671, "73-0", "Happy Otaku Pack Vol.19", True, 3, 5, 7), + "Maid's Prank": SongData(2900672, "73-1", "Happy Otaku Pack Vol.19", True, 5, 7, 10), + "Dance Dance Good Night Dance": SongData(2900673, "73-2", "Happy Otaku Pack Vol.19", True, 2, 4, 7), + "Ops Limone": SongData(2900674, "73-3", "Happy Otaku Pack Vol.19", True, 5, 8, 11), + "NOVA": SongData(2900675, "73-4", "Happy Otaku Pack Vol.19", True, 6, 8, 10), + "Heaven's Gradius": SongData(2900676, "73-5", "Happy Otaku Pack Vol.19", True, 6, 8, 10), + "Ray Tuning": SongData(2900678, "74-0", "CHUNITHM COURSE MUSE", True, 6, 8, 10), + "World Vanquisher": SongData(2900679, "74-1", "CHUNITHM COURSE MUSE", True, 6, 8, 10), + "Tsukuyomi Ni Naru Replaced": SongData(2900680, "74-2", "CHUNITHM COURSE MUSE", True, 5, 7, 9), + "The wheel to the right": SongData(2900681, "74-3", "CHUNITHM COURSE MUSE", True, 5, 7, 9), + "Climax": SongData(2900682, "74-4", "CHUNITHM COURSE MUSE", True, 4, 8, 11), + "Spider's Thread": SongData(2900683, "74-5", "CHUNITHM COURSE MUSE", True, 5, 8, 10), + "HIT ME UP": SongData(2900684, "43-54", "MD Plus Project", True, 4, 6, 8), + "Test Me feat. Uyeon": SongData(2900685, "43-55", "MD Plus Project", True, 3, 5, 9), + "Assault TAXI": SongData(2900686, "43-56", "MD Plus Project", True, 4, 7, 10), + "No": SongData(2900687, "43-57", "MD Plus Project", False, 4, 6, 9), + "Pop it": SongData(2900688, "43-58", "MD Plus Project", True, 1, 3, 6), + "HEARTBEAT! KyunKyun!": SongData(2900689, "43-59", "MD Plus Project", True, 4, 6, 9), + "SUPERHERO": SongData(2900691, "75-0", "Novice Rider Pack", False, 2, 4, 7), + "Highway_Summer": SongData(2900692, "75-1", "Novice Rider Pack", True, 2, 4, 6), + "Mx. Black Box": SongData(2900693, "75-2", "Novice Rider Pack", True, 5, 7, 9), + "Sweet Encounter": SongData(2900694, "75-3", "Novice Rider Pack", True, 2, 4, 7), + "Echo over you... Secret": SongData(2900695, "0-55", "Default Music", False, 6, 8, 10), + "Echo over you...": SongData(2900696, "0-56", "Default Music", False, 1, 4, None), + "Tsukuyomi Ni Naru": SongData(2900697, "74-6", "CHUNITHM COURSE MUSE", True, 5, 8, 10), + "disco light": SongData(2900699, "76-0", "MUSE RADIO FM105", True, 5, 7, 9), + "room light feat.chancylemon": SongData(2900700, "76-1", "MUSE RADIO FM105", True, 3, 5, 7), + "Invisible": SongData(2900701, "76-2", "MUSE RADIO FM105", True, 3, 5, 8), + "Christmas Season-LLABB": SongData(2900702, "76-3", "MUSE RADIO FM105", True, 1, 4, 7), + "Hyouryu": SongData(2900704, "77-0", "Let's Rhythm Jam!", False, 6, 8, 10), + "The Whole Rest": SongData(2900705, "77-1", "Let's Rhythm Jam!", False, 5, 8, 10), + "Hydra": SongData(2900706, "77-2", "Let's Rhythm Jam!", False, 4, 7, 11), + "Pastel Lines": SongData(2900707, "77-3", "Let's Rhythm Jam!", False, 3, 6, 9), + "LINK x LIN#S": SongData(2900708, "77-4", "Let's Rhythm Jam!", False, 3, 6, 9), + "Arcade ViruZ": SongData(2900709, "77-5", "Let's Rhythm Jam!", False, 6, 8, 11), + "Eve Avenir": SongData(2900711, "78-0", "Endless Pirouette", True, 6, 8, 10), + "Silverstring": SongData(2900712, "78-1", "Endless Pirouette", True, 5, 7, 10), + "Melusia": SongData(2900713, "78-2", "Endless Pirouette", False, 5, 7, 10), + "Devil's Castle": SongData(2900714, "78-3", "Endless Pirouette", True, 4, 7, 10), + "Abatement": SongData(2900715, "78-4", "Endless Pirouette", True, 6, 8, 10), + "Azalea": SongData(2900716, "78-5", "Endless Pirouette", False, 4, 8, 10), + "Brightly World": SongData(2900717, "78-6", "Endless Pirouette", True, 6, 8, 10), + "We'll meet in every world ***": SongData(2900718, "78-7", "Endless Pirouette", True, 7, 9, 11), + "Collapsar": SongData(2900719, "78-8", "Endless Pirouette", True, 7, 9, 10), + "Parousia": SongData(2900720, "78-9", "Endless Pirouette", False, 6, 8, 10), + "Gunners in the Rain": SongData(2900722, "79-0", "Ensemble Arcanum", False, 5, 8, 10), + "Halzion": SongData(2900723, "79-1", "Ensemble Arcanum", False, 2, 5, 8), + "SHOWTIME!!": SongData(2900724, "79-2", "Ensemble Arcanum", False, 6, 8, 10), + "Achromic Riddle": SongData(2900725, "79-3", "Ensemble Arcanum", False, 6, 8, 10), + "karanosu": SongData(2900726, "79-4", "Ensemble Arcanum", False, 3, 6, 8), + "Necromantic": SongData(2900727, "43-60", "MD Plus Project", False, 6, 8, 10), + "Saishuu kichiku imouto Flandre-S": SongData(2900729, "80-0", "Touhou Mugakudan -IV-", False, 6, 8, 10), + "Kachoufuugetsu": SongData(2900730, "80-1", "Touhou Mugakudan -IV-", False, 2, 6, 8), + "Maid heart is a puppet": SongData(2900731, "80-2", "Touhou Mugakudan -IV-", False, 5, 7, 9), + "Trance dance anarchy": SongData(2900732, "80-3", "Touhou Mugakudan -IV-", False, 4, 7, 10), + "fairy stage": SongData(2900733, "80-4", "Touhou Mugakudan -IV-", False, 4, 6, 9), + "Scarlet Police on Ghetto Patrol": SongData(2900734, "80-5", "Touhou Mugakudan -IV-", False, 5, 7, 10), + "Unwelcome School": SongData(2900735, "81-0", "MD-level Tactical Training Blu-ray", False, 6, 8, 10), + "Usagi Flap": SongData(2900736, "81-1", "MD-level Tactical Training Blu-ray", False, 3, 6, 8), + "RE Aoharu": SongData(2900737, "81-2", "MD-level Tactical Training Blu-ray", False, 3, 5, 8), + "Operation*DOTABATA!": SongData(2900738, "81-3", "MD-level Tactical Training Blu-ray", False, 5, 7, 10), +} diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt deleted file mode 100644 index d913449ed5..0000000000 --- a/worlds/musedash/MuseDashData.txt +++ /dev/null @@ -1,597 +0,0 @@ -Magical Wonderland|0-48|Default Music|True|1|3|0| -Iyaiya|0-0|Default Music|True|1|4|0| -Wonderful Pain|0-2|Default Music|False|1|3|0| -Breaking Dawn|0-3|Default Music|True|2|4|0| -One-Way Subway|0-4|Default Music|True|1|4|0| -Frost Land|0-1|Default Music|False|1|3|6| -Heart-Pounding Flight|0-5|Default Music|True|2|5|0| -Pancake is Love|0-29|Default Music|True|2|4|7| -Shiguang Tuya|0-6|Default Music|True|2|5|0| -Evolution|0-37|Default Music|False|2|4|7| -Dolphin and Broadcast|0-7|Default Music|True|2|5|0| -Yuki no Shizuku Ame no Oto|0-8|Default Music|True|2|4|6| -Best One feat.tooko|0-43|Default Music|False|3|5|0| -Candy-coloured Love Theory|0-31|Default Music|False|2|4|6| -Night Wander|0-38|Default Music|False|3|5|7| -Dohna Dohna no Uta|0-46|Default Music|False|2|4|6| -Spring Carnival|0-9|Default Music|False|2|4|7| -DISCO NIGHT|0-30|Default Music|True|2|4|7| -Koi no Moonlight|0-49|Default Music|False|2|5|8| -Lian Ai Audio Navigation|0-10|Default Music|False|3|5|7| -Lights of Muse|0-11|Default Music|True|4|6|8|10 -midstream jam|0-12|Default Music|False|2|5|8| -Nihao|0-40|Default Music|False|3|5|7| -Confession|0-13|Default Music|False|3|5|8| -Galaxy Striker|0-32|Default Music|False|4|7|9| -Departure Road|0-14|Default Music|True|2|5|8| -Bass Telekinesis|0-15|Default Music|False|2|5|8| -Cage of Almeria|0-16|Default Music|True|3|5|7| -Ira|0-17|Default Music|True|4|6|8| -Blackest Luxury Car|0-18|Default Music|True|3|6|8| -Medicine of Sing|0-19|Default Music|False|3|6|8| -irregulyze|0-20|Default Music|True|3|6|8| -I don't care about Christmas though|0-47|Default Music|False|4|6|8| -Imaginary World|0-21|Default Music|True|4|6|8|10 -Dysthymia|0-22|Default Music|True|4|7|9| -From the New World|0-42|Default Music|False|2|5|7| -NISEGAO|0-33|Default Music|True|4|7|9| -Say! Fanfare!|0-44|Default Music|False|4|6|9| -Star Driver|0-34|Default Music|True|5|7|9| -Formation|0-23|Default Music|True|4|6|9| -Shinsou Masui|0-24|Default Music|True|4|6|10| -Mezame Eurythmics|0-50|Default Music|False|4|6|9| -Shenri Kuaira -repeat-|0-51|Default Music|False|5|7|9| -Latitude|0-25|Default Music|True|3|6|9| -Aqua Stars|0-39|Default Music|False|5|7|10| -Funkotsu Saishin Casino|0-26|Default Music|False|5|7|10| -Clock Room & Spiritual World|0-27|Default Music|True|4|6|9| -INTERNET OVERDOSE|0-52|Default Music|False|3|6|9| -Tu Hua|0-35|Default Music|True|4|7|9| -Mujinku-Vacuum|0-28|Default Music|False|5|7|11| -MilK|0-36|Default Music|False|5|7|9| -umpopoff|0-41|Default Music|False|0|?|0| -Mopemope|0-45|Default Music|False|4|7|9|11 -The Happycore Idol|43-0|MD Plus Project|True|2|5|7| -Amatsumikaboshi|43-1|MD Plus Project|True|4|6|8|10 -ARIGA THESIS|43-2|MD Plus Project|True|3|6|10| -Night of Nights|43-3|MD Plus Project|False|4|7|10| -#Psychedelic_Meguro_River|43-4|MD Plus Project|False|3|6|8| -can you feel it|43-5|MD Plus Project|False|4|6|8|9 -Midnight O'clock|43-6|MD Plus Project|True|3|6|8| -Rin|43-7|MD Plus Project|True|5|7|10| -Smile-mileS|43-8|MD Plus Project|False|6|8|10| -Believing and Being|43-9|MD Plus Project|True|4|6|9| -Catalyst|43-10|MD Plus Project|False|5|7|9| -don't!stop!eroero!|43-11|MD Plus Project|True|5|7|9| -pa pi pu pi pu pi pa|43-12|MD Plus Project|False|6|8|10| -Sand Maze|43-13|MD Plus Project|True|6|8|10|11 -Diffraction|43-14|MD Plus Project|True|5|8|10| -AKUMU|43-15|MD Plus Project|False|4|6|8| -Queen Aluett|43-16|MD Plus Project|True|7|9|11| -DROPS|43-17|MD Plus Project|False|2|5|8| -Frightfully-insane Flan-chan's frightful song|43-18|MD Plus Project|False|5|7|10| -snooze|43-19|MD Plus Project|False|5|7|10| -Kuishinbo Hacker feat.Kuishinbo Akachan|43-20|MD Plus Project|True|5|7|9| -Inu no outa|43-21|MD Plus Project|True|3|5|7| -Prism Fountain|43-22|MD Plus Project|True|7|9|11| -Gospel|43-23|MD Plus Project|False|4|6|9| -East Ai Li Lovely|62-0|Happy Otaku Pack Vol.17|False|2|4|7| -Mori Umi no Fune|62-1|Happy Otaku Pack Vol.17|True|5|7|9| -Ooi|62-2|Happy Otaku Pack Vol.17|True|5|7|10| -Numatta!!|62-3|Happy Otaku Pack Vol.17|True|5|7|9| -SATELLITE|62-4|Happy Otaku Pack Vol.17|False|5|7|9|10 -Fantasia Sonata Colorful feat. V!C|62-5|Happy Otaku Pack Vol.17|True|6|8|11| -MuseDash ka nanika hi|61-0|Ola Dash|True|?|?|¿| -Aleph-0|61-1|Ola Dash|True|7|9|11| -Buttoba Supernova|61-2|Ola Dash|False|5|7|10|11 -Rush-Hour|61-3|Ola Dash|False|IG|Jh|a2|Eh -3rd Avenue|61-4|Ola Dash|False|3|5|〇| -WORLDINVADER|61-5|Ola Dash|True|5|8|10|11 -N3V3R G3T OV3R|60-0|maimai DX Limited-time Suite|True|4|7|10| -Oshama Scramble!|60-1|maimai DX Limited-time Suite|True|5|7|10| -Valsqotch|60-2|maimai DX Limited-time Suite|True|5|9|11| -Paranormal My Mind|60-3|maimai DX Limited-time Suite|True|5|7|9| -Flower, snow and Drum'n'bass.|60-4|maimai DX Limited-time Suite|True|5|8|10|? -Amenohoakari|60-5|maimai DX Limited-time Suite|True|6|8|10| -Boiling Blood|59-0|MSR Anthology|True|5|8|10| -ManiFesto|59-1|MSR Anthology|True|4|6|9| -Operation Blade|59-2|MSR Anthology|True|3|5|7| -Radiant|59-3|MSR Anthology|True|3|5|8| -Renegade|59-4|MSR Anthology|True|3|5|8| -Speed of Light|59-5|MSR Anthology|False|1|4|7| -Dossoles Holiday|59-6|MSR Anthology|True|5|7|9| -Autumn Moods|59-7|MSR Anthology|True|3|5|7| -People People|58-0|Nanahira Paradise|True|5|7|9|11 -Endless Error Loop|58-1|Nanahira Paradise|True|4|7|9| -Forbidden Pizza!|58-2|Nanahira Paradise|True|5|7|9| -Don't Make the Vocalist do Anything Insane|58-3|Nanahira Paradise|True|5|8|9| -Tokimeki*Meteostrike|57-0|Happy Otaku Pack Vol.16|True|3|6|8| -Down Low|57-1|Happy Otaku Pack Vol.16|True|4|6|8| -LOUDER MACHINE|57-2|Happy Otaku Pack Vol.16|True|5|7|9| -Sorewa mo Lovechu|57-3|Happy Otaku Pack Vol.16|True|5|7|10| -Rave_Tech|57-4|Happy Otaku Pack Vol.16|True|5|8|10| -Brilliant & Shining!|57-5|Happy Otaku Pack Vol.16|False|5|8|10| -Psyched Fevereiro|56-0|Give Up TREATMENT Vol.11|False|5|8|10| -Inferno City|56-1|Give Up TREATMENT Vol.11|False|6|8|10| -Paradigm Shift|56-2|Give Up TREATMENT Vol.11|False|4|7|10| -Snapdragon|56-3|Give Up TREATMENT Vol.11|False|5|7|10| -Prestige and Vestige|56-4|Give Up TREATMENT Vol.11|True|6|8|11| -Tiny Fate|56-5|Give Up TREATMENT Vol.11|False|7|9|11| -Tsuki ni Murakumo Hana ni Kaze|55-0|Touhou Mugakudan -2-|False|3|5|7| -Patchouli's - Best Hit GSK|55-1|Touhou Mugakudan -2-|False|3|5|8| -Monosugoi Space Shuttle de Koishi ga Monosugoi uta|55-2|Touhou Mugakudan -2-|False|3|5|7|11 -Kakoinaki Yo wa Ichigo no Tsukikage|55-3|Touhou Mugakudan -2-|False|3|6|8| -Psychedelic Kizakura Doumei|55-4|Touhou Mugakudan -2-|False|4|7|10| -Mischievous Sensation|55-5|Touhou Mugakudan -2-|False|5|7|9| -White Canvas|54-0|MEGAREX THE FUTURE|False|3|6|8| -Gloomy Flash|54-1|MEGAREX THE FUTURE|False|5|8|10| -Find this Month's Featured Playlist|54-2|MEGAREX THE FUTURE|False|?|?|¿| -Sunday Night|54-3|MEGAREX THE FUTURE|False|3|6|9| -Goodbye Goodnight|54-4|MEGAREX THE FUTURE|False|4|6|9| -ENDLESS CIDER|54-5|MEGAREX THE FUTURE|False|4|6|8| -On And On!!|53-0|Happy Otaku Pack Vol.15|True|4|7|9|11 -Trip!|53-1|Happy Otaku Pack Vol.15|True|3|5|7| -Hoshi no otoshimono|53-2|Happy Otaku Pack Vol.15|False|5|7|9| -Plucky Race|53-3|Happy Otaku Pack Vol.15|True|5|8|10|11 -Fantasia Sonata Destiny|53-4|Happy Otaku Pack Vol.15|True|3|7|10| -Run through|53-5|Happy Otaku Pack Vol.15|False|5|8|10| -marooned night|52-0|MUSE RADIO FM103|False|2|4|6| -daydream girl|52-1|MUSE RADIO FM103|False|3|6|8| -Not Ornament|52-2|MUSE RADIO FM103|True|3|5|8| -Baby Pink|52-3|MUSE RADIO FM103|False|3|5|8| -I'm Here|52-4|MUSE RADIO FM103|False|4|6|8| -Masquerade Diary|51-0|Virtual Idol Production|True|2|5|8| -Reminiscence|51-1|Virtual Idol Production|True|5|7|9| -DarakuDatenshi|51-2|Virtual Idol Production|True|3|6|9| -D.I.Y.|51-3|Virtual Idol Production|False|4|6|9| -Boys in Virtual Land|51-4|Virtual Idol Production|False|4|7|9| -kui|51-5|Virtual Idol Production|True|5|7|9|11 -Nyan Cat|50-0|Nyanya Universe!|False|4|7|9| -PeroPero in the Universe|50-1|Nyanya Universe!|True|?|?|¿| -In-kya Yo-kya Onmyoji|50-2|Nyanya Universe!|False|6|8|10| -KABOOOOOM!!!!|50-3|Nyanya Universe!|True|4|6|8| -Doppelganger|50-4|Nyanya Universe!|True|5|7|9|12 -Pray a LOVE|49-0|DokiDoki! Valentine!|False|2|5|8| -Love-Avoidance Addiction|49-1|DokiDoki! Valentine!|False|3|5|7| -Daisuki Dayo feat.Wotoha|49-2|DokiDoki! Valentine!|False|5|7|10| -glory day|48-0|DJMAX Reflect|False|2|5|7| -Bright Dream|48-1|DJMAX Reflect|False|2|4|7| -Groovin Up|48-2|DJMAX Reflect|False|4|6|8| -I Want You|48-3|DJMAX Reflect|False|3|6|8| -OBLIVION|48-4|DJMAX Reflect|False|3|6|9| -Elastic STAR|48-5|DJMAX Reflect|False|4|6|8| -U.A.D|48-6|DJMAX Reflect|False|4|6|8|10 -Jealousy|48-7|DJMAX Reflect|False|3|5|7| -Memory of Beach|48-8|DJMAX Reflect|False|3|6|8| -Don't Die|48-9|DJMAX Reflect|False|6|8|10| -Y CE Ver.|48-10|DJMAX Reflect|False|4|6|9| -Fancy Night|48-11|DJMAX Reflect|False|4|6|8| -Can We Talk|48-12|DJMAX Reflect|False|4|6|8| -Give Me 5|48-13|DJMAX Reflect|False|2|6|8| -Nightmare|48-14|DJMAX Reflect|False|7|9|11| -Haze of Autumn|47-0|Arcaea|True|3|6|9| -GIMME DA BLOOD|47-1|Arcaea|False|3|6|9| -Libertas|47-2|Arcaea|False|4|7|10| -Cyaegha|47-3|Arcaea|False|5|7|9|11 -Bang!!|46-0|Happy Otaku Pack Vol.14|False|4|6|8| -Paradise 2|46-1|Happy Otaku Pack Vol.14|False|4|6|8| -Symbol|46-2|Happy Otaku Pack Vol.14|False|5|7|9| -Nekojarashi|46-3|Happy Otaku Pack Vol.14|False|5|8|10|11 -A Philosophical Wanderer|46-4|Happy Otaku Pack Vol.14|False|4|6|10| -Isouten|46-5|Happy Otaku Pack Vol.14|True|6|8|10|11 -ONOMATO Pairing!!!|45-0|WACCA Horizon|False|4|6|9| -with U|45-1|WACCA Horizon|False|6|8|10|11 -Chariot|45-2|WACCA Horizon|False|3|6|9| -GASHATT|45-3|WACCA Horizon|False|5|7|10| -LIN NE KRO NE feat. lasah|45-4|WACCA Horizon|False|6|8|10| -ANGEL HALO|45-5|WACCA Horizon|False|5|8|11| -Party in the HOLLOWood|44-0|Happy Otaku Pack Vol.13|False|3|6|8| -Ying Ying da Zuozhan|44-1|Happy Otaku Pack Vol.13|True|5|7|9| -Howlin' Pumpkin|44-2|Happy Otaku Pack Vol.13|True|4|6|8| -Bad Apple!! feat. Nomico|42-0|Touhou Mugakudan -1-|False|1|3|6|8 -Iro wa Nioedo, Chirinuru wo|42-1|Touhou Mugakudan -1-|False|2|4|7| -Cirno's Perfect Math Class|42-2|Touhou Mugakudan -1-|False|4|7|9| -Hiiro Gekka Kyousai no Zetsu|42-3|Touhou Mugakudan -1-|False|4|6|8| -Flowery Moonlit Night|42-4|Touhou Mugakudan -1-|False|3|6|8| -Unconscious Requiem|42-5|Touhou Mugakudan -1-|False|3|6|8| -Super Battleworn Insomniac|41-0|7th Beat Games|True|4|7|9|? -Bomb-Sniffing Pomeranian|41-1|7th Beat Games|True|4|6|8| -Rollerdisco Rumble|41-2|7th Beat Games|True|4|6|9| -Rose Garden|41-3|7th Beat Games|False|5|8|9| -EMOMOMO|41-4|7th Beat Games|True|4|7|10| -Heracles|41-5|7th Beat Games|False|6|8|10|? -Rush-More|40-0|Happy Otaku Pack Vol.12|False|4|7|9| -Kill My Fortune|40-1|Happy Otaku Pack Vol.12|False|5|7|10| -Yosari Tsukibotaru Suminoborite|40-2|Happy Otaku Pack Vol.12|False|5|7|9| -JUMP! HardCandy|40-3|Happy Otaku Pack Vol.12|False|3|6|8| -Hibari|40-4|Happy Otaku Pack Vol.12|False|3|5|8| -OCCHOCO-REST-LESS|40-5|Happy Otaku Pack Vol.12|True|4|7|9| -See-Saw Day|39-0|MUSE RADIO FM102|True|1|3|6| -happy hour|39-1|MUSE RADIO FM102|True|2|4|7| -Seikimatsu no Natsu|39-2|MUSE RADIO FM102|True|4|6|8| -twinkle night|39-3|MUSE RADIO FM102|False|3|6|8| -ARUYA HARERUYA|39-4|MUSE RADIO FM102|False|2|5|7| -Blush|39-5|MUSE RADIO FM102|False|2|4|7| -Naked Summer|39-6|MUSE RADIO FM102|True|4|6|8| -BLESS ME|39-7|MUSE RADIO FM102|True|2|5|7| -FM 17314 SUGAR RADIO|39-8|MUSE RADIO FM102|True|?|?|?| -NO ONE YES MAN|38-0|Phigros|False|5|7|9| -Snowfall, Merry Christmas|38-1|Phigros|False|5|8|10| -Igallta|38-2|Phigros|False|6|8|10|11 -Colored Glass|37-0|Cute Is Everything Vol.7|False|1|4|7| -Neonlights|37-1|Cute Is Everything Vol.7|False|4|7|9| -Hope for the flowers|37-2|Cute Is Everything Vol.7|False|4|7|9| -Seaside Cycling on May 30|37-3|Cute Is Everything Vol.7|False|3|6|8| -SKY HIGH|37-4|Cute Is Everything Vol.7|False|2|4|6| -Mousou Chu!!|37-5|Cute Is Everything Vol.7|False|4|7|8| -NightTheater|36-0|Give Up TREATMENT Vol.10|True|6|8|11| -Cutter|36-1|Give Up TREATMENT Vol.10|False|4|7|10| -bamboo|36-2|Give Up TREATMENT Vol.10|False|6|8|10|11 -enchanted love|36-3|Give Up TREATMENT Vol.10|False|2|6|9| -c.s.q.n.|36-4|Give Up TREATMENT Vol.10|False|5|8|11| -Booouncing!!|36-5|Give Up TREATMENT Vol.10|False|5|7|10| -PeroPeroGames goes Bankrupt|35-0|Happy Otaku Pack SP|True|6|8|10| -MARENOL|35-1|Happy Otaku Pack SP|False|4|7|10| -I am really good at Japanese style|35-2|Happy Otaku Pack SP|True|6|8|10| -Rush B|35-3|Happy Otaku Pack SP|True|4|7|9| -DataErr0r|35-4|Happy Otaku Pack SP|False|5|7|9|? -Burn|35-5|Happy Otaku Pack SP|True|4|7|9| -ALiVE|34-0|HARDCORE TANO*C|False|5|7|10| -BATTLE NO.1|34-1|HARDCORE TANO*C|False|5|8|10|11 -Cthugha|34-2|HARDCORE TANO*C|False|6|8|10|11 -TWINKLE*MAGIC|34-3|HARDCORE TANO*C|False|4|7|10|11 -Comet Coaster|34-4|HARDCORE TANO*C|False|6|8|10|11 -XODUS|34-5|HARDCORE TANO*C|False|7|9|11|12 -Fireflies|33-0|cyTus|True|1|4|7| -Light up my love!!|33-1|cyTus|True|3|5|7| -Happiness Breeze|33-2|cyTus|True|4|6|8|9 -Chrome VOX|33-3|cyTus|True|6|8|10|11 -CHAOS|33-4|cyTus|True|3|6|9| -Saika|33-5|cyTus|True|3|5|8| -Standby for Action|33-6|cyTus|True|4|6|8| -Hydrangea|33-7|cyTus|True|5|7|9| -Amenemhat|33-8|cyTus|True|6|8|10| -Santouka|33-9|cyTus|True|2|5|8| -HEXENNACHTROCK-katashihaya-|33-10|cyTus|True|4|8|10| -Blah!!|33-11|cyTus|True|5|8|11| -CHAOS Glitch|33-12|cyTus|True|0|?|0| -Preparara|32-0|Let's Do Bad Things Together|False|1|4|6| -Whatcha;Whatcha Doin'|32-1|Let's Do Bad Things Together|False|3|6|9| -Madara|32-2|Let's Do Bad Things Together|False|4|6|9| -pICARESq|32-3|Let's Do Bad Things Together|False|4|6|8| -Desastre|32-4|Let's Do Bad Things Together|False|4|6|8| -Shoot for the Moon|32-5|Let's Do Bad Things Together|False|2|5|8| -The 90's Decision|31-0|Happy Otaku Pack Vol.11|True|5|7|9| -Medusa|31-1|Happy Otaku Pack Vol.11|False|4|6|8|10 -Final Step!|31-2|Happy Otaku Pack Vol.11|False|5|7|10| -MAGENTA POTION|31-3|Happy Otaku Pack Vol.11|False|4|7|9| -Cross Ray|31-4|Happy Otaku Pack Vol.11|False|3|6|9| -Square Lake|31-5|Happy Otaku Pack Vol.11|False|6|8|9|11 -Girly Cupid|30-0|Cute Is Everything Vol.6|False|3|6|8| -sheep in the light|30-1|Cute Is Everything Vol.6|False|2|5|8| -Breaker city|30-2|Cute Is Everything Vol.6|False|4|6|9| -heterodoxy|30-3|Cute Is Everything Vol.6|False|4|6|8| -Computer Music Girl|30-4|Cute Is Everything Vol.6|False|3|5|7| -Focus Point|30-5|Cute Is Everything Vol.6|True|2|5|7| -Groove Prayer|29-0|Let' s GROOVE!|True|3|5|7| -FUJIN Rumble|29-1|Let' s GROOVE!|True|5|7|10|11 -Marry me, Nightmare|29-2|Let' s GROOVE!|False|6|8|11| -HG Makaizou Polyvinyl Shounen|29-3|Let' s GROOVE!|True|4|7|9|10 -Seizya no Ibuki|29-4|Let' s GROOVE!|True|6|8|10| -ouroboros -twin stroke of the end-|29-5|Let' s GROOVE!|True|4|6|9|12 -Heisha Onsha|28-0|Happy Otaku Pack Vol.10|False|4|6|8| -Ginevra|28-1|Happy Otaku Pack Vol.10|True|5|7|10|10 -Paracelestia|28-2|Happy Otaku Pack Vol.10|False|5|8|10| -un secret|28-3|Happy Otaku Pack Vol.10|False|2|4|6| -Good Life|28-4|Happy Otaku Pack Vol.10|False|4|6|8| -nini-nini-|28-5|Happy Otaku Pack Vol.10|False|4|7|9| -Can I friend you on Bassbook? lol|27-0|Nanahira Festival|False|3|6|8| -Gaming*Everything|27-1|Nanahira Festival|False|5|8|11| -Renji de haochi|27-2|Nanahira Festival|False|5|7|9| -You Make My Life 1UP|27-3|Nanahira Festival|False|4|6|8| -Newbies, Geeks, Internets|27-4|Nanahira Festival|False|6|8|10| -Onegai!Kon kon Oinarisama|27-5|Nanahira Festival|False|3|6|9| -Legend of Eastern Rabbit -SKY DEFENDER-|26-0|Give Up TREATMENT Vol.9|False|4|6|9| -ENERGY SYNERGY MATRIX|26-1|Give Up TREATMENT Vol.9|False|6|8|10| -Punai Punai Genso|26-2|Give Up TREATMENT Vol.9|False|2|7|11| -Better Graphic Animation|26-3|Give Up TREATMENT Vol.9|False|5|8|11| -Variant Cross|26-4|Give Up TREATMENT Vol.9|False|4|7|10| -Ultra Happy Miracle Bazoooooka!!|26-5|Give Up TREATMENT Vol.9|False|7|9|11| -tape/stop/night|25-0|MUSE RADIO FM101|True|3|5|7| -Pixel Galaxy|25-1|MUSE RADIO FM101|False|2|5|8| -Notice|25-2|MUSE RADIO FM101|False|4|7|10| -Strawberry Godzilla|25-3|MUSE RADIO FM101|True|2|5|7| -OKIMOCHI EXPRESSION|25-4|MUSE RADIO FM101|False|4|6|10| -Kimi to pool disco|25-5|MUSE RADIO FM101|False|4|6|8| -The Last Page|24-0|Happy Otaku Pack Vol.9|False|3|5|7| -IKAROS|24-1|Happy Otaku Pack Vol.9|False|4|7|10| -Tsukuyomi|24-2|Happy Otaku Pack Vol.9|False|3|6|9| -Future Stream|24-3|Happy Otaku Pack Vol.9|False|4|6|8| -FULi AUTO SHOOTER|24-4|Happy Otaku Pack Vol.9|True|4|7|9| -GOODFORTUNE|24-5|Happy Otaku Pack Vol.9|False|5|7|9| -The Dessert After Rain|23-0|Cute Is Everything Vol.5|True|2|4|6| -Confession Support Formula|23-1|Cute Is Everything Vol.5|False|3|5|7| -Omatsuri|23-2|Cute Is Everything Vol.5|False|1|3|6| -FUTUREPOP|23-3|Cute Is Everything Vol.5|True|2|5|7| -The Breeze|23-4|Cute Is Everything Vol.5|False|1|4|6| -I LOVE LETTUCE FRIED RICE!!|23-5|Cute Is Everything Vol.5|False|3|7|9| -The NightScape|22-0|Give Up TREATMENT Vol.8|False|4|7|9| -FREEDOM DiVE|22-1|Give Up TREATMENT Vol.8|False|6|8|10|12 -Phi|22-2|Give Up TREATMENT Vol.8|False|5|8|10| -Lueur de la nuit|22-3|Give Up TREATMENT Vol.8|False|6|8|11| -Creamy Sugary OVERDRIVE!!!|22-4|Give Up TREATMENT Vol.8|True|4|7|10| -Disorder|22-5|Give Up TREATMENT Vol.8|False|5|7|11| -Glimmer|21-0|Budget Is Burning: Nano Core|False|2|5|8| -EXIST|21-1|Budget Is Burning: Nano Core|False|3|5|8| -Irreplaceable|21-2|Budget Is Burning: Nano Core|False|4|6|8| -Moonlight Banquet|20-0|Happy Otaku Pack Vol.8|True|2|5|8| -Flashdance|20-1|Happy Otaku Pack Vol.8|False|3|6|9| -INFiNiTE ENERZY -Overdoze-|20-2|Happy Otaku Pack Vol.8|False|4|7|9|10 -One Way Street|20-3|Happy Otaku Pack Vol.8|False|3|6|10| -This Club is Not 4 U|20-4|Happy Otaku Pack Vol.8|False|4|7|9| -ULTRA MEGA HAPPY PARTY!!!|20-5|Happy Otaku Pack Vol.8|False|5|7|10| -INFINITY|19-0|Give Up TREATMENT Vol.7|True|5|8|10| -Punai Punai Senso|19-1|Give Up TREATMENT Vol.7|False|2|7|11| -Maxi|19-2|Give Up TREATMENT Vol.7|False|5|8|10| -YInMn Blue|19-3|Give Up TREATMENT Vol.7|False|6|8|10| -Plumage|19-4|Give Up TREATMENT Vol.7|False|4|7|10| -Dr.Techro|19-5|Give Up TREATMENT Vol.7|False|7|9|11| -SWEETSWEETSWEET|18-0|Cute Is Everything Vol.4|True|2|5|7| -Deep Blue and the Breaths of the Night|18-1|Cute Is Everything Vol.4|True|2|4|6| -Joy Connection|18-2|Cute Is Everything Vol.4|False|3|6|8| -Self Willed Girl Ver.B|18-3|Cute Is Everything Vol.4|True|4|6|8| -Just Disobedient|18-4|Cute Is Everything Vol.4|False|3|6|8| -Holy Sh*t Grass Snake|18-5|Cute Is Everything Vol.4|False|2|6|9| -Cotton Candy Wonderland|17-0|Happy Otaku Pack Vol.7|False|2|5|8| -Punai Punai Taiso|17-1|Happy Otaku Pack Vol.7|False|2|7|10| -Fly High|17-2|Happy Otaku Pack Vol.7|False|3|5|7| -prejudice|17-3|Happy Otaku Pack Vol.7|True|4|6|9| -The 89's Momentum|17-4|Happy Otaku Pack Vol.7|True|5|7|9| -energy night|17-5|Happy Otaku Pack Vol.7|True|5|7|10| -Future Dive|16-0|Give Up TREATMENT Vol.6|True|4|6|9| -Re End of a Dream|16-1|Give Up TREATMENT Vol.6|False|5|8|11| -Etude -Storm-|16-2|Give Up TREATMENT Vol.6|True|6|8|10| -Unlimited Katharsis|16-3|Give Up TREATMENT Vol.6|False|4|6|10| -Magic Knight Girl|16-4|Give Up TREATMENT Vol.6|False|4|7|9| -Eeliaas|16-5|Give Up TREATMENT Vol.6|False|6|9|11| -Magic Spell|15-0|Cute Is Everything Vol.3|True|2|5|7| -Colorful Star, Colored Drawing, Travel Poem|15-1|Cute Is Everything Vol.3|False|3|4|6| -Satell Knight|15-2|Cute Is Everything Vol.3|False|3|6|8| -Black River Feat.Mes|15-3|Cute Is Everything Vol.3|True|1|4|6| -I am sorry|15-4|Cute Is Everything Vol.3|False|2|5|8| -Ueta Tori Tachi|15-5|Cute Is Everything Vol.3|False|3|6|8| -Elysion's Old Mans|14-0|Happy Otaku Pack Vol.6|False|3|5|8| -AXION|14-1|Happy Otaku Pack Vol.6|False|4|5|8| -Amnesia|14-2|Happy Otaku Pack Vol.6|True|3|6|9| -Onsen Dai Sakusen|14-3|Happy Otaku Pack Vol.6|True|4|6|8| -Gleam stone|14-4|Happy Otaku Pack Vol.6|False|4|7|9| -GOODWORLD|14-5|Happy Otaku Pack Vol.6|False|4|7|10| -Instant Soluble Neon|13-0|Cute Is Everything Vol.2|True|2|4|7| -Retrospective Poem on the Planet|13-1|Cute Is Everything Vol.2|False|3|5|7| -I'm Gonna Buy! Buy! Buy!|13-2|Cute Is Everything Vol.2|True|4|6|8| -Dating Manifesto|13-3|Cute Is Everything Vol.2|True|2|4|6| -First Snow|13-4|Cute Is Everything Vol.2|True|2|3|6| -Xin Shang Huahai|13-5|Cute Is Everything Vol.2|False|3|6|8| -Gaikan Chrysalis|12-0|Give Up TREATMENT Vol.5|False|4|6|8| -Sterelogue|12-1|Give Up TREATMENT Vol.5|True|5|7|10| -Cheshire's Dance|12-2|Give Up TREATMENT Vol.5|True|4|7|10| -Skrik|12-3|Give Up TREATMENT Vol.5|True|5|7|11| -Soda Pop Canva5!|12-4|Give Up TREATMENT Vol.5|False|5|8|10| -RUBY LINTe|12-5|Give Up TREATMENT Vol.5|False|5|8|11| -Brave My Heart|11-0|Happy Otaku Pack Vol.5|True|3|5|7| -Sakura Fubuki|11-1|Happy Otaku Pack Vol.5|False|4|7|10| -8bit Adventurer|11-2|Happy Otaku Pack Vol.5|False|6|8|10| -Suffering of screw|11-3|Happy Otaku Pack Vol.5|False|3|5|8| -tiny lady|11-4|Happy Otaku Pack Vol.5|True|4|6|9| -Power Attack|11-5|Happy Otaku Pack Vol.5|False|5|7|10| -Destr0yer|10-0|Give Up TREATMENT Vol.4|False|4|7|9| -Noel|10-1|Give Up TREATMENT Vol.4|False|5|8|10| -Kyoukiranbu|10-2|Give Up TREATMENT Vol.4|False|7|9|11| -Two Phace|10-3|Give Up TREATMENT Vol.4|True|4|7|10| -Fly Again|10-4|Give Up TREATMENT Vol.4|False|5|7|10| -ouroVoros|10-5|Give Up TREATMENT Vol.4|False|7|9|11| -Leave It Alone|9-0|Happy Otaku Pack Vol.4|True|2|5|8| -Tsubasa no Oreta Tenshitachi no Requiem|9-1|Happy Otaku Pack Vol.4|False|4|7|9| -Chronomia|9-2|Happy Otaku Pack Vol.4|False|5|7|10| -Dandelion's Daydream|9-3|Happy Otaku Pack Vol.4|True|5|7|8| -Lorikeet Flat design|9-4|Happy Otaku Pack Vol.4|True|5|7|10| -GOODRAGE|9-5|Happy Otaku Pack Vol.4|False|6|9|11| -Altale|8-0|Give Up TREATMENT Vol.3|False|3|5|7|10 -Brain Power|8-1|Give Up TREATMENT Vol.3|False|4|7|10| -Berry Go!!|8-2|Give Up TREATMENT Vol.3|False|3|6|9| -Sweet* Witch* Girl*|8-3|Give Up TREATMENT Vol.3|False|6|8|10|? -trippers feeling!|8-4|Give Up TREATMENT Vol.3|True|5|7|9|11 -Lilith ambivalence lovers|8-5|Give Up TREATMENT Vol.3|False|5|8|10| -Brave My Soul|7-0|Give Up TREATMENT Vol.2|False|4|6|8| -Halcyon|7-1|Give Up TREATMENT Vol.2|False|4|7|10| -Crimson Nightingale|7-2|Give Up TREATMENT Vol.2|True|4|7|10| -Invader|7-3|Give Up TREATMENT Vol.2|True|3|7|11| -Lyrith|7-4|Give Up TREATMENT Vol.2|False|5|7|10| -GOODBOUNCE|7-5|Give Up TREATMENT Vol.2|False|4|6|9| -Out of Sense|6-0|Budget Is Burning Vol.1|False|3|5|8| -My Life Is For You|6-1|Budget Is Burning Vol.1|False|2|4|7| -Etude -Sunset-|6-2|Budget Is Burning Vol.1|True|5|7|9| -Goodbye Boss|6-3|Budget Is Burning Vol.1|False|4|6|8| -Stargazer|6-4|Budget Is Burning Vol.1|True|2|5|8|9 -Lys Tourbillon|6-5|Budget Is Burning Vol.1|True|4|6|8| -Thirty Million Persona|5-0|Happy Otaku Pack Vol.3|False|2|4|6| -conflict|5-1|Happy Otaku Pack Vol.3|False|2|6|9|10 -Enka Dance Music|5-2|Happy Otaku Pack Vol.3|False|3|5|7| -XING|5-3|Happy Otaku Pack Vol.3|True|4|6|8|9 -Amakakeru Soukyuu no Serenade|5-4|Happy Otaku Pack Vol.3|False|3|6|9| -Gift box|5-5|Happy Otaku Pack Vol.3|False|5|7|10| -MUSEDASH!!!!|4-0|Happy Otaku Pack Vol.2|False|2|6|9|0 -Imprinting|4-1|Happy Otaku Pack Vol.2|False|3|6|9|0 -Skyward|4-2|Happy Otaku Pack Vol.2|True|4|7|10|0 -La nuit de vif|4-3|Happy Otaku Pack Vol.2|True|2|5|8|0 -Bit-alize|4-4|Happy Otaku Pack Vol.2|False|3|6|8|0 -GOODTEK|4-5|Happy Otaku Pack Vol.2|False|4|6|9|? -Maharajah|3-0|Happy Otaku Pack Vol.1|False|1|3|6| -keep on running|3-1|Happy Otaku Pack Vol.1|False|5|7|9| -Kafig|3-2|Happy Otaku Pack Vol.1|True|4|6|8| --+|3-3|Happy Otaku Pack Vol.1|True|4|6|8| -Tenri Kaku Jou|3-4|Happy Otaku Pack Vol.1|True|3|6|9| -Adjudicatorz-DanZai-|3-5|Happy Otaku Pack Vol.1|False|3|7|10| -Oriens|2-0|Give Up TREATMENT Vol.1|True|3|7|9| -PUPA|2-1|Give Up TREATMENT Vol.1|False|6|8|11| -Luna Express 2032|2-2|Give Up TREATMENT Vol.1|False|4|6|8| -Ukiyoe Yokochou|2-3|Give Up TREATMENT Vol.1|False|6|7|9| -Alice in Misanthrope|2-4|Give Up TREATMENT Vol.1|False|5|7|10| -GOODMEN|2-5|Give Up TREATMENT Vol.1|False|5|7|10| -Sunshine and Rainbow after August Rain|1-0|Cute Is Everything Vol.1|False|2|5|8| -Magical Number|1-1|Cute Is Everything Vol.1|False|2|5|8| -Dreaming Girl|1-2|Cute Is Everything Vol.1|False|2|5|6| -Daruma-san Fell Over|1-3|Cute Is Everything Vol.1|False|3|4|6| -Different|1-4|Cute Is Everything Vol.1|False|1|3|6| -The Future of the Phantom|1-5|Cute Is Everything Vol.1|False|1|3|5| -Doki Doki Jump!|63-0|MUSE RADIO FM104|True|3|5|7| -Centennial Streamers High|63-1|MUSE RADIO FM104|False|4|7|9| -Love Patrol|63-2|MUSE RADIO FM104|True|3|5|7| -Mahorova|63-3|MUSE RADIO FM104|True|3|5|8| -Yoru no machi|63-4|MUSE RADIO FM104|True|1|4|7| -INTERNET YAMERO|63-5|MUSE RADIO FM104|True|6|8|10| -Abracadabra|43-24|MD Plus Project|False|6|8|10| -Squalldecimator feat. EZ-Ven|43-25|MD Plus Project|True|5|7|9| -Amateras Rhythm|43-26|MD Plus Project|True|6|8|11| -Record one's Dream|43-27|MD Plus Project|False|4|7|10| -Lunatic|43-28|MD Plus Project|True|5|8|10| -Jiumeng|43-29|MD Plus Project|True|3|6|8| -The Day We Become Family|43-30|MD Plus Project|True|3|5|8| -Sutori ma FIRE!?!?|64-0|COSMIC RADIO PEROLIST|True|3|5|8| -Tanuki Step|64-1|COSMIC RADIO PEROLIST|True|5|7|10|11 -Space Stationery|64-2|COSMIC RADIO PEROLIST|True|5|7|10| -Songs Are Judged 90% by Chorus feat. Mameko|64-3|COSMIC RADIO PEROLIST|True|6|8|10| -Kawai Splendid Space Thief|64-4|COSMIC RADIO PEROLIST|False|6|8|10|11 -Night City Runway|64-5|COSMIC RADIO PEROLIST|True|4|6|8| -Chaos Shotgun feat. ChumuNote|64-6|COSMIC RADIO PEROLIST|True|6|8|10| -mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11 -BrainDance|65-0|NeonAbyss|True|3|6|9| -My Focus!|65-1|NeonAbyss|True|5|7|10| -ABABABA BURST|65-2|NeonAbyss|True|5|7|9| -ULTRA HIGHER|65-3|NeonAbyss|True|4|7|10| -Silver Bullet|43-31|MD Plus Project|True|5|7|10| -Random|43-32|MD Plus Project|True|4|7|9| -OTOGE-BOSS-KYOKU-CHAN|43-33|MD Plus Project|False|6|8|10|11 -Crow Rabbit|43-34|MD Plus Project|True|7|9|11| -SyZyGy|43-35|MD Plus Project|True|6|8|10|11 -Mermaid Radio|43-36|MD Plus Project|True|3|5|7| -Helixir|43-37|MD Plus Project|False|6|8|10| -Highway Cruisin'|43-38|MD Plus Project|False|3|5|8| -JACK PT BOSS|43-39|MD Plus Project|False|6|8|10| -Time Capsule|43-40|MD Plus Project|False|7|9|11| -39 Music!|66-0|Miku in Museland|False|3|5|8| -Hand in Hand|66-1|Miku in Museland|False|1|3|6| -Cynical Night Plan|66-2|Miku in Museland|False|4|6|8| -God-ish|66-3|Miku in Museland|False|4|7|10| -Darling Dance|66-4|Miku in Museland|False|4|7|9| -Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10|11 -The Vampire|66-6|Miku in Museland|False|4|6|9| -Future Eve|66-7|Miku in Museland|False|4|8|11| -Unknown Mother Goose|66-8|Miku in Museland|False|4|8|10| -Shun-ran|66-9|Miku in Museland|False|4|7|9| -NICE TYPE feat. monii|43-41|MD Plus Project|True|3|6|8| -Rainy Angel|67-0|Happy Otaku Pack Vol.18|True|4|6|9|11 -Gullinkambi|67-1|Happy Otaku Pack Vol.18|True|4|7|10| -RakiRaki Rebuilders!!!|67-2|Happy Otaku Pack Vol.18|True|5|7|10| -Laniakea|67-3|Happy Otaku Pack Vol.18|False|5|8|10| -OTTAMA GAZER|67-4|Happy Otaku Pack Vol.18|True|5|8|10| -Sleep Tight feat.Macoto|67-5|Happy Otaku Pack Vol.18|True|3|5|8| -New York Back Raise|68-0|Gambler's Tricks|True|6|8|10| -slic.hertz|68-1|Gambler's Tricks|True|5|7|9| -Fuzzy-Navel|68-2|Gambler's Tricks|True|6|8|10|11 -Swing Edge|68-3|Gambler's Tricks|True|4|8|10| -Twisted Escape|68-4|Gambler's Tricks|True|5|8|10|11 -Swing Sweet Twee Dance|68-5|Gambler's Tricks|False|4|7|10| -Sanyousei SAY YA!!!|43-42|MD Plus Project|False|4|6|8| -YUKEMURI TAMAONSEN II|43-43|MD Plus Project|False|3|6|9| -Samayoi no mei Amatsu|69-0|Touhou Mugakudan -3-|False|4|6|9| -INTERNET SURVIVOR|69-1|Touhou Mugakudan -3-|False|5|8|10| -Shuki*RaiRai|69-2|Touhou Mugakudan -3-|False|5|7|9| -HELLOHELL|69-3|Touhou Mugakudan -3-|False|4|7|10| -Calamity Fortune|69-4|Touhou Mugakudan -3-|True|6|8|10|11 -Tsurupettan|69-5|Touhou Mugakudan -3-|True|2|5|8| -Twilight Poems|43-44|MD Plus Project|True|3|6|8| -All My Friends feat. RANASOL|43-45|MD Plus Project|True|4|7|9| -Heartache|43-46|MD Plus Project|True|5|7|10| -Blue Lemonade|43-47|MD Plus Project|True|3|6|8| -Haunted Dance|43-48|MD Plus Project|False|6|9|11| -Hey Vincent.|43-49|MD Plus Project|True|6|8|10| -Meteor feat. TEA|43-50|MD Plus Project|True|3|6|9| -Narcissism Angel|43-51|MD Plus Project|True|1|3|6| -AlterLuna|43-52|MD Plus Project|True|6|8|11|12 -Niki Tousen|43-53|MD Plus Project|True|6|8|10|12 -Rettou Joutou|70-0|Rin Len's Mirrorland|False|4|7|9| -Telecaster B-Boy|70-1|Rin Len's Mirrorland|False|5|7|10| -Iya Iya Iya|70-2|Rin Len's Mirrorland|False|2|4|7| -Nee Nee Nee|70-3|Rin Len's Mirrorland|False|4|6|8| -Chaotic Love Revolution|70-4|Rin Len's Mirrorland|False|4|6|8| -Dance of the Corpses|70-5|Rin Len's Mirrorland|False|2|5|8| -Bitter Choco Decoration|70-6|Rin Len's Mirrorland|False|3|6|9| -Dance Robot Dance|70-7|Rin Len's Mirrorland|False|4|7|10| -Sweet Devil|70-8|Rin Len's Mirrorland|False|5|7|9| -Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9| -Yume Ou Mono Yo Secret|0-53|Default Music|True|6|8|10| -Yume Ou Mono Yo|0-54|Default Music|True|1|4|0| -Sweet Dream VIVINOS|71-0|Valentine Stage|False|1|4|7| -Ruler Of My Heart VIVINOS|71-1|Valentine Stage|False|2|4|6| -Reality Show|71-2|Valentine Stage|False|5|7|10| -SIG feat.Tobokegao|71-3|Valentine Stage|True|3|6|8| -Rose Love|71-4|Valentine Stage|True|2|4|7| -Euphoria|71-5|Valentine Stage|True|1|3|6| -P E R O P E R O Brother Dance|72-0|Legends of Muse Warriors|True|0|?|0| -PA PPA PANIC|72-1|Legends of Muse Warriors|False|4|8|10| -How To Make Music Game Song!|72-2|Legends of Muse Warriors|True|6|8|10|11 -Re Re|72-3|Legends of Muse Warriors|True|7|9|11|12 -Marmalade Twins|72-4|Legends of Muse Warriors|True|5|8|10| -DOMINATOR|72-5|Legends of Muse Warriors|True|7|9|11| -Teshikani TESHiKANi|72-6|Legends of Muse Warriors|True|5|7|9| -Urban Magic|73-0|Happy Otaku Pack Vol.19|True|3|5|7| -Maid's Prank|73-1|Happy Otaku Pack Vol.19|True|5|7|10| -Dance Dance Good Night Dance|73-2|Happy Otaku Pack Vol.19|True|2|4|7| -Ops Limone|73-3|Happy Otaku Pack Vol.19|True|5|8|11| -NOVA|73-4|Happy Otaku Pack Vol.19|True|6|8|10| -Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10| -Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10| -World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11 -Tsukuyomi Ni Naru Replaced|74-2|CHUNITHM COURSE MUSE|True|5|7|9| -The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11 -Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11 -Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12 -HIT ME UP|43-54|MD Plus Project|True|4|6|8| -Test Me feat. Uyeon|43-55|MD Plus Project|True|3|5|9| -Assault TAXI|43-56|MD Plus Project|True|4|7|10| -No|43-57|MD Plus Project|False|4|6|9| -Pop it|43-58|MD Plus Project|True|1|3|6| -HEARTBEAT! KyunKyun!|43-59|MD Plus Project|True|4|6|9| -SUPERHERO|75-0|Novice Rider Pack|False|2|4|7| -Highway_Summer|75-1|Novice Rider Pack|True|2|4|6| -Mx. Black Box|75-2|Novice Rider Pack|True|5|7|9| -Sweet Encounter|75-3|Novice Rider Pack|True|2|4|7| -Echo over you... Secret|0-55|Default Music|False|6|8|10| -Echo over you...|0-56|Default Music|False|1|4|0| -Tsukuyomi Ni Naru|74-6|CHUNITHM COURSE MUSE|True|5|8|10| -disco light|76-0|MUSE RADIO FM105|True|5|7|9| -room light feat.chancylemon|76-1|MUSE RADIO FM105|True|3|5|7| -Invisible|76-2|MUSE RADIO FM105|True|3|5|8| -Christmas Season-LLABB|76-3|MUSE RADIO FM105|True|1|4|7| -Hyouryu|77-0|Let's Rhythm Jam!|False|6|8|10| -The Whole Rest|77-1|Let's Rhythm Jam!|False|5|8|10|11 -Hydra|77-2|Let's Rhythm Jam!|False|4|7|11| -Pastel Lines|77-3|Let's Rhythm Jam!|False|3|6|9| -LINK x LIN#S|77-4|Let's Rhythm Jam!|False|3|6|9| -Arcade ViruZ|77-5|Let's Rhythm Jam!|False|6|8|11| -Eve Avenir|78-0|Endless Pirouette|True|6|8|10| -Silverstring|78-1|Endless Pirouette|True|5|7|10| -Melusia|78-2|Endless Pirouette|False|5|7|10|11 -Devil's Castle|78-3|Endless Pirouette|True|4|7|10| -Abatement|78-4|Endless Pirouette|True|6|8|10|11 -Azalea|78-5|Endless Pirouette|False|4|8|10| -Brightly World|78-6|Endless Pirouette|True|6|8|10| -We'll meet in every world ***|78-7|Endless Pirouette|True|7|9|11| -Collapsar|78-8|Endless Pirouette|True|7|9|10|11 -Parousia|78-9|Endless Pirouette|False|6|8|10| -Gunners in the Rain|79-0|Ensemble Arcanum|False|5|8|10| -Halzion|79-1|Ensemble Arcanum|False|2|5|8| -SHOWTIME!!|79-2|Ensemble Arcanum|False|6|8|10| -Achromic Riddle|79-3|Ensemble Arcanum|False|6|8|10|11 -karanosu|79-4|Ensemble Arcanum|False|3|6|8| diff --git a/worlds/musedash/Options.py b/worlds/musedash/Options.py index b8c969c39b..9f729c2d03 100644 --- a/worlds/musedash/Options.py +++ b/worlds/musedash/Options.py @@ -1,13 +1,14 @@ -from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions, OptionGroup, Removed +from Options import Toggle, Range, Choice, DeathLink, OptionSet, PerGameCommonOptions, OptionGroup, Removed from dataclasses import dataclass from .MuseDashCollection import MuseDashCollections +from .MuseDashData import SONG_DATA class DLCMusicPacks(OptionSet): """ Choose which DLC Packs will be included in the pool of chooseable songs. - + Note: The [Just As Planned] DLC contains all [Muse Plus] songs. """ display_name = "DLC Packs" @@ -17,7 +18,7 @@ class DLCMusicPacks(OptionSet): class StreamerModeEnabled(Toggle): """ In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming. - + If this is enabled, only songs available under Streamer Mode will be available for randomization. """ display_name = "Streamer Mode Only Songs" @@ -69,7 +70,7 @@ class DifficultyMode(Choice): class DifficultyModeOverrideMin(Range): """ Ensures that 1 difficulty has at least 1 this value or higher per song. - + Note: Difficulty Mode must be set to Manual. """ display_name = "Manual Difficulty Min" @@ -82,7 +83,7 @@ class DifficultyModeOverrideMin(Range): class DifficultyModeOverrideMax(Range): """ Ensures that 1 difficulty has at least 1 this value or lower per song. - + Note: Difficulty Mode must be set to Manual. """ display_name = "Manual Difficulty Max" @@ -114,7 +115,7 @@ class GradeNeeded(Choice): class MusicSheetCountPercentage(Range): """ Controls how many music sheets are added to the pool based on the number of songs, including starting songs. - + Higher numbers leads to more consistent game lengths, but will cause individual music sheets to be less important. """ range_start = 10 @@ -137,7 +138,7 @@ class ChosenTraps(OptionSet): - Traps last the length of a song, or until you die. - VFX Traps consist of visual effects that play over the song. (i.e. Grayscale.) - SFX Traps consist of changing your sfx setting to one possibly more annoying sfx. - + Note: SFX traps are only available if [Just as Planned] DLC songs are enabled. """ display_name = "Chosen Traps" @@ -152,24 +153,26 @@ class TrapCountPercentage(Range): display_name = "Trap Percentage" -class IncludeSongs(ItemSet): +class SongSet(OptionSet): + valid_keys = SONG_DATA.keys() + + +class IncludeSongs(SongSet): """ These songs will be guaranteed to show up within the seed. - You must have the DLC enabled to play these songs. - Difficulty options will not affect these songs. - If there are too many included songs, this will act as a whitelist ignoring song difficulty. """ - verify_item_name = True display_name = "Include Songs" -class ExcludeSongs(ItemSet): +class ExcludeSongs(SongSet): """ These songs will be guaranteed to not show up within the seed. - + Note: Does not affect songs within the "Include Songs" list. """ - verify_item_name = True display_name = "Exclude Songs" @@ -211,7 +214,7 @@ class MuseDashOptions(PerGameCommonOptions): death_link: DeathLink include_songs: IncludeSongs exclude_songs: ExcludeSongs - + # Removed allow_just_as_planned_dlc_songs: Removed available_trap_types: Removed diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py index be2eec2f87..d793308a7c 100644 --- a/worlds/musedash/__init__.py +++ b/worlds/musedash/__init__.py @@ -63,6 +63,11 @@ class MuseDashWorld(World): item_name_to_id = {name: code for name, code in md_collection.item_names_to_id.items()} location_name_to_id = {name: code for name, code in md_collection.location_names_to_id.items()} + item_name_groups = { + "Songs": {name for name in md_collection.song_items.keys()}, + "Filler Items": {name for name in md_collection.filler_items.keys()}, + "Traps": {name for name in md_collection.trap_items.keys()} + } # Working Data victory_song_name: str = "" @@ -179,10 +184,6 @@ class MuseDashWorld(World): if trap: return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player) - album = self.md_collection.album_items.get(name) - if album: - return MuseDashSongItem(name, self.player, album) - song = self.md_collection.song_items[name] return MuseDashSongItem(name, self.player, song) diff --git a/worlds/musedash/test/TestDifficultyRanges.py b/worlds/musedash/test/TestDifficultyRanges.py index a9c36985af..27798243a5 100644 --- a/worlds/musedash/test/TestDifficultyRanges.py +++ b/worlds/musedash/test/TestDifficultyRanges.py @@ -1,7 +1,17 @@ from . import MuseDashTestBase +from typing import List class DifficultyRanges(MuseDashTestBase): + DIFF_OVERRIDES: List[str] = [ + "MuseDash ka nanika hi", + "Rush-Hour", + "Find this Month's Featured Playlist", + "PeroPero in the Universe", + "umpopoff", + "P E R O P E R O Brother Dance", + ] + def test_all_difficulty_ranges(self) -> None: muse_dash_world = self.get_world() dlc_set = {x for x in muse_dash_world.md_collection.DLC} @@ -63,7 +73,7 @@ class DifficultyRanges(MuseDashTestBase): def test_songs_have_difficulty(self) -> None: muse_dash_world = self.get_world() - for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES: + for song_name in self.DIFF_OVERRIDES: song = muse_dash_world.md_collection.song_items[song_name] # Some songs are weird and have less than the usual 3 difficulties. From 172ad4e57d440809685462927454252609ea45fa Mon Sep 17 00:00:00 2001 From: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Date: Sun, 12 Jan 2025 13:00:20 -0500 Subject: [PATCH 20/21] Adventure: Optimize imports (#4300) --- worlds/adventure/Options.py | 5 ++--- worlds/adventure/Regions.py | 2 +- worlds/adventure/Rom.py | 12 ++++++------ worlds/adventure/__init__.py | 23 ++++++----------------- 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/worlds/adventure/Options.py b/worlds/adventure/Options.py index e6a8e4c202..4b3f30df24 100644 --- a/worlds/adventure/Options.py +++ b/worlds/adventure/Options.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import Dict - from dataclasses import dataclass -from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions + +from Options import Choice, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions class FreeincarnateMax(Range): diff --git a/worlds/adventure/Regions.py b/worlds/adventure/Regions.py index 4e4dd1e7ba..a0a04be2aa 100644 --- a/worlds/adventure/Regions.py +++ b/worlds/adventure/Regions.py @@ -1,6 +1,6 @@ from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType from Options import PerGameCommonOptions -from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region +from .Locations import location_table, AdventureLocation, dragon_room_to_region def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True, diff --git a/worlds/adventure/Rom.py b/worlds/adventure/Rom.py index 643f7a6c76..4d56cd19e5 100644 --- a/worlds/adventure/Rom.py +++ b/worlds/adventure/Rom.py @@ -2,15 +2,15 @@ import hashlib import json import os import zipfile -from typing import Optional, Any - -import Utils -from .Locations import AdventureLocation, LocationData -from settings import get_settings -from worlds.Files import APPatch, AutoPatchRegister +from typing import Any import bsdiff4 +import Utils +from settings import get_settings +from worlds.Files import APPatch, AutoPatchRegister +from .Locations import LocationData + ADVENTUREHASH: str = "157bddb7192754a45372be196797f284" diff --git a/worlds/adventure/__init__.py b/worlds/adventure/__init__.py index 4fde1482cf..9dab2ffcef 100644 --- a/worlds/adventure/__init__.py +++ b/worlds/adventure/__init__.py @@ -1,35 +1,24 @@ -import base64 import copy -import itertools import math import os -import settings import typing -from enum import IntFlag -from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple +from typing import ClassVar, Dict, Optional, Tuple -from BaseClasses import Entrance, Item, ItemClassification, MultiWorld, Region, Tutorial, \ - LocationProgressType +import settings +from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial, LocationProgressType from Utils import __version__ -from Options import AssembleOptions from worlds.AutoWorld import WebWorld, World -from Fill import fill_restrictive -from worlds.generic.Rules import add_rule, set_rule -from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \ - AdventureOptions -from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \ - AdventureAutoCollectLocation +from worlds.LauncherComponents import Component, components, SuffixIdentifier from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max from .Locations import location_table, base_location_id, LocationData, get_random_room_in_regions from .Offsets import static_item_data_location, items_ram_start, static_item_element_size, item_position_table, \ static_first_dragon_index, connector_port_offset, yorgle_speed_data_location, grundle_speed_data_location, \ rhindle_speed_data_location, item_ram_addresses, start_castle_values, start_castle_offset +from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, AdventureOptions from .Regions import create_regions +from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, AdventureAutoCollectLocation from .Rules import set_rules - -from worlds.LauncherComponents import Component, components, SuffixIdentifier - # Adventure components.append(Component('Adventure Client', 'AdventureClient', file_identifier=SuffixIdentifier('.apadvn'))) From 1f966ee705e576f385c71b3a19d013a0995f0df1 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sun, 12 Jan 2025 12:01:16 -0600 Subject: [PATCH 21/21] BizhawkClient: set metadata from patch file (#4346) --- worlds/_bizhawk/context.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/worlds/_bizhawk/context.py b/worlds/_bizhawk/context.py index 2a3965a54f..8d029f92ec 100644 --- a/worlds/_bizhawk/context.py +++ b/worlds/_bizhawk/context.py @@ -231,12 +231,14 @@ async def _run_game(rom: str): ) -async def _patch_and_run_game(patch_file: str): +def _patch_and_run_game(patch_file: str): try: metadata, output_file = Patch.create_rom_file(patch_file) Utils.async_start(_run_game(output_file)) + return metadata except Exception as exc: logger.exception(exc) + return {} def launch(*launch_args) -> None: @@ -245,6 +247,11 @@ def launch(*launch_args) -> None: parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file") args = parser.parse_args(launch_args) + if args.patch_file != "": + metadata = _patch_and_run_game(args.patch_file) + if "server" in metadata: + args.connect = metadata["server"] + ctx = BizHawkClientContext(args.connect, args.password) ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") @@ -252,9 +259,6 @@ def launch(*launch_args) -> None: ctx.run_gui() ctx.run_cli() - if args.patch_file != "": - Utils.async_start(_patch_and_run_game(args.patch_file)) - watcher_task = asyncio.create_task(_game_watcher(ctx), name="GameWatcher") try: