Skip to content
This repository was archived by the owner on Aug 11, 2021. It is now read-only.

Commit c377d69

Browse files
committed
Merge branch 'release/2.2.1'
2 parents a6bdc03 + fd1f408 commit c377d69

File tree

13 files changed

+149
-68
lines changed

13 files changed

+149
-68
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "metroid-prime-randomizer",
3-
"version": "2.2.0",
3+
"version": "2.2.1",
44
"description": "Metroid Prime Randomizer",
55
"homepage": "https://github.com/etaylor8086/metroid-prime-randomizer",
66
"contributors": [

src/client/src/app/generate-game/generate-game.component.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class GenerateGameComponent implements OnInit {
2626
private userPresets: PresetObject;
2727
private form: FormGroup;
2828
private ngUnsubscribe: Subject<any> = new Subject();
29+
private lastUpdatedPreset: string;
2930

3031
// Constants
3132
private readonly CUSTOM_PRESET = 'Custom';
@@ -69,6 +70,28 @@ export class GenerateGameComponent implements OnInit {
6970
this.loaded = true;
7071
}
7172
});
73+
74+
this.presetsService._previousAction
75+
.pipe(takeUntil(this.ngUnsubscribe))
76+
.subscribe(previousAction => {
77+
switch (previousAction) {
78+
case 'update':
79+
if (this.lastUpdatedPreset) {
80+
this.setPreset(this.lastUpdatedPreset);
81+
this.lastUpdatedPreset = null;
82+
}
83+
break;
84+
case 'remove':
85+
this.setPreset(this.randomizerService.DEFAULT_PRESET);
86+
break;
87+
}
88+
});
89+
90+
this.presetsService._lastUpdatedPreset
91+
.pipe(takeUntil(this.ngUnsubscribe))
92+
.subscribe(preset => {
93+
this.lastUpdatedPreset = preset;
94+
});
7295
}
7396

7497
ngOnDestroy() {
@@ -188,7 +211,6 @@ export class GenerateGameComponent implements OnInit {
188211

189212
removePreset(name: string): void {
190213
this.presetsService.removePreset(name);
191-
this.form.patchValue({ preset: this.randomizerService.DEFAULT_PRESET });
192214
}
193215

194216
generateSeed(spoiler: boolean) {
@@ -217,15 +239,5 @@ export class GenerateGameComponent implements OnInit {
217239
this.presets[key] = preset[key];
218240
}
219241
}
220-
221-
// After building presets, if we imported a preset, select it in the form
222-
this.presetsService._importedPreset
223-
.pipe(take(1))
224-
.subscribe(preset => {
225-
if (preset) {
226-
this.setPreset(preset);
227-
this.presetsService.clearImportPresetSubject();
228-
}
229-
});
230242
}
231243
}

src/client/src/app/help/help.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class HelpComponent implements OnInit {
1717
readonly menuItems = [
1818
{ name: 'Getting Started', file: 'assets/help-documents/gettingStarted.md' },
1919
{ name: 'FAQ', file: 'assets/help-documents/faq.md' },
20+
{ name: 'Softlocks', file: 'assets/help-documents/softlocks.md' },
2021
{ name: 'Differences', file: 'assets/help-documents/differences.md' },
2122
{ name: 'Trackers', file: 'assets/help-documents/trackers.md' }
2223
];

src/client/src/app/services/presets.service.ts

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import { RandomizerForm } from '../../../../common/models/randomizerForm';
1010
providedIn: 'root'
1111
})
1212
export class PresetsService {
13-
private cachedImportedPreset: string;
1413
private defaultPresets$ = new BehaviorSubject<PresetObject>(undefined);
1514
private userPresets$ = new BehaviorSubject<PresetObject>(undefined);
16-
private importedPreset$ = new BehaviorSubject<string>(undefined);
15+
private previousAction$ = new BehaviorSubject<string>(undefined);
16+
private lastUpdatedPreset$ = new BehaviorSubject<string>(undefined);
1717
_defaultPresets = this.defaultPresets$.asObservable();
1818
_userPresets = this.userPresets$.asObservable();
19-
_importedPreset = this.importedPreset$.asObservable();
19+
_previousAction = this.previousAction$.asObservable();
20+
_lastUpdatedPreset = this.lastUpdatedPreset$.asObservable();
2021

2122
constructor(private ngZone: NgZone, private electronService: ElectronService, private toastrService: ToastrService) {
2223
this.getAllPresets();
@@ -27,33 +28,28 @@ export class PresetsService {
2728
});
2829
});
2930

