Multiworld stuff
This commit is contained in:
93
src/components/Multi.vue
Normal file
93
src/components/Multi.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { Base64 } from "js-base64";
|
||||
import axios from "axios";
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
multidata: null,
|
||||
worlds: {},
|
||||
error: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
id: "",
|
||||
},
|
||||
async mounted() {
|
||||
document.title = `ALttPR Multiworld: ${this.id}`;
|
||||
await this.fetchMulti();
|
||||
},
|
||||
computed: {
|
||||
permalink() {
|
||||
return `/multi/${this.id}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async fetchMulti() {
|
||||
await axios.get(`/multi/${this.id}`)
|
||||
.then(response => {
|
||||
if (response && response.data && response.data.multidata) {
|
||||
const multi = response.data;
|
||||
this.multidata = Base64.toUint8Array(multi.multidata);
|
||||
this.worlds = multi.worlds;
|
||||
} else {
|
||||
this.error = "Error loading multiworld.";
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response?.status == 409) {
|
||||
// still generating, try again
|
||||
setTimeout(this.fetchMulti.bind(this), 10000);
|
||||
} else {
|
||||
this.error = "Multiworld not found. :(";
|
||||
}
|
||||
});
|
||||
},
|
||||
async downloadMultidata() {
|
||||
const blob = new Blob([this.multidata], { type: 'octet/stream' });
|
||||
const link = document.getElementById('downloader');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `GK_${this.id}_multidata`;
|
||||
link.click();
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mw-30">
|
||||
<div v-if="multidata" class="card content-div m-3">
|
||||
<div class="card-header">
|
||||
Permalink: <a :href="permalink">{{ permalink }}</a>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li v-for="world in worlds" class="list-group-item">
|
||||
{{ world.name }}: <a :href="`/seed/${world.id}`">/seed/{{ world.id }}</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<button type="submit" class="btn btn-primary submit-btn" :disabled="!multidata" @click="downloadMultidata">
|
||||
Download Multidata!
|
||||
</button>
|
||||
<a id="downloader" style="display: none;" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<img class="center" src="/bludormspinbig.gif" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-message {
|
||||
text-align: center;
|
||||
font-size: xx-large;
|
||||
font-weight: bold;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
</style>
|
||||
@@ -69,6 +69,9 @@ export default defineComponent({
|
||||
},
|
||||
settingsMatch(newSettings, preset) {
|
||||
for (const settingName of Object.keys(newSettings)) {
|
||||
if (settingName == "player_name") {
|
||||
continue;
|
||||
}
|
||||
if (preset[settingName] != newSettings[settingName]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export default defineComponent({
|
||||
patch: null,
|
||||
error: null,
|
||||
settings: null,
|
||||
multi: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -63,6 +64,7 @@ export default defineComponent({
|
||||
dataLoaded(patch, seedData) {
|
||||
this.patch = patch;
|
||||
this.settings = seedData.settings;
|
||||
this.multi = seedData.parent;
|
||||
},
|
||||
async fetchSeed() {
|
||||
await axios.get(`/seed/${this.id}`)
|
||||
@@ -175,7 +177,7 @@ export default defineComponent({
|
||||
</li>
|
||||
<li v-if="settings" class="list-group-item">
|
||||
<div class="mb-1">
|
||||
<SeedSettings :settings="settings" />
|
||||
<SeedSettings :settings="settings" :multi="multi" />
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
|
||||
@@ -7,6 +7,7 @@ import mustache from "mustache";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
settings: {},
|
||||
multi: null,
|
||||
},
|
||||
computed: {
|
||||
settingsDisplay() {
|
||||
@@ -29,7 +30,14 @@ export default defineComponent({
|
||||
|| (this.settings.maps && this.settings.maps != "dungeon")
|
||||
|| (this.settings.compasses && this.settings.compasses != "dungeon")
|
||||
|| (this.settings.prize_shuffle && this.settings.prize_shuffle != "vanilla");
|
||||
}
|
||||
},
|
||||
multilink() {
|
||||
if (this.multi) {
|
||||
return `/multi/${this.multi}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
@@ -37,6 +45,10 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="multi">
|
||||
Part of Multiworld: <a :href="multilink">{{ multilink }}</a>
|
||||
<hr class="mt-2 mb-2" />
|
||||
</div>
|
||||
<div v-if="settings.randomizer && settingsDisplay.randomizer[settings.randomizer]">
|
||||
{{ settingsDisplay.randomizer[settings.randomizer] }}
|
||||
<hr class="mt-2 mb-2" />
|
||||
|
||||
@@ -19,6 +19,7 @@ export default defineComponent({
|
||||
name: null,
|
||||
color: "primary",
|
||||
generator: null,
|
||||
prefix: null,
|
||||
},
|
||||
watch: {
|
||||
async modelValue(newValue, oldValue) {
|
||||
@@ -44,7 +45,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.selected = await localforage.getItem(`setting_${this.name}`) ?? this.settings.default;
|
||||
this.selected = await localforage.getItem(`${this.prefix}setting_${this.name}`) ?? this.settings.default;
|
||||
this.change();
|
||||
},
|
||||
methods: {
|
||||
@@ -53,7 +54,7 @@ export default defineComponent({
|
||||
await this.updateLocalForage();
|
||||
},
|
||||
async updateLocalForage() {
|
||||
await localforage.setItem(`setting_${this.name}`, this.selected);
|
||||
await localforage.setItem(`${this.prefix}setting_${this.name}`, this.selected);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -64,9 +65,9 @@ export default defineComponent({
|
||||
<div class="nav nav-pills nav-fill mt-1 mb-1">
|
||||
<template v-for="value of settings.order ?? Object.keys(settings.values)">
|
||||
<template v-if="settings.values[value]">
|
||||
<input type="radio" class="btn-check" :name="name" :id="`${name}_${value}`"
|
||||
<input type="radio" class="btn-check" :name="`${prefix}${name}`" :id="`${prefix}${name}_${value}`"
|
||||
autocomplete="off" :value="value" v-model="selected" @change="change" />
|
||||
<label :class="`btn btn-outline-${color} nav-item m-1`" :for="`${name}_${value}`">
|
||||
<label :class="`btn btn-outline-${color} nav-item m-1`" :for="`${prefix}${name}_${value}`">
|
||||
{{ settings.values[value].display }}
|
||||
</label>
|
||||
</template>
|
||||
|
||||
209
src/components/SettingsPage.vue
Normal file
209
src/components/SettingsPage.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import axios from "axios";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
import AccordionItem from "@/components/AccordionItem.vue";
|
||||
import PresetPicker from "@/components/PresetPicker.vue";
|
||||
import SettingPicker from "@/components/SettingPicker.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AccordionItem,
|
||||
PresetPicker,
|
||||
SettingPicker,
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
props: {
|
||||
modelValue: {},
|
||||
prefix: null,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
set: {},
|
||||
existingLocalPresets: [],
|
||||
newPresetName: "",
|
||||
replacePreset: null,
|
||||
modal: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.modal = new Modal(document.getElementById("savePresetModal"), {});
|
||||
},
|
||||
watch: {
|
||||
set: {
|
||||
handler(newValue, oldValue) {
|
||||
this.$refs.preset.settingChanged(newValue);
|
||||
this.$emit("update:modelValue", this.set);
|
||||
},
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async generate(race) {
|
||||
const settings = {
|
||||
randomizer: "base",
|
||||
race: race ? "race" : "normal"
|
||||
};
|
||||
for (const setting of Object.keys(this.set)) {
|
||||
settings[setting] = this.set[setting];
|
||||
}
|
||||
await axios.post("/generate", settings)
|
||||
.then(response => {
|
||||
const id = response.data;
|
||||
this.$router.push(`/seed/${id}`);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
presetSelected(preset) {
|
||||
for (const setting of Object.keys(this.set)) {
|
||||
if (preset[setting] != undefined) {
|
||||
this.set[setting] = preset[setting];
|
||||
}
|
||||
}
|
||||
},
|
||||
savePreset(names) {
|
||||
this.newPresetName = "";
|
||||
this.existingLocalPresets = names;
|
||||
this.modal.show();
|
||||
},
|
||||
async modalSavePreset() {
|
||||
const preset = JSON.parse(JSON.stringify(this.set));
|
||||
preset.display = this.newPresetName;
|
||||
if (this.replacePreset != null) {
|
||||
preset.display = this.existingLocalPresets[this.replacePreset];
|
||||
}
|
||||
await this.$refs.preset.savePreset(this.replacePreset, preset);
|
||||
this.modal.hide();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal" tabindex="-1" id="savePresetModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Save Preset</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="input-group mb-3">
|
||||
<select v-model="replacePreset" class="form-select">
|
||||
<option :value="null" selected>New</option>
|
||||
<optgroup v-if="existingLocalPresets.length" label="Overwrite Preset:">
|
||||
<template v-for="(preset, idx) of existingLocalPresets">
|
||||
<option :value="idx">
|
||||
{{ preset }}
|
||||
</option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group mb-3" v-if="replacePreset == null">
|
||||
<label class="input-group-text" for="new-preset-name">
|
||||
Name
|
||||
</label>
|
||||
<input type="text" class="form-control" placeholder="Display Name"
|
||||
id="new-preset-name" v-model="newPresetName" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button :disabled="replacePreset == null && newPresetName.length == 0" type="button"
|
||||
class="btn btn-primary" @click="modalSavePreset">
|
||||
Save Preset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings" class="accordion accordion-flush">
|
||||
<AccordionItem>
|
||||
<PresetPicker ref="preset" generator="base" @selected="presetSelected"
|
||||
@save="savePreset" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="true">
|
||||
<template #header>
|
||||
<b>World:</b>
|
||||
</template>
|
||||
<SettingPicker color="primary" v-model="set.mode" name="mode" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="primary" v-model="set.weapons" name="weapons" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="true">
|
||||
<template #header>
|
||||
<b>Goal:</b>
|
||||
</template>
|
||||
<SettingPicker color="success" v-model="set.goal" name="goal" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="success" v-if="['ganon', 'fast_ganon'].includes(this.set.goal)"
|
||||
v-model="set.crystals_ganon" name="crystals_ganon" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="success" v-model="set.crystals_gt" name="crystals_gt" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="false">
|
||||
<template #header>
|
||||
<b>Entrance Shuffle:</b>
|
||||
</template>
|
||||
<SettingPicker color="warning" v-model="set.entrance_shuffle" name="entrance_shuffle" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
|
||||
v-model="set.skull_woods" name="skull_woods" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
|
||||
v-model="set.linked_drops" name="linked_drops" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="false">
|
||||
<template #header>
|
||||
<b>Dungeon Shuffle:</b>
|
||||
</template>
|
||||
<SettingPicker color="danger" v-model="set.door_shuffle" name="door_shuffle" generator="base" />
|
||||
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
|
||||
v-model="set.door_lobbies" name="door_lobbies" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
|
||||
v-model="set.door_type_mode" name="door_type_mode" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
|
||||
v-model="set.trap_door_mode" name="trap_door_mode" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="false">
|
||||
<template #header>
|
||||
<b>Enemizer:</b>
|
||||
</template>
|
||||
<SettingPicker color="success" v-model="set.boss_shuffle" name="boss_shuffle" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="success" v-model="set.enemy_shuffle" name="enemy_shuffle" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="false">
|
||||
<template #header>
|
||||
<b>Dungeon Item Shuffle:</b>
|
||||
</template>
|
||||
<SettingPicker color="warning" v-model="set.small_keys" name="small_keys" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="warning" v-model="set.big_keys" name="big_keys" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="warning" v-model="set.maps" name="maps" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="warning" v-model="set.compasses" name="compasses" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="false">
|
||||
<template #header>
|
||||
<b>Other Pool Settings:</b>
|
||||
</template>
|
||||
<SettingPicker color="danger" v-model="set.shop_shuffle" name="shop_shuffle" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="danger" v-model="set.drop_shuffle" name="drop_shuffle" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="danger" v-model="set.pottery" name="pottery" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="danger" v-model="set.prize_shuffle" name="prize_shuffle" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
<AccordionItem :expanded="false">
|
||||
<template #header>
|
||||
<b>Item Settings:</b>
|
||||
</template>
|
||||
<SettingPicker color="primary" v-model="set.boots" name="boots" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="primary" v-model="set.flute" name="flute" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="primary" v-model="set.dark_rooms" name="dark_rooms" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="primary" v-model="set.bombs" name="bombs" generator="base" :prefix="prefix" />
|
||||
<SettingPicker color="primary" v-model="set.book" name="book" generator="base" :prefix="prefix" />
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user