Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 76 additions & 24 deletions CrossPlatformUI.Browser/wwwroot/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dotnet } from './_framework/dotnet.js'
import { compile } from './js65/libassembler.js'

const BUNDLE_DOWNLOAD_SIZE = 80 * 1024 * 1024; // used for progress bar - doesn't have to be exact
const BUNDLE_DOWNLOAD_SIZE = 71 * 1024 * 1024; // used for progress bar - doesn't have to be exact

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
Expand All @@ -17,8 +17,22 @@ function showError(msg) {
(() => {
const origFetch = globalThis.fetch.bind(globalThis);

// This is a custom cache workaround for GitHub Pages arbitrarily
// sending different ETags for the same files. (It might depend on
// which CDN handles the request.)
// Instead rely on GitHub Pages setting Last - Modified to be the
// same for all files when the page is updated, including this script.
const deployTimestampPromise = (async () => {
try {
const r = await origFetch(import.meta.url, { cache: "default" });
return Date.parse(r.headers.get("Last-Modified") || "") || 0;
} catch {
return 0;
}
})();

// track cumulative progress across all boot resources
let loadedKnownBytes = 0;
let completedBytes = 0;
const inFlight = new Map(); // id -> {loaded,total}
let started = false;
let finishTimer;
Expand All @@ -40,24 +54,57 @@ function showError(msg) {
};

const updateOverall = () => {
updateProgress(loadedKnownBytes / BUNDLE_DOWNLOAD_SIZE);
let loaded = completedBytes;
for (const r of inFlight.values()) {
loaded += r.loaded;
}
updateProgress(loaded / BUNDLE_DOWNLOAD_SIZE);
};

globalThis.fetch = async (input, init) => {
const res = await origFetch(input, init).catch((e) => {
showError("Network error while loading app. Please reload.");
throw e;
});
const fetchError = (e) => {
showError("Network error while loading app. Please reload.");
throw e;
};

globalThis.fetch = async (input, init) => {
const deployTs = await deployTimestampPromise;
const url = typeof input === "string" ? input : (input && input.url) || "";
if (!isBootResource(url, res) || !res.body || res.bodyUsed) {
return res; // leave non-boot fetches alone
const absoluteUrl = new URL(url, window.location.href).href;

var fetchResult;

if (deployTs !== 0) {
// check if transferSize is non-zero (there was no local cache)
const cachedFetchResult = await origFetch(input, { ...init, cache: "force-cache" }).catch(fetchError);
const perfEntries = performance?.getEntriesByName(absoluteUrl, "resource");
const lastEntry = perfEntries?.[perfEntries.length - 1];
const wasNetworkRequest = lastEntry?.transferSize > 0;

var staleCache = false;
if (!wasNetworkRequest) {
// if we have a reference timestamp, ensure the cached copy isn't
// much older than the deploy timestamp.
const lm = Date.parse(cachedFetchResult.headers.get("Last-Modified"));
staleCache = !lm || lm < deployTs - 180_000;
}

if (!staleCache) {
fetchResult = cachedFetchResult;
}
}

if (!fetchResult) {
fetchResult = await origFetch(input, { ...init, cache: "default" }).catch(fetchError);
}

if (!isBootResource(url, fetchResult) || !fetchResult.body || fetchResult.bodyUsed) {
return fetchResult; // leave non-boot fetches alone
}

started = true;
const contentLength = parseInt(res.headers.get("Content-Length") || "0", 10);
const contentLength = parseInt(fetchResult.headers.get("Content-Length") || "0", 10);
const id = Math.random().toString(36).slice(2);
const reader = res.body.getReader();
const reader = fetchResult.body.getReader();

inFlight.set(id, { loaded: 0, total: contentLength });

Expand All @@ -67,31 +114,36 @@ function showError(msg) {
showError("Error reading a resource stream. Please reload.");
throw e;
});

if (done) {
controller.close();
// account for any rounding misses
const r = inFlight.get(id);
if (r && r.total > 0) loadedKnownBytes += (r.total - r.loaded);
inFlight.delete(id);

if (r) {
completedBytes += r.loaded;
inFlight.delete(id);
}

updateOverall();
controller.close();
return;
}

controller.enqueue(value);
const r = inFlight.get(id);
if (r) {
r.loaded += value.length;
if (r.total > 0) loadedKnownBytes += value.length;
inFlight.set(id, r);

const current = inFlight.get(id);
if (current) {
current.loaded += value.length;
inFlight.set(id, current);
}
updateOverall();
},
cancel(reason) { try { reader.cancel(reason); } catch { } }
});

return new Response(stream, {
headers: res.headers,
status: res.status,
statusText: res.statusText
headers: fetchResult.headers,
status: fetchResult.status,
statusText: fetchResult.statusText
});
};

Expand Down
22 changes: 20 additions & 2 deletions RandomizerCore/EnumTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,24 @@ public static bool IsCoordinateBased(this PalaceStyle style)
_ => false
};
}
public static bool NormalPalaceStyle(this PalaceStyle style)
{
return style switch
{
PalaceStyle.RANDOM => false,
_ => true
};
}
public static bool IsGpStyle(this PalaceStyle style)
{
return style switch
{
PalaceStyle.VANILLA_WEIGHTED => false,
PalaceStyle.RANDOM_ALL => false,
PalaceStyle.RANDOM_PER_PALACE => false,
_ => true
};
}
}

