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) {
|
settingsMatch(newSettings, preset) {
|
||||||
for (const settingName of Object.keys(newSettings)) {
|
for (const settingName of Object.keys(newSettings)) {
|
||||||
|
if (settingName == "player_name") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (preset[settingName] != newSettings[settingName]) {
|
if (preset[settingName] != newSettings[settingName]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export default defineComponent({
|
|||||||
patch: null,
|
patch: null,
|
||||||
error: null,
|
error: null,
|
||||||
settings: null,
|
settings: null,
|
||||||
|
multi: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -63,6 +64,7 @@ export default defineComponent({
|
|||||||
dataLoaded(patch, seedData) {
|
dataLoaded(patch, seedData) {
|
||||||
this.patch = patch;
|
this.patch = patch;
|
||||||
this.settings = seedData.settings;
|
this.settings = seedData.settings;
|
||||||
|
this.multi = seedData.parent;
|
||||||
},
|
},
|
||||||
async fetchSeed() {
|
async fetchSeed() {
|
||||||
await axios.get(`/seed/${this.id}`)
|
await axios.get(`/seed/${this.id}`)
|
||||||
@@ -175,7 +177,7 @@ export default defineComponent({
|
|||||||
</li>
|
</li>
|
||||||
<li v-if="settings" class="list-group-item">
|
<li v-if="settings" class="list-group-item">
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<SeedSettings :settings="settings" />
|
<SeedSettings :settings="settings" :multi="multi" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import mustache from "mustache";
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
settings: {},
|
settings: {},
|
||||||
|
multi: null,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
settingsDisplay() {
|
settingsDisplay() {
|
||||||
@@ -29,14 +30,25 @@ export default defineComponent({
|
|||||||
|| (this.settings.maps && this.settings.maps != "dungeon")
|
|| (this.settings.maps && this.settings.maps != "dungeon")
|
||||||
|| (this.settings.compasses && this.settings.compasses != "dungeon")
|
|| (this.settings.compasses && this.settings.compasses != "dungeon")
|
||||||
|| (this.settings.prize_shuffle && this.settings.prize_shuffle != "vanilla");
|
|| (this.settings.prize_shuffle && this.settings.prize_shuffle != "vanilla");
|
||||||
|
},
|
||||||
|
multilink() {
|
||||||
|
if (this.multi) {
|
||||||
|
return `/multi/${this.multi}`;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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]">
|
<div v-if="settings.randomizer && settingsDisplay.randomizer[settings.randomizer]">
|
||||||
{{ settingsDisplay.randomizer[settings.randomizer] }}
|
{{ settingsDisplay.randomizer[settings.randomizer] }}
|
||||||
<hr class="mt-2 mb-2" />
|
<hr class="mt-2 mb-2" />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export default defineComponent({
|
|||||||
name: null,
|
name: null,
|
||||||
color: "primary",
|
color: "primary",
|
||||||
generator: null,
|
generator: null,
|
||||||
|
prefix: null,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
async modelValue(newValue, oldValue) {
|
async modelValue(newValue, oldValue) {
|
||||||
@@ -44,7 +45,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
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();
|
this.change();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -53,7 +54,7 @@ export default defineComponent({
|
|||||||
await this.updateLocalForage();
|
await this.updateLocalForage();
|
||||||
},
|
},
|
||||||
async 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">
|
<div class="nav nav-pills nav-fill mt-1 mb-1">
|
||||||
<template v-for="value of settings.order ?? Object.keys(settings.values)">
|
<template v-for="value of settings.order ?? Object.keys(settings.values)">
|
||||||
<template v-if="settings.values[value]">
|
<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" />
|
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 }}
|
{{ settings.values[value].display }}
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</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>
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@ import router from "./router";
|
|||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||||
import "vue-select/dist/vue-select.css";
|
import "vue-select/dist/vue-select.css";
|
||||||
|
import "bootstrap/js/dist/tab.js";
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
|
|
||||||
import "./assets/main.css";
|
import "./assets/main.css";
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import SeedView from "@/views/SeedView.vue";
|
import SeedView from "@/views/SeedView.vue";
|
||||||
|
import MultiView from "@/views/MultiView.vue";
|
||||||
import GenerateView from "@/views/GenerateView.vue";
|
import GenerateView from "@/views/GenerateView.vue";
|
||||||
|
import GenerateMulti from "@/views/GenerateMulti.vue";
|
||||||
import GenerateApr2025View from "@/views/GenerateApr2025View.vue";
|
import GenerateApr2025View from "@/views/GenerateApr2025View.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -12,6 +14,12 @@ const router = createRouter({
|
|||||||
component: GenerateView,
|
component: GenerateView,
|
||||||
alias: '/',
|
alias: '/',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/multiworld',
|
||||||
|
name: 'multiworld',
|
||||||
|
component: GenerateMulti,
|
||||||
|
alias: '/multi',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/generate/apr2025',
|
path: '/generate/apr2025',
|
||||||
component: GenerateApr2025View,
|
component: GenerateApr2025View,
|
||||||
@@ -21,6 +29,11 @@ const router = createRouter({
|
|||||||
name: 'seed',
|
name: 'seed',
|
||||||
component: SeedView,
|
component: SeedView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/multi/:id',
|
||||||
|
name: 'multi',
|
||||||
|
component: MultiView,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
106
src/views/GenerateMulti.vue
Normal file
106
src/views/GenerateMulti.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import localforage from "localforage";
|
||||||
|
import { Modal } from "bootstrap";
|
||||||
|
|
||||||
|
import SettingsPage from "@/components/SettingsPage.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
SettingsPage,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
worlds: [{}, {}, {}],
|
||||||
|
worldCount: 3,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
document.title = "ALttPR Multiworld"
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
|
for (var i = 0; i < this.worldCount; i++) {
|
||||||
|
this.worlds[i].player_name = await localforage.getItem(`world_${i + 1}_setting_player_name`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async playerNameUpdated(num) {
|
||||||
|
await localforage.setItem(`world_${num + 1}_setting_player_name`, this.worlds[num].player_name);
|
||||||
|
},
|
||||||
|
async generate(race) {
|
||||||
|
const settings = [];
|
||||||
|
for (var i = 0; i < this.worldCount; i++) {
|
||||||
|
const world = {
|
||||||
|
randomizer: "base",
|
||||||
|
race: race ? "race" : "normal"
|
||||||
|
};
|
||||||
|
for (const setting of Object.keys(this.worlds[i])) {
|
||||||
|
world[setting] = this.worlds[i][setting];
|
||||||
|
}
|
||||||
|
if (!world.player_name) {
|
||||||
|
world.player_name = `Player ${i + 1}`;
|
||||||
|
}
|
||||||
|
settings.push(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios.post("/multiworld", settings)
|
||||||
|
.then(response => {
|
||||||
|
const id = response.data;
|
||||||
|
this.$router.push(`/multi/${id}`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card content-div m-3">
|
||||||
|
<div class="card-header">
|
||||||
|
Generate Multiworld
|
||||||
|
</div>
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li v-for="(n, idx) in worldCount" class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" :class="{active: idx == 0}" data-bs-toggle="tab"
|
||||||
|
:data-bs-target="`#world_page_${n}`" type="button" role="tab">
|
||||||
|
{{ worlds[idx]?.player_name ?? n }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<template v-for="(n, idx) in worldCount">
|
||||||
|
<div :id="`world_page_${n}`" class="tab-pane fade"
|
||||||
|
:class="{show: idx == 0, active: idx == 0}" role="tabpanel">
|
||||||
|
<div class="input-group ms-3 me-3 mt-3" style="width: inherit;">
|
||||||
|
<label class="input-group-text" :for="`player_${n}_name`">
|
||||||
|
Player {{ n }} Name:
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" :placeholder="`Player ${n}`"
|
||||||
|
id="`player_${n}_name`" v-model="worlds[idx].player_name"
|
||||||
|
@change="playerNameUpdated(idx)" />
|
||||||
|
</div>
|
||||||
|
<div id="settings" class="accordion accordion-flush">
|
||||||
|
<SettingsPage v-model="worlds[idx]" :prefix="`world_${n}_`" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="nav nav-pills nav-fill" role="group">
|
||||||
|
<button type="button" class="m-1 nav-item btn btn-outline-danger"
|
||||||
|
@click="generate(true);">
|
||||||
|
Generate Race ROM
|
||||||
|
</button>
|
||||||
|
<button type="button" class="m-1 nav-item btn btn-outline-primary"
|
||||||
|
@click="generate(false);">
|
||||||
|
Generate ROM
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -4,36 +4,19 @@ import { defineComponent } from "vue";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Modal } from "bootstrap";
|
import { Modal } from "bootstrap";
|
||||||
|
|
||||||
import AccordionItem from "@/components/AccordionItem.vue";
|
import SettingsPage from "@/components/SettingsPage.vue";
|
||||||
import PresetPicker from "@/components/PresetPicker.vue";
|
|
||||||
import SettingPicker from "@/components/SettingPicker.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
AccordionItem,
|
SettingsPage,
|
||||||
PresetPicker,
|
|
||||||
SettingPicker,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
set: {},
|
set: {},
|
||||||
existingLocalPresets: [],
|
|
||||||
newPresetName: "",
|
|
||||||
replacePreset: null,
|
|
||||||
modal: null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = "ALttPRandomizer"
|
document.title = "ALttPRandomizer"
|
||||||
this.modal = new Modal(document.getElementById("savePresetModal"), {});
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
set: {
|
|
||||||
handler(newValue, oldValue) {
|
|
||||||
this.$refs.preset.settingChanged(newValue);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async generate(race) {
|
async generate(race) {
|
||||||
@@ -53,154 +36,17 @@ export default defineComponent({
|
|||||||
console.log(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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 class="card content-div m-3">
|
<div class="card content-div m-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Generate Seed
|
Generate Seed
|
||||||
</div>
|
</div>
|
||||||
<div id="settings" class="accordion accordion-flush">
|
<div id="settings" class="accordion accordion-flush">
|
||||||
<AccordionItem>
|
<SettingsPage v-model="set" prefix="" />
|
||||||
<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" />
|
|
||||||
<SettingPicker color="primary" v-model="set.weapons" name="weapons" generator="base" />
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem :expanded="true">
|
|
||||||
<template #header>
|
|
||||||
<b>Goal:</b>
|
|
||||||
</template>
|
|
||||||
<SettingPicker color="success" v-model="set.goal" name="goal" generator="base" />
|
|
||||||
<SettingPicker color="success" v-if="['ganon', 'fast_ganon'].includes(this.set.goal)"
|
|
||||||
v-model="set.crystals_ganon" name="crystals_ganon" generator="base" />
|
|
||||||
<SettingPicker color="success" v-model="set.crystals_gt" name="crystals_gt" generator="base" />
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem :expanded="false">
|
|
||||||
<template #header>
|
|
||||||
<b>Entrance Shuffle:</b>
|
|
||||||
</template>
|
|
||||||
<SettingPicker color="warning" v-model="set.entrance_shuffle" name="entrance_shuffle" generator="base" />
|
|
||||||
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
|
|
||||||
v-model="set.skull_woods" name="skull_woods" generator="base" />
|
|
||||||
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
|
|
||||||
v-model="set.linked_drops" name="linked_drops" generator="base" />
|
|
||||||
</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" />
|
|
||||||
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
|
|
||||||
v-model="set.door_type_mode" name="door_type_mode" generator="base" />
|
|
||||||
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
|
|
||||||
v-model="set.trap_door_mode" name="trap_door_mode" generator="base" />
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem :expanded="false">
|
|
||||||
<template #header>
|
|
||||||
<b>Enemizer:</b>
|
|
||||||
</template>
|
|
||||||
<SettingPicker color="success" v-model="set.boss_shuffle" name="boss_shuffle" generator="base" />
|
|
||||||
<SettingPicker color="success" v-model="set.enemy_shuffle" name="enemy_shuffle" generator="base" />
|
|
||||||
</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" />
|
|
||||||
<SettingPicker color="warning" v-model="set.big_keys" name="big_keys" generator="base" />
|
|
||||||
<SettingPicker color="warning" v-model="set.maps" name="maps" generator="base" />
|
|
||||||
<SettingPicker color="warning" v-model="set.compasses" name="compasses" generator="base" />
|
|
||||||
</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" />
|
|
||||||
<SettingPicker color="danger" v-model="set.drop_shuffle" name="drop_shuffle" generator="base" />
|
|
||||||
<SettingPicker color="danger" v-model="set.pottery" name="pottery" generator="base" />
|
|
||||||
<SettingPicker color="danger" v-model="set.prize_shuffle" name="prize_shuffle" generator="base" />
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem :expanded="false">
|
|
||||||
<template #header>
|
|
||||||
<b>Item Settings:</b>
|
|
||||||
</template>
|
|
||||||
<SettingPicker color="primary" v-model="set.boots" name="boots" generator="base" />
|
|
||||||
<SettingPicker color="primary" v-model="set.flute" name="flute" generator="base" />
|
|
||||||
<SettingPicker color="primary" v-model="set.dark_rooms" name="dark_rooms" generator="base" />
|
|
||||||
<SettingPicker color="primary" v-model="set.bombs" name="bombs" generator="base" />
|
|
||||||
<SettingPicker color="primary" v-model="set.book" name="book" generator="base" />
|
|
||||||
</AccordionItem>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="nav nav-pills nav-fill" role="group">
|
<div class="nav nav-pills nav-fill" role="group">
|
||||||
|
|||||||
15
src/views/MultiView.vue
Normal file
15
src/views/MultiView.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import Multi from "@/components/Multi.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
Multi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Multi :id="$route.params.id" />
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
import Seed from '../components/Seed.vue';
|
import Seed from "@/components/Seed.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
Reference in New Issue
Block a user