30-
this.electronService.ipcRenderer.on('getUserPresetsResponse', (event, response: PresetsResponse) => {
31+
this.electronService.ipcRenderer.on('getUserPresetsResponse', (event, response: PresetsResponse, previousAction: string) => {
3132
this.ngZone.run(() => {
32-
// Emit imported preset key if one is provided
33-
if (this.cachedImportedPreset) {
34-
this.importedPreset$.next(this.cachedImportedPreset);
35-
this.cachedImportedPreset = null;
36-
}
37-
3833
this.handlePresetsResponse(response, this.userPresets$);
34+
this.previousAction$.next(previousAction);
3935
});
4036
});
4137

4238
this.electronService.ipcRenderer.on('updateUserPresetResponse', (event, response) => {
4339
this.ngZone.run(() => {
44-
this.getUserPresets();
40+
this.getUserPresets('update');
4541
});
4642
});
4743

4844
this.electronService.ipcRenderer.on('removeUserPresetResponse', (event, response) => {
4945
this.ngZone.run(() => {
50-
this.getUserPresets();
46+
this.getUserPresets('remove');
5147
});
5248
});
5349

5450
this.electronService.ipcRenderer.on('importPresetResponse', (event, key: string, preset: RandomizerForm) => {
5551
this.ngZone.run(() => {
56-
this.cachedImportedPreset = key;
52+
this.lastUpdatedPreset$.next(key);
5753
this.electronService.ipcRenderer.send('updateUserPreset', preset, key);
5854
});
5955
});
@@ -79,8 +75,8 @@ export class PresetsService {
7975
this.electronService.ipcRenderer.send('getDefaultPresets');
8076
}
8177

82-
getUserPresets() {
83-
this.electronService.ipcRenderer.send('getUserPresets');
78+
getUserPresets(previousAction?: string) {
79+
this.electronService.ipcRenderer.send('getUserPresets', previousAction);
8480
}
8581

8682
getAllPresets() {
@@ -89,6 +85,7 @@ export class PresetsService {
8985
}
9086

9187
addOrUpdatePreset(name: string, preset: RandomizerForm) {
88+
this.lastUpdatedPreset$.next(name);
9289
this.electronService.ipcRenderer.send('updateUserPreset', preset, name);
9390
}
9491

@@ -122,10 +119,6 @@ export class PresetsService {
122119
});
123120
}
124121

125-
clearImportPresetSubject(): void {
126-
this.importedPreset$.next(null);
127-
}
128-
129122
private handlePresetsResponse(response: PresetsResponse, subject: Subject<PresetObject>): void {
130123
// If presets and keys are defined, return the original order the presets were in, otherwise don't do anything
131124
if (!response.presets) {

src/client/src/assets/help-documents/faq.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Every time you play a randomizer seed, you should always start from a brand-new
1010

1111
## My seed is broken. Items are missing, artifacts aren't working, I'm starting out with items, etc.
1212

13-
The randomizer doesn't work (or work *well*) with pre-existing saves. You need to start from a brand-new save slot each time you play a randomizer seed or else you might run into this issue.
13+
The randomizer doesn't work (or work *well*) with pre-existing save slots. You need to start from a brand-new save slot each time you play a randomizer seed or else you might run into this issue.
1414

1515
## The game crashes if I lay a Morph Ball Bomb while submerged in water or lava.
1616

@@ -30,10 +30,6 @@ This is a variation of the above problem, except the game doesn't crash. Try run
3030

3131
You need the Charge Beam equipped in order to use beam combos.
3232

33-
## The big red flower isn't there when I enter Sunchamber (Flaahgra's room).
34-
35-
This happens when you enter Sunchamber from the Sun Tower side without fighting Flaahgra first. If you have Space Jump Boots, you can slope jump from one of the roots to reach the item that spawns after the Chozo Ghost fight, otherwise you won't be able to load the Flaahgra fight. If you don't have Space Jump Boots, or Flaahgra blocks access to them, you are softlocked and need to restart.
36-
3733
## I can't hurt Omega Pirate.
3834

3935
Omega Pirate can only be hurt while you have the X-Ray Visor active. If you start the boss fight and don't have the X-Ray Visor, you will softlock.
@@ -48,6 +44,14 @@ You killed the two Space Pirates that normally jump down from the ledge near the
4844

4945
Make sure you trigger the door lock first before killing the space pirates.
5046

51-
## Why is Varia Suit in Phendrana Drifts? I don't have heat protection to get there!
47+
## Why is Varia Suit in Phendrana Drifts? I don't have heat protection to get there.
5248

5349
The second half of Magmoor Caverns (past Twin Fires Tunnel) is not superheated, so the logic is likely expecting you to go to the Phendrana Quarantine Cave elevator by going to Magmoor Workstation from Phazon Mines.
50+
51+
## When Samus morphs, her Morph Ball form looks different than her suit.
52+
53+
Metroid Prime does not contain any 3D models or assets for Power Suit Spider Ball, Gravity Suit spiderless Morph Ball, and Phazon Suit spiderless Morph Ball, so it does the following:
54+
55+
If Samus has the Power Suit and the Spider Ball, the game uses the Gravity Suit Spider Ball model when she is morphed.
56+
57+
If Samus has the Gravity Suit or Phazon Suit but does not have Spider Ball, the game uses the Power Suit Morph Ball model when she is morphed.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Softlocks
2+
3+
Softlocks are situations where the game is usually still playable, but further progression is impossible.
4+
5+
If you're not careful, you can accidentally find yourself in a room without the item needed to leave it, causing you to softlock. While the randomizer handles a lot of these cases, there are some that it does not and cannot account for.
6+
7+
## Ruined Shrine
8+
9+
If you fall into the pit in Ruined Shrine, you will need either the Morph Ball or Space Jump Boots to escape. If you get stuck, you can slope jump and abuse the standable terrain on the large vine near where the Beetle Battle item is, then dash across to the half pipe, but this is never expected of players.
10+
11+
## Burn Dome
12+
13+
If you do not have bombs, the Morph Ball tunnel leading to Burn Dome is a one-way trip. You will be stuck and softlocked if you do not find bombs there.
14+
15+
## Chapel of the Elders, Antechamber, and Plasma Processing
16+
17+
These rooms use different door colors to enter and leave them, so you can get stuck if you enter the room without having the necessary beam to open the door from the inside.
18+
19+
## Warrior Shrine Tunnel
20+
21+
If you enter the tunnel underneath Warrior Shrine without having bombs, you will get stuck.
22+
23+
## Shore Tunnel
24+
25+
If you do not have Space Jump Boots, you will either need to double bomb jump or perform a precise slope jump to reach the broken tube from the pit underneath.
26+
27+
## Phendrana Canyon
28+
29+
If you do not have Boost Ball or Space Jump Boots after entering the room, you will need to jump on the crates to leave. If the crates are destroyed, you will get stuck.
30+
31+
## Observatory
32+
33+
The bottom door only locks when you get near it. If you enter Observatory from the top door and kill the Space Pirates before triggering the lock, it will stay locked while you are still in the room. If you cannot complete the puzzle to extend the platforms, or know how to climb the room with dashes, you will softlock.
34+
35+
Make sure you trigger the door lock first, then kill the Space Pirates.
36+
37+
## Control Tower
38+
39+
If you collapse the tower and enter the section where the item is without bombs, you will be unable to escape in a glitchless setting.
40+
41+
## Quarantine Cave
42+
43+
You can get stuck in Quarantine Cave if you do not have Spider Ball, Grapple Beam, or know how to slope jump to escape the room.
44+
45+
## Lower Phazon Mines / Ventilation Shaft
46+
47+
If you fall down into the half pipe section of Ventilation Shaft, you will either need Boost Ball to leave the way you entered, or the minimum items needed to traverse through lower Phazon Mines, defeat Omega Pirate, and reach Phazon Processing Center through Processing Center Access. If you cannot do either of these, you will be stuck.
48+
49+
## Elite Quarters
50+
51+
If you start the Omega Pirate fight and don't have X-Ray Visor, you will softlock. Omega Pirate can only take damage while you have X-Ray Visor equipped, and the room will only unlock after defeating it.
52+
53+
## Metroid Prime Lair
54+
55+
You can only use the Phazon Beam if you have the Phazon Suit, and Metroid Prime Essence can only take damage from the Phazon Beam. If you start the fight without the Phazon Suit, you will be unable to continue.

src/electron/controllers/presetsController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function initialize() {
3131
event.sender.send('getDefaultPresetsResponse', response);
3232
});
3333

34-
ipcMain.on('getUserPresets', (event) => {
34+
ipcMain.on('getUserPresets', (event, previousAction: string) => {
3535
const userPresetsResponse = 'getUserPresetsResponse';
3636

3737
fs.access(userPresetsPath, fs.constants.R_OK, (err) => {
@@ -45,7 +45,7 @@ export function initialize() {
4545
readUserPresetsFile(response => {
4646
// Add to allPresets object
4747
Object.assign(allPresets, response.presets);
48-
event.sender.send(userPresetsResponse, response);
48+
event.sender.send(userPresetsResponse, response, previousAction);
4949
});
5050
}
5151
});

src/electron/data/presetsDefault.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
"Beginner Friendly": {
2929
"excludeLocations": [],
3030
"itemOverrides": [
31+
{
32+
"count": 1,
33+
"name": "Charge Beam",
34+
"state": "starting-item"
35+
},
3136
{
3237
"count": 1,
3338
"name": "Morph Ball",
@@ -40,7 +45,12 @@
4045
},
4146
{
4247
"count": 1,
43-
"name": "Charge Beam",
48+
"name": "Boost Ball",
49+
"state": "starting-item"
50+
},
51+
{
52+
"count": 1,
53+
"name": "Spider Ball",
4454
"state": "starting-item"
4555
}
4656
],

src/electron/models/prime/fill.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,35 @@ import { PrimeWorld } from './world';
33
import { Item } from '../item';
44
import { ItemPriority } from './items';
55
import { Location } from '../location';
6+
import { PrimeItemCollection } from './itemCollection';
67

78
/**
89
* Parent function for distributing an item pool across the Metroid Prime world.
910
* @param world The Metroid Prime world instance being used.
1011
*/
1112
export function distributeItemsRestrictive(world: PrimeWorld): void {
12-
// Get whole item pool, and shuffle it
13-
const itemPool = world.getItemPool().shuffle(world.getRng());
13+
// Get whole item pool, and shuffle it
14+
const itemPool = world.getItemPool().shuffle(world.getRng());
1415

15-
// Get unfilled item locations
16-
let fillLocations = world.getLocations().filter((location: Location) => !location.hasItem());
16+
// Get unfilled item locations
17+
let fillLocations = world.getLocations().filter((location: Location) => !location.hasItem());
1718

18-
const progressionItemPool = itemPool.filter((item: Item) => item.getPriority() === ItemPriority.PROGRESSION);
19-
const artifactsItemPool = itemPool.filter((item: Item) => item.getPriority() === ItemPriority.ARTIFACTS);
20-
const extrasItemPool = itemPool.filter((item: Item) => item.getPriority() === ItemPriority.EXTRA);
19+
const progressionItemPool = itemPool.filter((item: Item) => item.getPriority() === ItemPriority.PROGRESSION);
20+
const artifactsItemPool = itemPool.filter((item: Item) => item.getPriority() === ItemPriority.ARTIFACTS);
21+
const extrasItemPool = itemPool.filter((item: Item) => item.getPriority() === ItemPriority.EXTRA);
2122

22-
// Logically fill progressive items to ensure the game can be completed.
23-
fillRestrictive(world, fillLocations, progressionItemPool);
23+
// Logically fill progressive items to ensure the game can be completed.
24+
fillRestrictive(world, fillLocations, progressionItemPool);
2425

25-
// Filter out filled locations
26-
fillLocations = world.getLocations().filter((location: Location) => !location.hasItem());
26+
// Filter out filled locations
27+
fillLocations = world.getLocations().filter((location: Location) => !location.hasItem());
2728

28-
// Progression items are filled, artifacts can be filled fast among non-excluded locations
29-
fillFast(world, fillLocations, artifactsItemPool);
29+
// Progression items are filled, fill artifacts restrictively to ensure reachability
30+
fillRestrictive(world, fillLocations, artifactsItemPool);
3031

31-
// Filter out filled locations
32-
fillLocations = world.getLocations().filter((location: Location) => !location.hasItem());
32+
// Filter out filled locations
33+
fillLocations = world.getLocations().filter((location: Location) => !location.hasItem());
3334

34-
// Fill extras/remaining junk items last. No logic needed as the progression items are placed by now.
35-
fillFast(world, fillLocations, extrasItemPool, true);
35+
// Fill extras/remaining junk items last. No logic needed as the progression items are placed by now.
36+
fillFast(world, fillLocations, extrasItemPool, true);
3637
}

src/electron/models/prime/regions/chozoRuins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export function chozoRuins(): RegionObject[] {
114114
name: 'Ruined Shrine (Outer)',
115115
locations: {
116116
[PrimeLocation.RUINED_SHRINE_HALF_PIPE]: (items: PrimeItemCollection, settings: PrimeRandomizerSettings) => {
117-
const spaceJumpReqs = settings.tricks.upperRuinedShrineTowerOfLightFewerAccessReqs && items.has(PrimeItem.SPACE_JUMP_BOOTS);
117+
const spaceJumpReqs = settings.tricks.upperRuinedShrineTowerOfLightFewerAccessReqs && items.has(PrimeItem.SPACE_JUMP_BOOTS) && items.has(PrimeItem.MORPH_BALL);
118118
return spaceJumpReqs || items.canBoost();
119119
}
120120
},

src/electron/models/prime/regions/phendranaDrifts.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,12 @@ export function phendranaDrifts(): RegionObject[] {
285285
name: 'Phendrana\'s Edge',
286286
locations: {
287287
[PrimeLocation.STORAGE_CAVE]: (items: PrimeItemCollection, settings: PrimeRandomizerSettings) => {
288-
const grappleReqs = settings.tricks.removePhendranaDepthsGrappleReqs || items.has(PrimeItem.GRAPPLE_BEAM);
289-
return items.canLayPowerBombs() && grappleReqs && items.has(PrimeItem.PLASMA_BEAM)
288+
const grappleReqs = items.has(PrimeItem.GRAPPLE_BEAM) || settings.tricks.removePhendranaDepthsGrappleReqs;
289+
// Effectively removes visor requirements if either Remove Thermal or Remove X-Ray Requirements are enabled.
290+
const visorReqs = (items.has(PrimeItem.THERMAL_VISOR) || settings.tricks.removeThermalReqs)
291+
|| (items.has(PrimeItem.XRAY_VISOR) || settings.tricks.removeXrayReqs);
292+
293+
return grappleReqs && visorReqs && items.canLayPowerBombs() && items.has(PrimeItem.PLASMA_BEAM)
290294
&& items.has(PrimeItem.SPACE_JUMP_BOOTS);
291295
},
292296
[PrimeLocation.SECURITY_CAVE]: (items: PrimeItemCollection, settings: PrimeRandomizerSettings) => {

0 commit comments

Comments
 (0)