public enum PalaceDropStyle
Expand Down Expand Up @@ -976,9 +994,9 @@ public static class Enums
public static IEnumerable<EnumDescription> PalaceLengthOptionList { get; } = ToDescriptions<PalaceLengthOption>();
public static IEnumerable<EnumDescription> PalaceItemRoomCountOptions { get; } = ToDescriptions<PalaceItemRoomCount>();
public static IEnumerable<EnumDescription> NormalPalaceStyleList { get; }
= ToDescriptions<PalaceStyle>(i => i != PalaceStyle.RANDOM);
= ToDescriptions<PalaceStyle>(i => i.NormalPalaceStyle());
public static IEnumerable<EnumDescription> GpPalaceStyleList { get; }
= ToDescriptions<PalaceStyle>(i => i != PalaceStyle.RANDOM_PER_PALACE && i != PalaceStyle.RANDOM_ALL);
= ToDescriptions<PalaceStyle>(i => i.IsGpStyle());
public static IEnumerable<EnumDescription> BossRoomsExitTypeList { get; } = ToDescriptions<BossRoomsExitType>();
public static IEnumerable<EnumDescription> PalaceDropStyleList { get; } = ToDescriptions<PalaceDropStyle>();

Expand Down
6 changes: 1 addition & 5 deletions RandomizerCore/Hyrule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3849,11 +3849,7 @@ private void ApplyAsmPatches(RandomizerProperties props, Assembler engine, Rando
rom.FixBigBubbleSplit(engine, randomizedStats);
StatTracking(props, engine);
AddCredits(engine);

if (props.ShuffleBossHP != EnemyLifeOption.VANILLA)
{
rom.SetBossHpBarDivisors(engine, randomizedStats);
}
rom.SetBossHpBarDivisors(engine, randomizedStats);

if (props.DripperEnemyOption != DripperEnemyOption.ONLY_BOTS)
{
Expand Down
2 changes: 1 addition & 1 deletion RandomizerCore/Overworld/Climate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public IEnumerable<Terrain> RandomTerrains(IEnumerable<Terrain> filter)

public void DisallowTerrain(Terrain terrain)
{
weightedSampler = weightedSampler.Subtract(terrain);
weightedSampler = weightedSampler.Subtract(terrain)!;
}

public Climate Clone()
Expand Down
19 changes: 18 additions & 1 deletion RandomizerCore/Overworld/EastHyrule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1662,12 +1662,29 @@ private void DrawMountains(bool useRiverDevil)
}
}

protected override bool IsReserved(IntVector2 pos)
/// If this tile is reserved, in the context of a river expanding to
/// this tile. Most of these are probably logically impossible.
/// The point is to stop a river opening up VoD.
protected override bool IsReserved(IntVector2 pos, Terrain terrain)
{
switch (terrain)
{
case Terrain.TOWN:
case Terrain.CAVE:
case Terrain.PALACE:
case Terrain.BRIDGE:
case Terrain.LAVA:
case Terrain.MOUNTAIN:
case Terrain.ROCK:
case Terrain.RIVER_DEVIL:
return true;
}

if ((locationAtGP.Pos - pos).Abs().MinComponent() < 4)
{
return true;
}

return false;
}

Expand Down
24 changes: 19 additions & 5 deletions RandomizerCore/Overworld/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2190,7 +2190,7 @@ IntVector2 AdjustAndPaint(IntVector2 pos, IntVector2 delta, IntVector2 sideDir)
int maxAdj = (sideDir.X != 0 && pos.X == MapColumns - 2) || (sideDir.Y != 0 && pos.Y == MapRows - 2) ? 0 : 1;
int adjust = RNG.Next(minAdj, maxAdj + 1);
IntVector2 adjusted = pos + adjust * sideDir;
if (WithinMapBounds(adjusted) && !IsReserved(adjusted))
if (WithinMapBounds(adjusted) && !IsReserved(adjusted, map[adjusted]))
{
map[adjusted] = water;
}
Expand Down Expand Up @@ -2536,7 +2536,7 @@ protected bool VerticalCave(int caveDirection, int centerX, int centerY, Locatio
}

// subset of walkable terrains that we would expect to be next to a trap tile
protected static readonly FrozenSet<Terrain> TRAP_PATH_VALID_TERRAIN = [Terrain.DESERT, Terrain.GRASS, Terrain.ROAD, Terrain.LAVA, Terrain.NONE];
protected static readonly FrozenSet<Terrain> TRAP_PATH_VALID_TERRAIN = [Terrain.DESERT, Terrain.GRASS, Terrain.ROAD, Terrain.LAVA];
protected static readonly FrozenSet<Terrain> TRAP_PATH_BLOCKING_TERRAIN = [Terrain.MOUNTAIN, Terrain.WATER];

/// <summary>
Expand Down Expand Up @@ -2610,8 +2610,22 @@ public void PlaceCaveBlocker(Location cave, IntVector2 dir, Terrain blockerTerra
cave.Pos = newCavePos;
}

protected virtual bool IsReserved(IntVector2 pos)
/// If this tile is reserved, in the context of a river expanding to
/// this tile. Most of these are probably logically impossible.
/// The point is to stop a river opening up VoD.
/// (This method is overridden in EastHyrule)
protected virtual bool IsReserved(IntVector2 pos, Terrain terrain)
{
switch (terrain)
{
case Terrain.TOWN:
case Terrain.CAVE:
case Terrain.PALACE:
case Terrain.BRIDGE:
case Terrain.ROCK:
return true;
}

return false;
}

Expand All @@ -2628,8 +2642,8 @@ protected virtual bool IsReserved(IntVector2 pos)
/// </returns>
public IntVector2? ValidTrapTilePosition(IntVector2 pos)
{
bool isPassable(IntVector2 pos) => TRAP_PATH_VALID_TERRAIN.Contains(map[pos.Y, pos.X]);
bool isBlocking(IntVector2 pos) => TRAP_PATH_BLOCKING_TERRAIN.Contains(map[pos.Y, pos.X]);
bool isPassable(IntVector2 pos) => TRAP_PATH_VALID_TERRAIN.Contains(map[pos]);
bool isBlocking(IntVector2 pos) => TRAP_PATH_BLOCKING_TERRAIN.Contains(map[pos]);

if (!WithinMapBounds(pos, 1)) { return null; }
if (!isPassable(pos)) { return null; }
Expand Down
6 changes: 3 additions & 3 deletions RandomizerCore/RandomizerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ public RandomizerProperties Export(Random r, bool includeDifficulty = true)
if(GpStyle.IsMetastyle())
{
Debug.Assert(GpStyle == PalaceStyle.RANDOM);
allowedPalaceStyles = Enums.GetShufflableList<PalaceStyle>();
allowedPalaceStyles = [.. Enums.GetShufflableList<PalaceStyle>().Where(i => i.IsGpStyle())];
if (!randomStylesAllowVanilla)
{
allowedPalaceStyles.RemoveAll(i => i.UsesVanillaRoomPool());
Expand All @@ -946,8 +946,8 @@ public RandomizerProperties Export(Random r, bool includeDifficulty = true)

if (NormalPalaceStyle.IsMetastyle())
{
Debug.Assert(NormalPalaceStyle != PalaceStyle.RANDOM);
allowedPalaceStyles = Enums.GetShufflableList<PalaceStyle>();
Debug.Assert(NormalPalaceStyle == PalaceStyle.RANDOM_PER_PALACE || NormalPalaceStyle == PalaceStyle.RANDOM_ALL);
allowedPalaceStyles = [.. Enums.GetShufflableList<PalaceStyle>().Where(i => i.NormalPalaceStyle())];
if (!randomStylesAllowVanilla)
{
allowedPalaceStyles.RemoveAll(i => i.UsesVanillaRoomPool());
Expand Down
Loading