Add VVVVVV to Archipelago (#178)

This commit is contained in:
Yussur Mustafa Oraji 2022-01-21 22:42:11 +01:00 committed by GitHub
parent 4291912577
commit 344f4afdbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 292 additions and 0 deletions

View File

@ -0,0 +1,36 @@
# VVVVVV
## Where is the settings page?
The player settings page for this game contains all the options you need to configure and export a config file. Player
settings page link: [VVVVVV Player Settings Page](../player-settings).
## What does randomization do to this game?
All 20 Trinkets are now Location Checks and may not actually contain Trinkets, but Items for different games.
Optionally, you may enable DoorCost, which will gate away some areas:
- Laboratory
- The Tower
- Space Station 2 and
- Warp Zone
until you've collected some Trinkets.
Examples:
- If you set DoorCost at 2, then to enter Laboratory you will need Trinkets 1-2, for The Tower 3-4, etc.
- If you set DoorCost at 3, then to enter Laboratory you will need Trinkets 1-3, for The Tower 4-6, etc.
## What is the goal of VVVVVV when randomized?
Save all crew members, and finish the story.
## Which items can be in another player's world?
Any of the 20 Trinkets.
## What does another world's item look like in VVVVVV?
The Trinkets are visually unchanged, though after collecting a textbox will pop up to inform you what you collected,
and who will receive it.
## When the player receives an item, what happens?
When you receive a Trinket, the standard Animation will play. Afterwards a textbox will inform you where
you received the Trinket from, and which one it is.
NOTE: You can't check your trinkets in the Spaceship. Instead, you can check them in the pause menu under 'Stats'.
This is especially useful if you have DoorCost enabled.

View File

@ -441,5 +441,24 @@
]
}
]
},
{
"gameTitle": "VVVVVV",
"tutorials": [
{
"name": "Multiworld Setup Guide",
"description": "A guide to setting up VVVVVV for MultiWorld.",
"files": [
{
"language": "English",
"filename": "v6/setup_en.md",
"link": "v6/setup/en",
"authors": [
"N00byKing"
]
}
]
}
]
}
]

View File

