Add VVVVVV to Archipelago (#178)
This commit is contained in:
parent
4291912577
commit
344f4afdbd
|
@ -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.
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue