Merge branch 'main' of https://github.com/ArchipelagoMW/Archipelago into main
This commit is contained in:
commit
54cd32872e
|
@ -9,10 +9,10 @@ from collections import Counter
|
|||
import string
|
||||
|
||||
import ModuleUpdate
|
||||
import Utils
|
||||
|
||||
ModuleUpdate.update()
|
||||
|
||||
import Utils
|
||||
from worlds.alttp import Options as LttPOptions
|
||||
from worlds.generic import PlandoItem, PlandoConnection
|
||||
from Utils import parse_yaml, version_tuple, __version__, tuplize_version, get_options
|
||||
|
|
|
@ -35,18 +35,25 @@ def update(yes = False, force = False):
|
|||
if not os.path.exists(path):
|
||||
path = os.path.join(os.path.dirname(__file__), req_file)
|
||||
with open(path) as requirementsfile:
|
||||
requirements = pkg_resources.parse_requirements(requirementsfile)
|
||||
for requirement in requirements:
|
||||
requirement = str(requirement)
|
||||
try:
|
||||
pkg_resources.require(requirement)
|
||||
except pkg_resources.ResolutionError:
|
||||
if not yes:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input(f'Requirement {requirement} is not satisfied, press enter to install it')
|
||||
update_command()
|
||||
return
|
||||
for line in requirementsfile:
|
||||
if line.startswith('https://'):
|
||||
# extract name and version from url
|
||||
url = line.split(';')[0]
|
||||
wheel = line.split('/')[-1]
|
||||
name, version, _ = wheel.split('-',2)
|
||||
line = f'{name}=={version}'
|
||||
requirements = pkg_resources.parse_requirements(line)
|
||||
for requirement in requirements:
|
||||
requirement = str(requirement)
|
||||
try:
|
||||
pkg_resources.require(requirement)
|
||||
except pkg_resources.ResolutionError:
|
||||
if not yes:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input(f'Requirement {requirement} is not satisfied, press enter to install it')
|
||||
update_command()
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -11,6 +11,8 @@ Currently, the following games are supported:
|
|||
* Risk of Rain 2
|
||||
* The Legend of Zelda: Ocarina of Time
|
||||
* Timespinner
|
||||
* Super Metroid
|
||||
* Secret of Evermore
|
||||
|
||||
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
||||
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
||||
|
|
10
SNIClient.py
10
SNIClient.py
|
@ -22,6 +22,7 @@ from NetUtils import *
|
|||
from worlds.alttp import Regions, Shops
|
||||
from worlds.alttp import Items
|
||||
from worlds.alttp.Rom import ROM_PLAYER_LIMIT
|
||||
from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT
|
||||
import Utils
|
||||
from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, gui_enabled, get_base_parser
|
||||
from Patch import GAME_ALTTP, GAME_SM
|
||||
|
@ -156,7 +157,7 @@ class Context(CommonContext):
|
|||
self.killing_player_task = asyncio.create_task(deathlink_kill_player(self))
|
||||
super(Context, self).on_deathlink(data)
|
||||
|
||||
def handle_deathlink_state(self, currently_dead: bool):
|
||||
async def handle_deathlink_state(self, currently_dead: bool):
|
||||
# in this state we only care about triggering a death send
|
||||
if self.death_state == DeathState.alive:
|
||||
if currently_dead:
|
||||
|
@ -935,7 +936,7 @@ async def game_watcher(ctx: Context):
|
|||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||
currently_dead = gamemode[0] in DEATH_MODES
|
||||
ctx.handle_deathlink_state(currently_dead)
|
||||
await ctx.handle_deathlink_state(currently_dead)
|
||||
|
||||
gameend = await snes_read(ctx, SAVEDATA_START + 0x443, 1)
|
||||
game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4)
|
||||
|
@ -1004,7 +1005,7 @@ async def game_watcher(ctx: Context):
|
|||
gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1)
|
||||
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||
currently_dead = gamemode[0] in SM_DEATH_MODES
|
||||
ctx.handle_deathlink_state(currently_dead)
|
||||
await ctx.handle_deathlink_state(currently_dead)
|
||||
if gamemode is not None and gamemode[0] in SM_ENDGAME_MODES:
|
||||
if not ctx.finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
|
@ -1048,7 +1049,7 @@ async def game_watcher(ctx: Context):
|
|||
item = ctx.items_received[itemOutPtr]
|
||||
itemId = item.item - items_start_id
|
||||
|
||||
playerID = (item.player-1) if item.player != 0 else (len(ctx.player_names)-1)
|
||||
playerID = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes([playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, (itemId >> 8) & 0xFF]))
|
||||
itemOutPtr += 1
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x602, bytes([itemOutPtr & 0xFF, (itemOutPtr >> 8) & 0xFF]))
|
||||
|
@ -1057,7 +1058,6 @@ async def game_watcher(ctx: Context):
|
|||
ctx.location_name_getter(item.location), itemOutPtr, len(ctx.items_received)))
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
|
||||
async def run_game(romfile):
|
||||
auto_start = Utils.get_options()["lttp_options"].get("rom_start", True)
|
||||
if auto_start is True:
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Secret of Evermore
|
||||
|
||||
## Where is the settings page?
|
||||
The player settings page for this game is located <a href="../player-settings">here</a>. It contains all options
|
||||
necessary to configure and export a config file.
|
||||
|
||||
## What does randomization do to this game?
|
||||
Items which would normally be acquired throughout the game have been moved around! Progression logic remains,
|
||||
so the game is always able to be completed. However, because of the item shuffle, the player may need to access certain
|
||||
areas before they would in the vanilla game. For example, the Windwalker (flying machine) is accessible as soon as any
|
||||
weapon is obtained.
|
||||
|
||||
Additional help can be found in the [guide](https://github.com/black-sliver/evermizer/blob/feat-mw/guide.md).
|
||||
|
||||
## What items and locations get shuffled?
|
||||
All gourds/chests/pots, boss drops and alchemists are shuffled. Alchemy ingredients, sniff spot items, call bead spells
|
||||
and the dog can be randomized using yaml options.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
Any of the items which can be shuffled may also be placed in another player's world.
|
||||
Specific items can be limited to your own world using plando.
|
||||
|
||||
## What does another world's item look like in Secret of Evermore?
|
||||
Secret of Evermore will display "Sent an Item". Check the client output if you want to know which.
|
||||
|
||||
## What happens when the player receives an item?
|
||||
When the player receives an item, a popup will appear to show which item was received. Items won't be recieved while a
|
||||
script is active such as when visiting Nobilia Market or during most Boss Fights. Once all scripts have ended, items
|
||||
will be recieved.
|
|
@ -0,0 +1,116 @@
|
|||
# Secret of Evermore Setup Guide
|
||||
|
||||
## Required Software
|
||||
- [SNI](https://github.com/alttpo/sni/releases) (included in Archipelago if already installed)
|
||||
- Hardware or software capable of loading and playing SNES ROM files
|
||||
- An emulator capable of connecting to SNI with ROM access
|
||||
- [snes9x-rr win32.zip](https://github.com/gocha/snes9x-rr/releases) +
|
||||
[socket.dll](http://www.nyo.fr/~skarsnik/socket.dll) +
|
||||
[connector.lua](https://raw.githubusercontent.com/alttpo/sni/main/lua/Connector.lua)
|
||||
- or [BizHawk](http://tasvideos.org/BizHawk.html)
|
||||
- or [bsnes-plus-nwa](https://github.com/black-sliver/bsnes-plus)
|
||||
- Or SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware
|
||||
- Your Secret of Evermore US ROM file, probably named `Secret of Evermore (USA).sfc`
|
||||
|
||||
## Create a Config (.yaml) File
|
||||
|
||||
### What is a config file and why do I need one?
|
||||
Your config file contains a set of configuration options which provide the generator with information about how
|
||||
it should generate your game. Each player of a multiworld will provide their own config file. This setup allows
|
||||
each player to enjoy an experience customized for their taste, and different players in the same multiworld
|
||||
can all have different options.
|
||||
|
||||
### Where do I get a config file?
|
||||
The [Player Settings](/games/Secret%20of%20Evermore/player-settings) page on the website allows you to configure your
|
||||
personal settings and export a config file from them.
|
||||
|
||||
### Verifying your config file
|
||||
If you would like to validate your config file to make sure it works, you may do so on the
|
||||
[YAML Validator](/mysterycheck) page.
|
||||
|
||||
## Generating a Single-Player Game
|
||||
Stand-alone "Evermizer" has a way of balancing single-player games, but may not always be on par feature-wise.
|
||||
Head over to [evermizer.com](https://evermizer.com) if you want to try the official stand-alone, otherwise read below.
|
||||
|
||||
1. Navigate to the [Player Settings](/games/Secret%20of%20Evermore/player-settings) page, configure your options, and
|
||||
click the "Generate Game" button.
|
||||
2. You will be presented with a "Seed Info" page.
|
||||
3. Click the "Create New Room" link.
|
||||
4. You will be presented with a server page, from which you can download your patch file.
|
||||
5. Run your patch file through [apbpatch](https://evermizer.com/apbpatch) and load it in your emulator or console.
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
|
||||
### Obtain your patch file and create your ROM
|
||||
When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that
|
||||
is done, the host will provide you with either a link to download your patch file, or with a zip file containing
|
||||
everyone's patch files. Your patch file should have a `.apsoe` extension.
|
||||
|
||||
Put your patch file on your desktop or somewhere convenient, open [apbpatch](https://evermizer.com/apbpatch) and
|
||||
generate your ROM from it. Load the ROM file in your emulator or console.
|
||||
|
||||
### Connect to SNI
|
||||
|
||||
#### With an emulator
|
||||
Start SNI either from the Archipelago install folder or the stand-alone version.
|
||||
If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall.
|
||||
|
||||
##### snes9x-rr
|
||||
1. Load your ROM file if it hasn't already been loaded.
|
||||
2. Click on the File menu and hover on **Lua Scripting**
|
||||
3. Click on **New Lua Script Window...**
|
||||
4. In the new window, click **Browse...**
|
||||
5. Select the `Connector.lua` file you downloaded above
|
||||
6. If the script window complains about missing `socket.dll` make sure the DLL is in snes9x or the lua file's directory.
|
||||
|
||||
##### BizHawk
|
||||
1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following
|
||||
these menu options:
|
||||
`Config --> Cores --> SNES --> BSNES`
|
||||
Once you have changed the loaded core, you must restart BizHawk.
|
||||
2. Load your ROM file if it hasn't already been loaded.
|
||||
3. Click on the Tools menu and click on **Lua Console**
|
||||
4. Click the button to open a new Lua script.
|
||||
5. Select the `Connector.lua` file you downloaded above
|
||||
|
||||
##### bsnes-plus-nwa
|
||||
This should automatically connect to SNI.
|
||||
If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall.
|
||||
|
||||
#### With hardware
|
||||
This guide assumes you have downloaded the correct firmware for your device. If you have not
|
||||
done so already, please do this now. SD2SNES and FXPak Pro users may download the appropriate firmware
|
||||
[here](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find helpful information
|
||||
[on this page](http://usb2snes.com/#supported-platforms).
|
||||
|
||||
1. Copy the ROM file to your SD card.
|
||||
2. Load the ROM file from the menu.
|
||||
|
||||
### Open the client
|
||||
Open [ap-soeclient](http://evermizer.com/apclient) in a modern browser. Do not switch tabs, open it in a new window
|
||||
if you want to use the browser while playing. Do not minimize the window with the client.
|
||||
|
||||
The client should automatically connect to SNI, the "SNES" status should change to green.
|
||||
|
||||
### Connect to the Archipelago Server
|
||||
Enter `/connect server:port` in the client's command prompt and press enter. You'll find `server:port` on the same page
|
||||
that had the patch file.
|
||||
|
||||
### Play the game
|
||||
When the game is loaded but not yet past the intro cutscene, the "Game" status is yellow. When the client shows "AP" as
|
||||
green and "Game" as yellow, you're ready to play. The intro can be skipped pressing the START button and "Game" should
|
||||
change to green. Congratulations on successfully joining a multiworld game!
|
||||
|
||||
## Hosting a MultiWorld game
|
||||
The recommended way to host a game is to use our [hosting service](/generate). The process is relatively simple:
|
||||
|
||||
1. Collect config files from your players.
|
||||
2. Create a zip file containing your players' config files.
|
||||
3. Upload that zip file to the website linked above.
|
||||
4. Wait a moment while the seed is generated.
|
||||
5. When the seed is generated, you will be redirected to a "Seed Info" page.
|
||||
6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players,
|
||||
so they may download their patch files from there.
|
||||
7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all
|
||||
players in the game. Any observers may also be given the link to this page.
|
||||
8. Once all players have joined, you may begin playing.
|
|
@ -290,5 +290,24 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"gameTitle": "Secret of Evermore",
|
||||
"tutorials": [
|
||||
{
|
||||
"name": "Multiworld Setup Guide",
|
||||
"description": "A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related software.",
|
||||
"files": [
|
||||
{
|
||||
"language": "English",
|
||||
"filename": "secret-of-evermore/multiworld_en.md",
|
||||
"link": "secret-of-evermore/multiworld/en",
|
||||
"authors": [
|
||||
"Black Sliver"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -108,4 +108,6 @@ minecraft_options:
|
|||
oot_options:
|
||||
# File name of the OoT v1.0 ROM
|
||||
rom_file: "The Legend of Zelda - Ocarina of Time.z64"
|
||||
rom_file: "The Legend of Zelda - Ocarina of Time.z64"
|
||||
soe_options:
|
||||
# File name of the SoE US ROM
|
||||
rom_file: "Secret of Evermore (USA).sfc"
|
||||
|
|
|
@ -34,7 +34,6 @@ SignTool= signtool
|
|||
LicenseFile= LICENSE
|
||||
WizardStyle= modern
|
||||
SetupLogging=yes
|
||||
MinVersion=6.3.9200
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
@ -51,11 +50,15 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom
|
|||
[Components]
|
||||
Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed
|
||||
Name: "generator"; Description: "Generator"; Types: full hosting
|
||||
Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728
|
||||
Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728
|
||||
Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680
|
||||
Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296
|
||||
Name: "server"; Description: "Server"; Types: full hosting
|
||||
Name: "client"; Description: "Clients"; Types: full playing
|
||||
Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing
|
||||
Name: "client/sni"; Description: "SNI Client"; Types: full playing
|
||||
Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Setup"; Types: full playing
|
||||
Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing
|
||||
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
||||
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
|
||||
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
|
||||
|
@ -64,18 +67,20 @@ Name: "client/text"; Description: "Text, to !command and chat"; Types: full play
|
|||
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
|
||||
|
||||
[Files]
|
||||
Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/lttp or generator/lttp
|
||||
Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/sni/lttp or generator/lttp
|
||||
Source: "{code:GetSMROMPath}"; DestDir: "{app}"; DestName: "Super Metroid (JU).sfc"; Flags: external; Components: client/sni/sm or generator/sm
|
||||
Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe
|
||||
Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: generator/oot
|
||||
Source: "{#sourcepath}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/lttp
|
||||
Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni
|
||||
Source: "{#sourcepath}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp
|
||||
|
||||
Source: "{#sourcepath}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator
|
||||
Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server
|
||||
Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio
|
||||
Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/text
|
||||
Source: "{#sourcepath}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp
|
||||
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp or generator/lttp
|
||||
Source: "{#sourcepath}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni
|
||||
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp
|
||||
Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft
|
||||
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||
|
||||
|
@ -85,30 +90,38 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt
|
|||
[Icons]
|
||||
Name: "{group}\{#MyAppName} Folder"; Filename: "{app}";
|
||||
Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server
|
||||
Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/lttp
|
||||
Name: "{group}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp
|
||||
Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text
|
||||
Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Components: client/sni
|
||||
Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio
|
||||
Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft
|
||||
Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon
|
||||
Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server
|
||||
Name: "{commondesktop}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Tasks: desktopicon; Components: client/lttp
|
||||
Name: "{commondesktop}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Tasks: desktopicon; Components: client/sni
|
||||
Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio
|
||||
|
||||
[Run]
|
||||
|
||||
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..."
|
||||
Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/lttp or generator/lttp
|
||||
Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/sni/lttp or generator/lttp
|
||||
Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.jar"" --installServer ""{app}\Minecraft Forge server"""; Flags: runhidden; Check: IsForgeNeeded(); StatusMsg: "Installing Forge Server..."; Components: client/minecraft
|
||||
|
||||
[UninstallDelete]
|
||||
Type: dirifempty; Name: "{app}"
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\ArchipelagoLttPClient.exe"
|
||||
|
||||
[Registry]
|
||||
|
||||
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoLttPClient.exe,0"; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoLttPClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni
|
||||
|
||||
Root: HKCR; Subkey: ".apm3"; ValueData: "{#MyAppName}smpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Archipelago Super Metroid Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni
|
||||
|
||||
Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/minecraft
|
||||
Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/minecraft
|
||||
|
@ -190,38 +203,53 @@ begin
|
|||
ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
|
||||
end;
|
||||
|
||||
var ROMFilePage: TInputFileWizardPage;
|
||||
var R : longint;
|
||||
var rom: string;
|
||||
|
||||
var lttprom: string;
|
||||
var LttPROMFilePage: TInputFileWizardPage;
|
||||
|
||||
var smrom: string;
|
||||
var SMRomFilePage: TInputFileWizardPage;
|
||||
|
||||
var soerom: string;
|
||||
var SoERomFilePage: TInputFileWizardPage;
|
||||
|
||||
var ootrom: string;
|
||||
var OoTROMFilePage: TInputFileWizardPage;
|
||||
|
||||
var MinecraftDownloadPage: TDownloadWizardPage;
|
||||
|
||||
procedure AddRomPage();
|
||||
function CheckRom(name: string; hash: string): string;
|
||||
var rom: string;
|
||||
begin
|
||||
rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue());
|
||||
log('Handling ' + name)
|
||||
rom := FileSearch(name, WizardDirValue());
|
||||
if Length(rom) > 0 then
|
||||
begin
|
||||
log('existing ROM found');
|
||||
log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173')));
|
||||
if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then
|
||||
log(IntToStr(CompareStr(GetMD5OfFile(rom), hash)));
|
||||
if CompareStr(GetMD5OfFile(rom), hash) = 0 then
|
||||
begin
|
||||
log('existing ROM verified');
|
||||
Result := rom;
|
||||
exit;
|
||||
end;
|
||||
log('existing ROM failed verification');
|
||||
end;
|
||||
rom := ''
|
||||
ROMFilePage :=
|
||||
end;
|
||||
|
||||
function AddRomPage(name: string): TInputFileWizardPage;
|
||||
begin
|
||||
Result :=
|
||||
CreateInputFilePage(
|
||||
wpSelectComponents,
|
||||
'Select ROM File',
|
||||
'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?',
|
||||
'Where is your ' + name + ' located?',
|
||||
'Select the file, then click Next.');
|
||||
|
||||
ROMFilePage.Add(
|
||||
Result.Add(
|
||||
'Location of ROM file:',
|
||||
'SNES ROM files|*.sfc|All files|*.*',
|
||||
'SNES ROM files|*.sfc;*.smc|All files|*.*',
|
||||
'.sfc');
|
||||
end;
|
||||
|
||||
|
@ -291,34 +319,50 @@ begin
|
|||
Result := True;
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
AddOoTRomPage();
|
||||
AddRomPage();
|
||||
AddMinecraftDownloads();
|
||||
end;
|
||||
|
||||
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (assigned(ROMFilePage)) and (PageID = ROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('client/lttp') or WizardIsComponentSelected('generator/lttp'));
|
||||
if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/oot'));
|
||||
end;
|
||||
|
||||
function GetROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(rom) > 0 then
|
||||
Result := rom
|
||||
else if Assigned(RomFilePage) then
|
||||
if Length(lttprom) > 0 then
|
||||
Result := lttprom
|
||||
else if Assigned(LttPRomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173')
|
||||
R := CompareStr(GetMD5OfFile(LttPROMFilePage.Values[0]), '03a63945398191337e896e5771f77173')
|
||||
if R <> 0 then
|
||||
MsgBox('ALttP ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := ROMFilePage.Values[0]
|
||||
|
||||
Result := LttPROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function GetSMROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(smrom) > 0 then
|
||||
Result := smrom
|
||||
else if Assigned(SMRomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(SMROMFilePage.Values[0]), '21f3e98df4780ee1c667b84e57d88675')
|
||||
if R <> 0 then
|
||||
MsgBox('Super Metroid ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := SMROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function GetSoEROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(soerom) > 0 then
|
||||
Result := soerom
|
||||
else if Assigned(SoERomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(SoEROMFilePage.Values[0]), '6e9c94511d04fac6e0a1e582c170be3a')
|
||||
log(GetMD5OfFile(SoEROMFilePage.Values[0]))
|
||||
if R <> 0 then
|
||||
MsgBox('Secret of Evermore ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := SoEROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
|
@ -333,9 +377,42 @@ begin
|
|||
R := CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '5bd1fe107bf8106b2ab6650abecd54d6') * CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '6697768a7a7df2dd27a692a2638ea90b') * CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '05f0f3ebacbc8df9243b6148ffe4792f');
|
||||
if R <> 0 then
|
||||
MsgBox('OoT ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
|
||||
Result := OoTROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
AddOoTRomPage();
|
||||
|
||||
lttprom := CheckRom('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', '03a63945398191337e896e5771f77173');
|
||||
if Length(lttprom) = 0 then
|
||||
LttPROMFilePage:= AddRomPage('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc');
|
||||
|
||||
smrom := CheckRom('Super Metroid (JU).sfc', '21f3e98df4780ee1c667b84e57d88675');
|
||||
if Length(smrom) = 0 then
|
||||
SMRomFilePage:= AddRomPage('Super Metroid (JU).sfc');
|
||||
|
||||
soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a');
|
||||
if Length(soerom) = 0 then
|
||||
SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc');
|
||||
|
||||
AddMinecraftDownloads();
|
||||
end;
|
||||
|
||||
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (assigned(LttPROMFilePage)) and (PageID = LttPROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp'));
|
||||
if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm'));
|
||||
if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/soe'));
|
||||
if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/oot'));
|
||||
end;
|
|
@ -50,11 +50,15 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom
|
|||
[Components]
|
||||
Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed
|
||||
Name: "generator"; Description: "Generator"; Types: full hosting
|
||||
Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728
|
||||
Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728
|
||||
Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680
|
||||
Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296
|
||||
Name: "server"; Description: "Server"; Types: full hosting
|
||||
Name: "client"; Description: "Clients"; Types: full playing
|
||||
Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing
|
||||
Name: "client/sni"; Description: "SNI Client"; Types: full playing
|
||||
Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Setup"; Types: full playing
|
||||
Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing
|
||||
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
||||
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
|
||||
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
|
||||
|
@ -63,18 +67,20 @@ Name: "client/text"; Description: "Text, to !command and chat"; Types: full play
|
|||
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
|
||||
|
||||
[Files]
|
||||
Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/lttp or generator/lttp
|
||||
Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/sni/lttp or generator/lttp
|
||||
Source: "{code:GetSMROMPath}"; DestDir: "{app}"; DestName: "Super Metroid (JU).sfc"; Flags: external; Components: client/sni/sm or generator/sm
|
||||
Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe
|
||||
Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: generator/oot
|
||||
Source: "{#sourcepath}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/lttp
|
||||
Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni
|
||||
Source: "{#sourcepath}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp
|
||||
|
||||
Source: "{#sourcepath}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator
|
||||
Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server
|
||||
Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio
|
||||
Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/text
|
||||
Source: "{#sourcepath}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp
|
||||
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp or generator/lttp
|
||||
Source: "{#sourcepath}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni
|
||||
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp
|
||||
Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft
|
||||
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||
|
||||
|
@ -84,30 +90,38 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt
|
|||
[Icons]
|
||||
Name: "{group}\{#MyAppName} Folder"; Filename: "{app}";
|
||||
Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server
|
||||
Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/lttp
|
||||
Name: "{group}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp
|
||||
Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text
|
||||
Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Components: client/sni
|
||||
Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio
|
||||
Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft
|
||||
Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon
|
||||
Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server
|
||||
Name: "{commondesktop}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Tasks: desktopicon; Components: client/lttp
|
||||
Name: "{commondesktop}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Tasks: desktopicon; Components: client/sni
|
||||
Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio
|
||||
|
||||
[Run]
|
||||
|
||||
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..."
|
||||
Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/lttp or generator/lttp
|
||||
Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/sni/lttp or generator/lttp
|
||||
Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.jar"" --installServer ""{app}\Minecraft Forge server"""; Flags: runhidden; Check: IsForgeNeeded(); StatusMsg: "Installing Forge Server..."; Components: client/minecraft
|
||||
|
||||
[UninstallDelete]
|
||||
Type: dirifempty; Name: "{app}"
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\ArchipelagoLttPClient.exe"
|
||||
|
||||
[Registry]
|
||||
|
||||
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoLttPClient.exe,0"; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoLttPClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/lttp
|
||||
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni
|
||||
|
||||
Root: HKCR; Subkey: ".apm3"; ValueData: "{#MyAppName}smpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Archipelago Super Metroid Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni
|
||||
|
||||
Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/minecraft
|
||||
Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/minecraft
|
||||
|
@ -189,38 +203,53 @@ begin
|
|||
ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
|
||||
end;
|
||||
|
||||
var ROMFilePage: TInputFileWizardPage;
|
||||
var R : longint;
|
||||
var rom: string;
|
||||
|
||||
var lttprom: string;
|
||||
var LttPROMFilePage: TInputFileWizardPage;
|
||||
|
||||
var smrom: string;
|
||||
var SMRomFilePage: TInputFileWizardPage;
|
||||
|
||||
var soerom: string;
|
||||
var SoERomFilePage: TInputFileWizardPage;
|
||||
|
||||
var ootrom: string;
|
||||
var OoTROMFilePage: TInputFileWizardPage;
|
||||
|
||||
var MinecraftDownloadPage: TDownloadWizardPage;
|
||||
|
||||
procedure AddRomPage();
|
||||
function CheckRom(name: string; hash: string): string;
|
||||
var rom: string;
|
||||
begin
|
||||
rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue());
|
||||
log('Handling ' + name)
|
||||
rom := FileSearch(name, WizardDirValue());
|
||||
if Length(rom) > 0 then
|
||||
begin
|
||||
log('existing ROM found');
|
||||
log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173')));
|
||||
if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then
|
||||
log(IntToStr(CompareStr(GetMD5OfFile(rom), hash)));
|
||||
if CompareStr(GetMD5OfFile(rom), hash) = 0 then
|
||||
begin
|
||||
log('existing ROM verified');
|
||||
Result := rom;
|
||||
exit;
|
||||
end;
|
||||
log('existing ROM failed verification');
|
||||
end;
|
||||
rom := ''
|
||||
ROMFilePage :=
|
||||
end;
|
||||
|
||||
function AddRomPage(name: string): TInputFileWizardPage;
|
||||
begin
|
||||
Result :=
|
||||
CreateInputFilePage(
|
||||
wpSelectComponents,
|
||||
'Select ROM File',
|
||||
'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?',
|
||||
'Where is your ' + name + ' located?',
|
||||
'Select the file, then click Next.');
|
||||
|
||||
ROMFilePage.Add(
|
||||
Result.Add(
|
||||
'Location of ROM file:',
|
||||
'SNES ROM files|*.sfc|All files|*.*',
|
||||
'SNES ROM files|*.sfc;*.smc|All files|*.*',
|
||||
'.sfc');
|
||||
end;
|
||||
|
||||
|
@ -290,34 +319,50 @@ begin
|
|||
Result := True;
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
AddOoTRomPage();
|
||||
AddRomPage();
|
||||
AddMinecraftDownloads();
|
||||
end;
|
||||
|
||||
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (assigned(ROMFilePage)) and (PageID = ROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('client/lttp') or WizardIsComponentSelected('generator/lttp'));
|
||||
if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/oot'));
|
||||
end;
|
||||
|
||||
function GetROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(rom) > 0 then
|
||||
Result := rom
|
||||
else if Assigned(RomFilePage) then
|
||||
if Length(lttprom) > 0 then
|
||||
Result := lttprom
|
||||
else if Assigned(LttPRomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173')
|
||||
R := CompareStr(GetMD5OfFile(LttPROMFilePage.Values[0]), '03a63945398191337e896e5771f77173')
|
||||
if R <> 0 then
|
||||
MsgBox('ALttP ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := ROMFilePage.Values[0]
|
||||
Result := LttPROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function GetSMROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(smrom) > 0 then
|
||||
Result := smrom
|
||||
else if Assigned(SMRomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(SMROMFilePage.Values[0]), '21f3e98df4780ee1c667b84e57d88675')
|
||||
if R <> 0 then
|
||||
MsgBox('Super Metroid ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := SMROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function GetSoEROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(soerom) > 0 then
|
||||
Result := soerom
|
||||
else if Assigned(SoERomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(SoEROMFilePage.Values[0]), '6e9c94511d04fac6e0a1e582c170be3a')
|
||||
log(GetMD5OfFile(SoEROMFilePage.Values[0]))
|
||||
if R <> 0 then
|
||||
MsgBox('Secret of Evermore ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := SoEROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
|
@ -338,3 +383,36 @@ begin
|
|||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
AddOoTRomPage();
|
||||
|
||||
lttprom := CheckRom('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', '03a63945398191337e896e5771f77173');
|
||||
if Length(lttprom) = 0 then
|
||||
LttPROMFilePage:= AddRomPage('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc');
|
||||
|
||||
smrom := CheckRom('Super Metroid (JU).sfc', '21f3e98df4780ee1c667b84e57d88675');
|
||||
if Length(smrom) = 0 then
|
||||
SMRomFilePage:= AddRomPage('Super Metroid (JU).sfc');
|
||||
|
||||
soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a');
|
||||
if Length(soerom) = 0 then
|
||||
SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc');
|
||||
|
||||
AddMinecraftDownloads();
|
||||
end;
|
||||
|
||||
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (assigned(LttPROMFilePage)) and (PageID = LttPROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp'));
|
||||
if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm'));
|
||||
if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/soe'));
|
||||
if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/oot'));
|
||||
end;
|
|
@ -85,7 +85,8 @@ class CustomTechnology(Technology):
|
|||
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
|
||||
ingredients = origin.ingredients & allowed_packs
|
||||
military_allowed = "military-science-pack" in allowed_packs \
|
||||
and (ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
or origin.name == "rocket-silo")
|
||||
self.player = player
|
||||
if origin.name not in world.worlds[player].static_nodes:
|
||||
if military_allowed:
|
||||
|
|
|
@ -2,6 +2,7 @@ import Utils
|
|||
from Patch import read_rom
|
||||
|
||||
JAP10HASH = '21f3e98df4780ee1c667b84e57d88675'
|
||||
ROM_PLAYER_LIMIT = 255
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
@ -27,4 +28,4 @@ def get_base_rom_path(file_name: str = "") -> str:
|
|||
file_name = options["sm_options"]["rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
file_name = Utils.local_path(file_name)
|
||||
return file_name
|
||||
return file_name
|
||||
|
|
|
@ -11,7 +11,7 @@ from .Items import lookup_name_to_id as items_lookup_name_to_id
|
|||
from .Regions import create_regions
|
||||
from .Rules import set_rules, add_entrance_rule
|
||||
from .Options import sm_options
|
||||
from .Rom import get_base_rom_path
|
||||
from .Rom import get_base_rom_path, ROM_PLAYER_LIMIT
|
||||
import Utils
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, RegionType, CollectionState
|
||||
|
@ -242,7 +242,7 @@ class SMWorld(World):
|
|||
idx += 1
|
||||
(w0, w1) = self.getWord(0 if itemLoc.item.player == self.player else 1)
|
||||
(w2, w3) = self.getWord(itemId)
|
||||
(w4, w5) = self.getWord(itemLoc.item.player - 1)
|
||||
(w4, w5) = self.getWord(itemLoc.item.player if itemLoc.item.player <= ROM_PLAYER_LIMIT else 0)
|
||||
(w6, w7) = self.getWord(0 if itemLoc.item.advancement else 1)
|
||||
multiWorldLocations[0x1C6000 + locationsDict[itemLoc.name].Id*8] = [w0, w1, w2, w3, w4, w5, w6, w7]
|
||||
|
||||
|
@ -268,9 +268,10 @@ class SMWorld(World):
|
|||
romPatcher.applyIPSPatchDict(patchDict)
|
||||
|
||||
playerNames = {}
|
||||
for p in range(1, self.world.players + 1):
|
||||
playerNames[0x1C5000 + (p - 1) * 16] = self.world.player_name[p][:16].upper().center(16).encode()
|
||||
playerNames[0x1C5000 + (self.world.players) * 16] = "Archipelago".upper().center(16).encode()
|
||||
playerNames[0x1C5000] = "Archipelago".upper().center(16).encode()
|
||||
for p in range(1, min(self.world.players, ROM_PLAYER_LIMIT) + 1):
|
||||
playerNames[0x1C5000 + p * 16] = self.world.player_name[p][:16].upper().center(16).encode()
|
||||
|
||||
|
||||
romPatcher.applyIPSPatch('PlayerName', { 'PlayerName': playerNames })
|
||||
|
||||
|
|
|
@ -573,12 +573,12 @@ class RomPatcher:
|
|||
self.writeCreditsStringBig(address, line, top=False)
|
||||
address += 0x80
|
||||
|
||||
value = " "+settings.progSpeed.upper()
|
||||
value = " "+"NA" # settings.progSpeed.upper()
|
||||
line = " PROGRESSION SPEED ....%s " % value.rjust(8, '.')
|
||||
self.writeCreditsString(address, 0x04, line)
|
||||
address += 0x40
|
||||
|
||||
line = " PROGRESSION DIFFICULTY %s " % settings.progDiff.upper()
|
||||
line = " PROGRESSION DIFFICULTY %s " % value.rjust(7, '.') # settings.progDiff.upper()
|
||||
self.writeCreditsString(address, 0x04, line)
|
||||
address += 0x80 # skip item distrib title
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
dump.py
|
||||
pyevermizer
|
||||
.pyevermizer
|
|
@ -0,0 +1,50 @@
|
|||
from BaseClasses import MultiWorld
|
||||
from ..AutoWorld import LogicMixin
|
||||
from typing import Set
|
||||
# TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early?
|
||||
|
||||
from . import pyevermizer
|
||||
|
||||
# TODO: resolve/flatten/expand rules to get rid of recursion below where possible
|
||||
# Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items)
|
||||
rules = [rule for rule in pyevermizer.get_logic() if len(rule.provides) > 0]
|
||||
# Logic.items are all items excluding non-progression items and duplicates
|
||||
item_names: Set[str] = set()
|
||||
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items())
|
||||
if item.name not in item_names and not item_names.add(item.name)]
|
||||
|
||||
|
||||
# when this module is loaded, this mixin will extend BaseClasses.CollectionState
|
||||
class SecretOfEvermoreLogic(LogicMixin):
|
||||
def _soe_count(self, progress: int, world: MultiWorld, player: int, max_count: int = 0) -> int:
|
||||
"""
|
||||
Returns reached count of one of evermizer's progress steps based on collected items.
|
||||
i.e. returns 0-3 for P_DE based on items providing CHECK_BOSS,DIAMOND_EYE_DROP
|
||||
"""
|
||||
n = 0
|
||||
for item in items:
|
||||
for pvd in item.provides:
|
||||
if pvd[1] == progress:
|
||||
if self.has(item.name, player):
|
||||
n += self.item_count(item.name, player) * pvd[0]
|
||||
if n >= max_count > 0:
|
||||
return n
|
||||
for rule in rules:
|
||||
for pvd in rule.provides:
|
||||
if pvd[1] == progress and pvd[0] > 0:
|
||||
has = True
|
||||
for req in rule.requires:
|
||||
if not self._soe_has(req[1], world, player, req[0]):
|
||||
has = False
|
||||
break
|
||||
if has:
|
||||
n += pvd[0]
|
||||
if n >= max_count > 0:
|
||||
return n
|
||||
return n
|
||||
|
||||
def _soe_has(self, progress: int, world: MultiWorld, player: int, count: int = 1) -> bool:
|
||||
"""
|
||||
Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE
|
||||
"""
|
||||
return self._soe_count(progress, world, player, count) >= count
|
|
@ -0,0 +1,154 @@
|
|||
import typing
|
||||
from Options import Option, Range, Choice, Toggle, DefaultOnToggle
|
||||
|
||||
|
||||
class EvermizerFlags:
|
||||
flags: typing.List[str]
|
||||
|
||||
def to_flag(self) -> str:
|
||||
return self.flags[self.value]
|
||||
|
||||
|
||||
class EvermizerFlag:
|
||||
flag: str
|
||||
|
||||
def to_flag(self) -> str:
|
||||
return self.flag if self.value != self.default else ''
|
||||
|
||||
|
||||
class OffOnChaosChoice(Choice):
|
||||
option_off = 0
|
||||
option_on = 1
|
||||
option_chaos = 2
|
||||
alias_false = 0
|
||||
alias_true = 1
|
||||
|
||||
|
||||
class Difficulty(EvermizerFlags, Choice):
|
||||
"""Changes relative spell cost and stuff"""
|
||||
displayname = "Difficulty"
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
option_chaos = 3 # random is reserved pre 0.2
|
||||
default = 1
|
||||
flags = ['e', 'n', 'h', 'x']
|
||||
|
||||
|
||||
class MoneyModifier(Range):
|
||||
"""Money multiplier in %"""
|
||||
displayname = "Money Modifier"
|
||||
range_start = 1
|
||||
range_end = 2500
|
||||
default = 200
|
||||
|
||||
|
||||
class ExpModifier(Range):
|
||||
"""EXP multiplier for Weapons, Characters and Spells in %"""
|
||||
displayname = "Exp Modifier"
|
||||
range_start = 1
|
||||
range_end = 2500
|
||||
default = 200
|
||||
|
||||
|
||||
class FixSequence(EvermizerFlag, DefaultOnToggle):
|
||||
"""Fix some sequence breaks"""
|
||||
displayname = "Fix Sequence"
|
||||
flag = '1'
|
||||
|
||||
|
||||
class FixCheats(EvermizerFlag, DefaultOnToggle):
|
||||
"""Fix cheats left in by the devs (not desert skip)"""
|
||||
displayname = "Fix Cheats"
|
||||
flag = '2'
|
||||
|
||||
|
||||
class FixInfiniteAmmo(EvermizerFlag, Toggle):
|
||||
"""Fix infinite ammo glitch"""
|
||||
displayname = "Fix Infinite Ammo"
|
||||
flag = '5'
|
||||
|
||||
|
||||
class FixAtlasGlitch(EvermizerFlag, Toggle):
|
||||
"""Fix atlas underflowing stats"""
|
||||
displayname = "Fix Atlas Glitch"
|
||||
flag = '6'
|
||||
|
||||
|
||||
class FixWingsGlitch(EvermizerFlag, Toggle):
|
||||
"""Fix wings making you invincible in some areas"""
|
||||
displayname = "Fix Wings Glitch"
|
||||
flag = '7'
|
||||
|
||||
|
||||
class ShorterDialogs(EvermizerFlag, Toggle):
|
||||
"""Cuts some dialogs"""
|
||||
displayname = "Shorter Dialogs"
|
||||
flag = '9'
|
||||
|
||||
|
||||
class ShortBossRush(EvermizerFlag, Toggle):
|
||||
"""Start boss rush at Magmar, cut HP in half"""
|
||||
displayname = "Short Boss Rush"
|
||||
flag = 'f'
|
||||
|
||||
|
||||
class Ingredienizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""Shuffles or randomizes spell ingredients"""
|
||||
displayname = "Ingredienizer"
|
||||
default = 1
|
||||
flags = ['i', '', 'I']
|
||||
|
||||
|
||||
class Sniffamizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""Shuffles or randomizes drops in sniff locations"""
|
||||
displayname = "Sniffamizer"
|
||||
default = 1
|
||||
flags = ['s', '', 'S']
|
||||
|
||||
|
||||
class Callbeadamizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""Shuffles call bead characters or spells"""
|
||||
displayname = "Callbeadamizer"
|
||||
default = 1
|
||||
flags = ['c', '', 'C']
|
||||
|
||||
|
||||
class Musicmizer(EvermizerFlag, Toggle):
|
||||
"""Randomize music for some rooms"""
|
||||
displayname = "Musicmizer"
|
||||
flag = 'm'
|
||||
|
||||
|
||||
class Doggomizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""On shuffles dog per act, Chaos randomizes dog per screen, Pupdunk gives you Everpupper everywhere"""
|
||||
displayname = "Doggomizer"
|
||||
option_pupdunk = 3
|
||||
default = 0
|
||||
flags = ['', 'd', 'D', 'p']
|
||||
|
||||
|
||||
class TurdoMode(EvermizerFlag, Toggle):
|
||||
"""Replace offensive spells by Turd Balls with varying strength and make weapons weak"""
|
||||
displayname = "Turdo Mode"
|
||||
flag = 't'
|
||||
|
||||
|
||||
soe_options: typing.Dict[str, type(Option)] = {
|
||||
"difficulty": Difficulty,
|
||||
"money_modifier": MoneyModifier,
|
||||
"exp_modifier": ExpModifier,
|
||||
"fix_sequence": FixSequence,
|
||||
"fix_cheats": FixCheats,
|
||||
"fix_infinite_ammo": FixInfiniteAmmo,
|
||||
"fix_atlas_glitch": FixAtlasGlitch,
|
||||
"fix_wings_glitch": FixWingsGlitch,
|
||||
"shorter_dialogs": ShorterDialogs,
|
||||
"short_boss_rush": ShortBossRush,
|
||||
"ingredienizer": Ingredienizer,
|
||||
"sniffamizer": Sniffamizer,
|
||||
"callbeadamizer": Callbeadamizer,
|
||||
"musicmizer": Musicmizer,
|
||||
"doggomizer": Doggomizer,
|
||||
"turdo_mode": TurdoMode,
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import bsdiff4
|
||||
import yaml
|
||||
from typing import Optional
|
||||
import Utils
|
||||
|
||||
|
||||
USHASH = '6e9c94511d04fac6e0a1e582c170be3a'
|
||||
current_patch_version = 2
|
||||
|
||||
|
||||
def read_rom(stream, strip_header=True) -> bytes:
|
||||
"""Reads rom into bytearray and optionally strips off any smc header"""
|
||||
data = stream.read()
|
||||
if strip_header and len(data) % 0x400 == 0x200:
|
||||
return data[0x200:]
|
||||
return data
|
||||
|
||||
|
||||
def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
|
||||
patch = yaml.dump({"meta": metadata,
|
||||
"patch": patch,
|
||||
"game": "Secret of Evermore",
|
||||
# minimum version of patch system expected for patching to be successful
|
||||
"compatible_version": 1,
|
||||
"version": current_patch_version,
|
||||
"base_checksum": USHASH})
|
||||
return patch.encode(encoding="utf-8-sig")
|
||||
|
||||
|
||||
def generate_patch(vanilla_file, randomized_file, metadata: Optional[dict] = None) -> bytes:
|
||||
with open(vanilla_file, "rb") as f:
|
||||
vanilla = read_rom(f)
|
||||
with open(randomized_file, "rb") as f:
|
||||
randomized = read_rom(f)
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
patch = bsdiff4.diff(vanilla, randomized)
|
||||
return generate_yaml(patch, metadata)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import pathlib
|
||||
import lzma
|
||||
parser = argparse.ArgumentParser(description='Apply patch to Secret of Evermore.')
|
||||
parser.add_argument('patch', type=pathlib.Path, help='path to .absoe file')
|
||||
args = parser.parse_args()
|
||||
with open(args.patch, "rb") as f:
|
||||
data = Utils.parse_yaml(lzma.decompress(f.read()).decode("utf-8-sig"))
|
||||
if data['game'] != 'Secret of Evermore':
|
||||
raise RuntimeError('Patch is not for Secret of Evermore')
|
||||
with open(Utils.get_options()['soe_options']['rom_file'], 'rb') as f:
|
||||
vanilla_data = read_rom(f)
|
||||
patched_data = bsdiff4.patch(vanilla_data, data["patch"])
|
||||
with open(args.patch.parent / (args.patch.stem + '.sfc'), 'wb') as f:
|
||||
f.write(patched_data)
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
from ..AutoWorld import World
|
||||
from ..generic.Rules import set_rule, add_item_rule
|
||||
from BaseClasses import Region, Location, Entrance, Item
|
||||
from Utils import get_options, output_path
|
||||
import typing
|
||||
import lzma
|
||||
import os
|
||||
import threading
|
||||
|
||||
try:
|
||||
import pyevermizer # from package
|
||||
except ImportError:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
from . import pyevermizer # as part of the source tree
|
||||
|
||||
from . import Logic # load logic mixin
|
||||
from .Options import soe_options
|
||||
from .Patch import generate_patch
|
||||
|
||||
"""
|
||||
In evermizer:
|
||||
|
||||
Items are uniquely defined by a pair of (type, id).
|
||||
For most items this is their vanilla location (i.e. CHECK_GOURD, number).
|
||||
|
||||
Items have `provides`, which give the actual progression
|
||||
instead of providing multiple events per item, we iterate through them in Logic.py
|
||||
e.g. Found any weapon
|
||||
|
||||
Locations have `requires` and `provides`.
|
||||
Requirements have to be converted to (access) rules for AP
|
||||
e.g. Chest locked behind having a weapon
|
||||
Provides could be events, but instead we iterate through the entire logic in Logic.py
|
||||
e.g. NPC available after fighting a Boss
|
||||
|
||||
Rules are special locations that don't have a physical location
|
||||
instead of implementing virtual locations and virtual items, we simply use them in Logic.py
|
||||
e.g. 2DEs+Wheel+Gauge = Rocket
|
||||
|
||||
Rules and Locations live on the same logic tree returned by pyevermizer.get_logic()
|
||||
|
||||
TODO: for balancing we may want to generate Regions (with Entrances) for some
|
||||
common rules, place the locations in those Regions and shorten the rules.
|
||||
"""
|
||||
|
||||
_id_base = 64000
|
||||
_id_offset: typing.Dict[int, int] = {
|
||||
pyevermizer.CHECK_ALCHEMY: _id_base + 0, # alchemy 64000..64049
|
||||
pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499
|
||||
pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399
|
||||
pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499
|
||||
# TODO: sniff 64500..64799
|
||||
}
|
||||
|
||||
# cache native evermizer items and locations
|
||||
_items = pyevermizer.get_items()
|
||||
_locations = pyevermizer.get_locations()
|
||||
# fix up texts for AP
|
||||
for _loc in _locations:
|
||||
if _loc.type == pyevermizer.CHECK_GOURD:
|
||||
_loc.name = f'{_loc.name} #{_loc.index}'
|
||||
|
||||
|
||||
def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]:
|
||||
name_to_id = {}
|
||||
id_to_raw = {}
|
||||
for loc in _locations:
|
||||
apid = _id_offset[loc.type] + loc.index
|
||||
id_to_raw[apid] = loc
|
||||
name_to_id[loc.name] = apid
|
||||
name_to_id['Done'] = None
|
||||
return name_to_id, id_to_raw
|
||||
|
||||
|
||||
def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]:
|
||||
name_to_id = {}
|
||||
id_to_raw = {}
|
||||
for item in _items:
|
||||
if item.name in name_to_id:
|
||||
continue
|
||||
apid = _id_offset[item.type] + item.index
|
||||
id_to_raw[apid] = item
|
||||
name_to_id[item.name] = apid
|
||||
name_to_id['Victory'] = None
|
||||
return name_to_id, id_to_raw
|
||||
|
||||
|
||||
class SoEWorld(World):
|
||||
"""
|
||||
Secret of Evermore is a SNES action RPG. You learn alchemy spells, fight bosses and gather rocket parts to visit a
|
||||
space station where the final boss must be defeated.
|
||||
"""
|
||||
game: str = "Secret of Evermore"
|
||||
options = soe_options
|
||||
topology_present: bool = False
|
||||
remote_items: bool = False
|
||||
data_version = 1
|
||||
|
||||
item_name_to_id, item_id_to_raw = _get_item_mapping()
|
||||
location_name_to_id, location_id_to_raw = _get_location_mapping()
|
||||
|
||||
evermizer_seed: int
|
||||
connect_name: str
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.connect_name_available_event = threading.Event()
|
||||
super(SoEWorld, self).__init__(*args, **kwargs)
|
||||
|
||||
def create_event(self, event: str) -> Item:
|
||||
progression = True
|
||||
return SoEItem(event, progression, None, self.player)
|
||||
|
||||
def create_item(self, item: typing.Union[pyevermizer.Item, str], force_progression: bool = False) -> Item:
|
||||
if type(item) is str:
|
||||
item = self.item_id_to_raw[self.item_name_to_id[item]]
|
||||
return SoEItem(item.name, force_progression or item.progression, self.item_name_to_id[item.name], self.player)
|
||||
|
||||
def create_regions(self):
|
||||
# TODO: generate *some* regions from locations' requirements?
|
||||
r = Region('Menu', None, 'Menu', self.player, self.world)
|
||||
r.exits = [Entrance(self.player, 'New Game', r)]
|
||||
self.world.regions += [r]
|
||||
|
||||
r = Region('Ingame', None, 'Ingame', self.player, self.world)
|
||||
r.locations = [SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], r)
|
||||
for loc in _locations]
|
||||
r.locations.append(SoELocation(self.player, 'Done', None, r))
|
||||
self.world.regions += [r]
|
||||
|
||||
self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player))
|
||||
|
||||
def create_items(self):
|
||||
# clear precollected items since we don't support them yet
|
||||
if type(self.world.precollected_items) is dict:
|
||||
self.world.precollected_items[self.player] = []
|
||||
# add items to the pool
|
||||
self.world.itempool += list(map(lambda item: self.create_item(item), _items))
|
||||
|
||||
def set_rules(self):
|
||||
self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player)
|
||||
# set Done from goal option once we have multiple goals
|
||||
set_rule(self.world.get_location('Done', self.player),
|
||||
lambda state: state._soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player))
|
||||
set_rule(self.world.get_entrance('New Game', self.player), lambda state: True)
|
||||
for loc in _locations:
|
||||
location = self.world.get_location(loc.name, self.player)
|
||||
set_rule(location, self.make_rule(loc.requires))
|
||||
|
||||
def make_rule(self, requires: typing.List[typing.Tuple[int]]) -> typing.Callable[[typing.Any], bool]:
|
||||
def rule(state) -> bool:
|
||||
for count, progress in requires:
|
||||
if not state._soe_has(progress, self.world, self.player, count):
|
||||
return False
|
||||
return True
|
||||
|
||||
return rule
|
||||
|
||||
def make_item_type_limit_rule(self, item_type: int):
|
||||
return lambda item: item.player != self.player or self.item_id_to_raw[item.code].type == item_type
|
||||
|
||||
def generate_basic(self):
|
||||
# place Victory event
|
||||
self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory'))
|
||||
# generate stuff for later
|
||||
self.evermizer_seed = self.world.random.randint(0, 2**16-1) # TODO: make this an option for "full" plando?
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
player_name = self.world.get_player_name(self.player)
|
||||
self.connect_name = player_name[:32]
|
||||
while len(self.connect_name.encode('utf-8')) > 32:
|
||||
self.connect_name = self.connect_name[:-1]
|
||||
self.connect_name_available_event.set()
|
||||
placement_file = None
|
||||
out_file = None
|
||||
try:
|
||||
money = self.world.money_modifier[self.player].value
|
||||
exp = self.world.exp_modifier[self.player].value
|
||||
rom_file = get_options()['soe_options']['rom_file']
|
||||
out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_{player_name}')
|
||||
out_file = out_base + '.sfc'
|
||||
placement_file = out_base + '.txt'
|
||||
patch_file = out_base + '.apsoe'
|
||||
flags = 'l' # spoiler log
|
||||
for option_name in self.options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
if hasattr(option, 'to_flag'):
|
||||
flags += option.to_flag()
|
||||
|
||||
with open(placement_file, "wb") as f: # generate placement file
|
||||
for location in filter(lambda l: l.player == self.player, self.world.get_locations()):
|
||||
item = location.item
|
||||
if item.code is None:
|
||||
continue # skip events
|
||||
loc = self.location_id_to_raw[location.address]
|
||||
if item.player != self.player:
|
||||
line = f'{loc.type},{loc.index}:{pyevermizer.CHECK_NONE},{item.code},{item.player}\n'
|
||||
else:
|
||||
item = self.item_id_to_raw[item.code]
|
||||
line = f'{loc.type},{loc.index}:{item.type},{item.index}\n'
|
||||
f.write(line.encode('utf-8'))
|
||||
|
||||
if (pyevermizer.main(rom_file, out_file, placement_file, self.world.seed_name, self.connect_name, self.evermizer_seed,
|
||||
flags, money, exp)):
|
||||
raise RuntimeError()
|
||||
with lzma.LZMAFile(patch_file, 'wb') as f:
|
||||
f.write(generate_patch(rom_file, out_file))
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
try:
|
||||
os.unlink(placement_file)
|
||||
os.unlink(out_file)
|
||||
os.unlink(out_file[:-4]+'_SPOILER.log')
|
||||
except:
|
||||
pass
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
# wait for self.connect_name to be available.
|
||||
self.connect_name_available_event.wait()
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if self.connect_name and self.connect_name != self.world.player_name[self.player]:
|
||||
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][self.connect_name] = payload
|
||||
del (multidata["connect_names"][self.world.player_name[self.player]])
|
||||
|
||||
|
||||
class SoEItem(Item):
|
||||
game: str = "Secret of Evermore"
|
||||
|
||||
|
||||
class SoELocation(Location):
|
||||
game: str = "Secret of Evermore"
|
||||
|
||||
def __init__(self, player: int, name: str, address: typing.Optional[int], parent):
|
||||
super().__init__(player, name, address, parent)
|
||||
self.event = not address
|
|
@ -0,0 +1,14 @@
|
|||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
|
||||
bsdiff4>=1.2.1
|
Loading…
Reference in New Issue