@ -0,0 +1,37 @@
# VVVVVV MultiWorld Setup Guide
## Required Software
- VVVVVV (Bought from the [Steam Store](https://store.steampowered.com/app/70300/VVVVVV/) or [GOG Store](https://www.gog.com/game/vvvvvv) Page, NOT Make and Play Edition!)
- [V6AP](https://github.com/N00byKing/VVVVVV/actions/workflows/ci.yml?query=branch%3Aarchipelago)
## Installation and Game Start Procedures
1. Install VVVVVV through either Steam or GOG
2. Go to the page linked for V6AP, and press on the topmost entry
3. Scroll down, and download the zip file corresponding to your platform (NOTE: Linux currently does not build automatically. Linux users will have to compile manually for now. Mac is unsupported, but may work if [APCpp](https://github.com/N00byKing/APCpp) is compiled and supplied)
4. Unpack the zip file where you have VVVVVV installed.
# Joining a MultiWorld Game
To join, set the following launch options: `-v6ap_name "YourName" -v6ap_ip "ServerIP"`.
Optionally, add `-v6ap_passwd "YourPassword"` if the room you are using requires a password. All parameters without quotation marks.
The Name in this case is the one specified in your generated .yaml file.
In case you are using the Archipelago Website, the IP should be `archipelago.gg`.
If everything worked out, you will see a textbox informing you the connection has been established after the story intro.
## Installation Troubleshooting
Start the game from the command line to view helpful messages regarding V6AP. These will look something like "V6AP: Message"
### Game no longer starts after copying the .exe
Most likely you forgot to set the launch options. `-v6ap_name "YourName"` and `-v6ap_ip "ServerIP"` are required for startup.
## Game Troubleshooting
### What happens if I lose connection?
V6AP tries to reconnect a few times, so be patient.
Should the problem still be there after about a minute or two, just save and restart the game.

27
worlds/v6/Items.py Normal file
View File

@ -0,0 +1,27 @@
from BaseClasses import Item
class V6Item(Item):
game: str = "VVVVVV"
item_table = {
"Trinket 01": 2515000,
"Trinket 02": 2515001,
"Trinket 03": 2515002,
"Trinket 04": 2515003,
"Trinket 05": 2515004,
"Trinket 06": 2515005,
"Trinket 07": 2515006,
"Trinket 08": 2515007,
"Trinket 09": 2515008,
"Trinket 10": 2515009,
"Trinket 11": 2515010,
"Trinket 12": 2515011,
"Trinket 13": 2515012,
"Trinket 14": 2515013,
"Trinket 15": 2515014,
"Trinket 16": 2515015,
"Trinket 17": 2515016,
"Trinket 18": 2515017,
"Trinket 19": 2515018,
"Trinket 20": 2515019
}

27
worlds/v6/Locations.py Normal file
View File

@ -0,0 +1,27 @@
from BaseClasses import Location
class V6Location(Location):
game: str = "VVVVVV"
location_table = { # Correspond to 2515000 + index in collect array of game code
"It's a Secret to Nobody": 2515000,
"Trench Warfare": 2515001,
"One Way Room": 2515002,
"You Just Keep Coming Back": 2515003,
"Clarion Call": 2515004,
"Doing things the hard way": 2515005,
"Prize for the Reckless": 2515006,
"The Tower 1": 2515007,
"The Tower 2": 2515008,
"Young Man, It's Worth the Challenge": 2515009,
"The Tantalizing Trinket": 2515010,
"Purest Unobtainium": 2515011,
"Edge Games": 2515012,
"Overworld (Pipe-shaped Segment)": 2515013,
"Overworld (Outside Entanglement Generator)": 2515014,
"Overworld (Left of Ship)": 2515015,
"Overworld (Square Room)": 2515016,
"Overworld (Sad Elephant)": 2515017,
"NPC Trinket": 2515018,
"V": 2515019
}

20
worlds/v6/Options.py Normal file
View File

@ -0,0 +1,20 @@
import typing
from Options import Option, DeathLink, Range
class DoorCost(Range):
"""Amount of Trinkets required to enter Areas. Set to 0 to disable artificial locks."""
range_start = 0
range_end = 3
default = 3
class DeathLinkAmnesty(Range):
"""Amount of Deaths to take before sending a DeathLink signal, for balancing difficulty"""
range_start = 0
range_end = 30
default = 15
v6_options: typing.Dict[str,type(Option)] = {
"DoorCost": DoorCost,
"DeathLink": DeathLink,
"DeathLinkAmnesty": DeathLinkAmnesty
}

45
worlds/v6/Regions.py Normal file
View File

@ -0,0 +1,45 @@
import typing
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
from .Locations import V6Location, location_table
def create_regions(world: MultiWorld, player: int):
regOvr = Region("Menu", RegionType.Generic, "Dimension VVVVVV", player, world)
locOvr_names = ["Overworld (Pipe-shaped Segment)", "Overworld (Left of Ship)", "Overworld (Square Room)", "Overworld (Sad Elephant)",
"It's a Secret to Nobody", "Trench Warfare", "NPC Trinket"]
regOvr.locations += [V6Location(player, loc_name, location_table[loc_name], regOvr) for loc_name in locOvr_names]
world.regions.append(regOvr)
regLab = Region("Laboratory", RegionType.Generic, "Laboratory", player, world)
locLab_names = ["Young Man, It's Worth the Challenge", "Overworld (Outside Entanglement Generator)", "The Tantalizing Trinket", "Purest Unobtainium"]
regLab.locations += [V6Location(player, loc_name, location_table[loc_name], regLab) for loc_name in locLab_names]
world.regions.append(regLab)
regTow = Region("The Tower", RegionType.Generic, "The Tower", player, world)
locTow_names = ["The Tower 1", "The Tower 2"]
regTow.locations += [V6Location(player, loc_name, location_table[loc_name], regTow) for loc_name in locTow_names]
world.regions.append(regTow)
regSp2 = Region("Space Station 2", RegionType.Generic, "Space Station 2", player, world)
locSp2_names = ["One Way Room", "You Just Keep Coming Back", "Clarion Call", "Prize for the Reckless", "Doing things the hard way"]
regSp2.locations += [V6Location(player, loc_name, location_table[loc_name], regSp2) for loc_name in locSp2_names]
world.regions.append(regSp2)
regWrp = Region("Warp Zone", RegionType.Generic, "Warp Zone", player, world)
locWrp_names = ["Edge Games"]
regWrp.locations += [V6Location(player, loc_name, location_table[loc_name], regWrp) for loc_name in locWrp_names]
world.regions.append(regWrp)
regEnd = Region("The Final Level", RegionType.Generic, "The Final Level", player, world)
locEnd_names = ["V"]
regEnd.locations += [V6Location(player, loc_name, location_table[loc_name], regEnd) for loc_name in locEnd_names]
world.regions.append(regEnd)
def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule):
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)
connection = Entrance(player,'', sourceRegion)
connection.access_rule = rule
sourceRegion.exits.append(connection)
connection.connect(targetRegion)

34
worlds/v6/Rules.py Normal file
View File

@ -0,0 +1,34 @@
import typing
from ..generic.Rules import add_rule
from .Regions import connect_regions
def _has_trinket_range(state,player,start,end):
for i in range(start+1,end+1):
if (not state.has("Trinket " + str(i).zfill(2), player)):
return False
return True
def create_npctrinket_rules(world,location,player):
add_rule(location, lambda state: state.can_reach(world.get_region("Laboratory",player),'Region',player) or
state.can_reach(world.get_region("Space Station 2",player),'Region',player))
def set_rules(world,player):
if (world.DoorCost[player].value == 0): pass
connect_regions(world, player, "Menu", "Laboratory", lambda state: _has_trinket_range(state,player,0,world.DoorCost[player].value))
connect_regions(world, player, "Menu", "The Tower", lambda state: _has_trinket_range(state,player,world.DoorCost[player].value,world.DoorCost[player].value*2))
connect_regions(world, player, "Menu", "Space Station 2", lambda state: _has_trinket_range(state,player,world.DoorCost[player].value*2,world.DoorCost[player].value*3))
connect_regions(world, player, "Menu", "Warp Zone", lambda state: _has_trinket_range(state,player,world.DoorCost[player].value*3,world.DoorCost[player].value*4))
connect_regions(world, player, "Menu", "The Final Level", lambda state : state.can_reach("Laboratory",'Region',player) and
state.can_reach("The Tower",'Region',player) and
state.can_reach("Space Station 2",'Region',player) and
state.can_reach("Warp Zone",'Region',player))
connect_regions(world, player, "Laboratory", "Menu", lambda state: True)
connect_regions(world, player, "The Tower", "Menu", lambda state: True)
connect_regions(world, player, "Space Station 2", "Menu", lambda state: True)
connect_regions(world, player, "Warp Zone", "Menu", lambda state: True)
connect_regions(world, player, "The Final Level", "Menu", lambda state: True)
create_npctrinket_rules(world,world.get_location("NPC Trinket",player),player)
world.completion_condition[player] = lambda state: state.can_reach(world.get_region("The Final Level",player),'Region',player)

47
worlds/v6/__init__.py Normal file
View File

@ -0,0 +1,47 @@
import string
from .Items import item_table, V6Item
from .Locations import location_table, V6Location
from .Options import v6_options
from .Rules import set_rules
from .Regions import create_regions
from BaseClasses import Region, RegionType, Entrance, Item, MultiWorld
from ..AutoWorld import World
client_version = 1
class V6World(World):
"""
VVVVVV is a platform game all about exploring one simple mechanical idea - what if you reversed gravity instead of jumping?
""" #Lifted from Store Page
game: str = "VVVVVV"
topology_present = False
item_name_to_id = item_table
location_name_to_id = location_table
data_version = 1
forced_auto_forfeit = False
options = v6_options
def create_regions(self):
create_regions(self.world,self.player)
def set_rules(self):
set_rules(self.world,self.player)
def create_item(self, name: str) -> Item:
item_id = item_table[name]
item = V6Item(name, True, item_id, self.player)
return item
def generate_basic(self):
self.world.itempool += [self.create_item(name) for name in self.item_names]
def fill_slot_data(self):
return {
"DoorCost": self.world.DoorCost[self.player].value,
"DeathLink": self.world.DeathLink[self.player].value,
"DeathLink_Amnesty": self.world.DeathLinkAmnesty[self.player].value
}