Core: Introduce new Option class NamedRange (#2330)
Co-authored-by: Chris Wilson <chris@legendserver.info> Co-authored-by: Zach Parks <zach@alliware.com>
This commit is contained in:
parent
e64c7b1cbb
commit
c944ecf628
29
Options.py
29
Options.py
|
@ -696,11 +696,19 @@ class Range(NumericOption):
|
||||||
return int(round(random.triangular(lower, end, tri), 0))
|
return int(round(random.triangular(lower, end, tri), 0))
|
||||||
|
|
||||||
|
|
||||||
class SpecialRange(Range):
|
class NamedRange(Range):
|
||||||
special_range_cutoff = 0
|
|
||||||
special_range_names: typing.Dict[str, int] = {}
|
special_range_names: typing.Dict[str, int] = {}
|
||||||
"""Special Range names have to be all lowercase as matching is done with text.lower()"""
|
"""Special Range names have to be all lowercase as matching is done with text.lower()"""
|
||||||
|
|
||||||
|
def __init__(self, value: int) -> None:
|
||||||
|
if value < self.range_start and value not in self.special_range_names.values():
|
||||||
|
raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__} " +
|
||||||
|
f"and is also not one of the supported named special values: {self.special_range_names}")
|
||||||
|
elif value > self.range_end and value not in self.special_range_names.values():
|
||||||
|
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__} " +
|
||||||
|
f"and is also not one of the supported named special values: {self.special_range_names}")
|
||||||
|
self.value = value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_text(cls, text: str) -> Range:
|
def from_text(cls, text: str) -> Range:
|
||||||
text = text.lower()
|
text = text.lower()
|
||||||
|
@ -708,6 +716,19 @@ class SpecialRange(Range):
|
||||||
return cls(cls.special_range_names[text])
|
return cls(cls.special_range_names[text])
|
||||||
return super().from_text(text)
|
return super().from_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialRange(NamedRange):
|
||||||
|
special_range_cutoff = 0
|
||||||
|
|
||||||
|
# TODO: remove class SpecialRange, earliest 3 releases after 0.4.3
|
||||||
|
def __new__(cls, value: int) -> SpecialRange:
|
||||||
|
from Utils import deprecate
|
||||||
|
deprecate(f"Option type {cls.__name__} is a subclass of SpecialRange, which is deprecated and pending removal. "
|
||||||
|
"Consider switching to NamedRange, which supports all use-cases of SpecialRange, and more. In "
|
||||||
|
"NamedRange, range_start specifies the lower end of the regular range, while special values can be "
|
||||||
|
"placed anywhere (below, inside, or above the regular range).")
|
||||||
|
return super().__new__(cls, value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def weighted_range(cls, text) -> Range:
|
def weighted_range(cls, text) -> Range:
|
||||||
if text == "random-low":
|
if text == "random-low":
|
||||||
|
@ -891,7 +912,7 @@ class Accessibility(Choice):
|
||||||
default = 1
|
default = 1
|
||||||
|
|
||||||
|
|
||||||
class ProgressionBalancing(SpecialRange):
|
class ProgressionBalancing(NamedRange):
|
||||||
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
||||||
A lower setting means more getting stuck. A higher setting means less getting stuck."""
|
A lower setting means more getting stuck. A higher setting means less getting stuck."""
|
||||||
default = 50
|
default = 50
|
||||||
|
@ -1108,7 +1129,7 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||||
if os.path.isfile(full_path) and full_path.endswith(".yaml"):
|
if os.path.isfile(full_path) and full_path.endswith(".yaml"):
|
||||||
os.unlink(full_path)
|
os.unlink(full_path)
|
||||||
|
|
||||||
def dictify_range(option: typing.Union[Range, SpecialRange]):
|
def dictify_range(option: Range):
|
||||||
data = {option.default: 50}
|
data = {option.default: 50}
|
||||||
for sub_option in ["random", "random-low", "random-high"]:
|
for sub_option in ["random", "random-low", "random-high"]:
|
||||||
if sub_option != option.default:
|
if sub_option != option.default:
|
||||||
|
|
|
@ -81,8 +81,8 @@ def create():
|
||||||
"max": option.range_end,
|
"max": option.range_end,
|
||||||
}
|
}
|
||||||
|
|
||||||
if issubclass(option, Options.SpecialRange):
|
if issubclass(option, Options.NamedRange):
|
||||||
game_options[option_name]["type"] = 'special_range'
|
game_options[option_name]["type"] = 'named_range'
|
||||||
game_options[option_name]["value_names"] = {}
|
game_options[option_name]["value_names"] = {}
|
||||||
for key, val in option.special_range_names.items():
|
for key, val in option.special_range_names.items():
|
||||||
game_options[option_name]["value_names"][key] = val
|
game_options[option_name]["value_names"][key] = val
|
||||||
|
@ -133,7 +133,7 @@ def create():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
option = world.options_dataclass.type_hints[option_name].from_any(option_value)
|
option = world.options_dataclass.type_hints[option_name].from_any(option_value)
|
||||||
if isinstance(option, Options.SpecialRange) and isinstance(option_value, str):
|
if isinstance(option, Options.NamedRange) and isinstance(option_value, str):
|
||||||
assert option_value in option.special_range_names, \
|
assert option_value in option.special_range_names, \
|
||||||
f"Invalid preset value '{option_value}' for '{option_name}' in '{preset_name}'. " \
|
f"Invalid preset value '{option_value}' for '{option_name}' in '{preset_name}'. " \
|
||||||
f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}."
|
f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}."
|
||||||
|
|
|
@ -216,13 +216,13 @@ const buildOptionsTable = (options, romOpts = false) => {
|
||||||
element.appendChild(randomButton);
|
element.appendChild(randomButton);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'special_range':
|
case 'named_range':
|
||||||
element = document.createElement('div');
|
element = document.createElement('div');
|
||||||
element.classList.add('special-range-container');
|
element.classList.add('named-range-container');
|
||||||
|
|
||||||
// Build the select element
|
// Build the select element
|
||||||
let specialRangeSelect = document.createElement('select');
|
let namedRangeSelect = document.createElement('select');
|
||||||
specialRangeSelect.setAttribute('data-key', option);
|
namedRangeSelect.setAttribute('data-key', option);
|
||||||
Object.keys(options[option].value_names).forEach((presetName) => {
|
Object.keys(options[option].value_names).forEach((presetName) => {
|
||||||
let presetOption = document.createElement('option');
|
let presetOption = document.createElement('option');
|
||||||
presetOption.innerText = presetName;
|
presetOption.innerText = presetName;
|
||||||
|
@ -232,58 +232,58 @@ const buildOptionsTable = (options, romOpts = false) => {
|
||||||
words[i] = words[i][0].toUpperCase() + words[i].substring(1);
|
words[i] = words[i][0].toUpperCase() + words[i].substring(1);
|
||||||
}
|
}
|
||||||
presetOption.innerText = words.join(' ');
|
presetOption.innerText = words.join(' ');
|
||||||
specialRangeSelect.appendChild(presetOption);
|
namedRangeSelect.appendChild(presetOption);
|
||||||
});
|
});
|
||||||
let customOption = document.createElement('option');
|
let customOption = document.createElement('option');
|
||||||
customOption.innerText = 'Custom';
|
customOption.innerText = 'Custom';
|
||||||
customOption.value = 'custom';
|
customOption.value = 'custom';
|
||||||
customOption.selected = true;
|
customOption.selected = true;
|
||||||
specialRangeSelect.appendChild(customOption);
|
namedRangeSelect.appendChild(customOption);
|
||||||
if (Object.values(options[option].value_names).includes(Number(currentOptions[gameName][option]))) {
|
if (Object.values(options[option].value_names).includes(Number(currentOptions[gameName][option]))) {
|
||||||
specialRangeSelect.value = Number(currentOptions[gameName][option]);
|
namedRangeSelect.value = Number(currentOptions[gameName][option]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build range element
|
// Build range element
|
||||||
let specialRangeWrapper = document.createElement('div');
|
let namedRangeWrapper = document.createElement('div');
|
||||||
specialRangeWrapper.classList.add('special-range-wrapper');
|
namedRangeWrapper.classList.add('named-range-wrapper');
|
||||||
let specialRange = document.createElement('input');
|
let namedRange = document.createElement('input');
|
||||||
specialRange.setAttribute('type', 'range');
|
namedRange.setAttribute('type', 'range');
|
||||||
specialRange.setAttribute('data-key', option);
|
namedRange.setAttribute('data-key', option);
|
||||||
specialRange.setAttribute('min', options[option].min);
|
namedRange.setAttribute('min', options[option].min);
|
||||||
specialRange.setAttribute('max', options[option].max);
|
namedRange.setAttribute('max', options[option].max);
|
||||||
specialRange.value = currentOptions[gameName][option];
|
namedRange.value = currentOptions[gameName][option];
|
||||||
|
|
||||||
// Build rage value element
|
// Build rage value element
|
||||||
let specialRangeVal = document.createElement('span');
|
let namedRangeVal = document.createElement('span');
|
||||||
specialRangeVal.classList.add('range-value');
|
namedRangeVal.classList.add('range-value');
|
||||||
specialRangeVal.setAttribute('id', `${option}-value`);
|
namedRangeVal.setAttribute('id', `${option}-value`);
|
||||||
specialRangeVal.innerText = currentOptions[gameName][option] !== 'random' ?
|
namedRangeVal.innerText = currentOptions[gameName][option] !== 'random' ?
|
||||||
currentOptions[gameName][option] : options[option].defaultValue;
|
currentOptions[gameName][option] : options[option].defaultValue;
|
||||||
|
|
||||||
// Configure select event listener
|
// Configure select event listener
|
||||||
specialRangeSelect.addEventListener('change', (event) => {
|
namedRangeSelect.addEventListener('change', (event) => {
|
||||||
if (event.target.value === 'custom') { return; }
|
if (event.target.value === 'custom') { return; }
|
||||||
|
|
||||||
// Update range slider
|
// Update range slider
|
||||||
specialRange.value = event.target.value;
|
namedRange.value = event.target.value;
|
||||||
document.getElementById(`${option}-value`).innerText = event.target.value;
|
document.getElementById(`${option}-value`).innerText = event.target.value;
|
||||||
updateGameOption(event.target);
|
updateGameOption(event.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure range event handler
|
// Configure range event handler
|
||||||
specialRange.addEventListener('change', (event) => {
|
namedRange.addEventListener('change', (event) => {
|
||||||
// Update select element
|
// Update select element
|
||||||
specialRangeSelect.value =
|
namedRangeSelect.value =
|
||||||
(Object.values(options[option].value_names).includes(parseInt(event.target.value))) ?
|
(Object.values(options[option].value_names).includes(parseInt(event.target.value))) ?
|
||||||
parseInt(event.target.value) : 'custom';
|
parseInt(event.target.value) : 'custom';
|
||||||
document.getElementById(`${option}-value`).innerText = event.target.value;
|
document.getElementById(`${option}-value`).innerText = event.target.value;
|
||||||
updateGameOption(event.target);
|
updateGameOption(event.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
element.appendChild(specialRangeSelect);
|
element.appendChild(namedRangeSelect);
|
||||||
specialRangeWrapper.appendChild(specialRange);
|
namedRangeWrapper.appendChild(namedRange);
|
||||||
specialRangeWrapper.appendChild(specialRangeVal);
|
namedRangeWrapper.appendChild(namedRangeVal);
|
||||||
element.appendChild(specialRangeWrapper);
|
element.appendChild(namedRangeWrapper);
|
||||||
|
|
||||||
// Randomize button
|
// Randomize button
|
||||||
randomButton.innerText = '🎲';
|
randomButton.innerText = '🎲';
|
||||||
|
@ -291,15 +291,15 @@ const buildOptionsTable = (options, romOpts = false) => {
|
||||||
randomButton.setAttribute('data-key', option);
|
randomButton.setAttribute('data-key', option);
|
||||||
randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!');
|
randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!');
|
||||||
randomButton.addEventListener('click', (event) => toggleRandomize(
|
randomButton.addEventListener('click', (event) => toggleRandomize(
|
||||||
event, specialRange, specialRangeSelect)
|
event, namedRange, namedRangeSelect)
|
||||||
);
|
);
|
||||||
if (currentOptions[gameName][option] === 'random') {
|
if (currentOptions[gameName][option] === 'random') {
|
||||||
randomButton.classList.add('active');
|
randomButton.classList.add('active');
|
||||||
specialRange.disabled = true;
|
namedRange.disabled = true;
|
||||||
specialRangeSelect.disabled = true;
|
namedRangeSelect.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
specialRangeWrapper.appendChild(randomButton);
|
namedRangeWrapper.appendChild(randomButton);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -93,9 +93,10 @@ class WeightedSettings {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'range':
|
case 'range':
|
||||||
case 'special_range':
|
case 'named_range':
|
||||||
this.current[game][gameSetting]['random'] = 0;
|
this.current[game][gameSetting]['random'] = 0;
|
||||||
this.current[game][gameSetting]['random-low'] = 0;
|
this.current[game][gameSetting]['random-low'] = 0;
|
||||||
|
this.current[game][gameSetting]['random-middle'] = 0;
|
||||||
this.current[game][gameSetting]['random-high'] = 0;
|
this.current[game][gameSetting]['random-high'] = 0;
|
||||||
if (setting.hasOwnProperty('defaultValue')) {
|
if (setting.hasOwnProperty('defaultValue')) {
|
||||||
this.current[game][gameSetting][setting.defaultValue] = 25;
|
this.current[game][gameSetting][setting.defaultValue] = 25;
|
||||||
|
@ -522,178 +523,185 @@ class GameSettings {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'range':
|
case 'range':
|
||||||
case 'special_range':
|
case 'named_range':
|
||||||
const rangeTable = document.createElement('table');
|
const rangeTable = document.createElement('table');
|
||||||
const rangeTbody = document.createElement('tbody');
|
const rangeTbody = document.createElement('tbody');
|
||||||
|
|
||||||
if (((setting.max - setting.min) + 1) < 11) {
|
const hintText = document.createElement('p');
|
||||||
for (let i=setting.min; i <= setting.max; ++i) {
|
hintText.classList.add('hint-text');
|
||||||
const tr = document.createElement('tr');
|
hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' +
|
||||||
const tdLeft = document.createElement('td');
|
`below, then press the "Add" button to add a weight for it.<br /><br />Accepted values:<br />` +
|
||||||
tdLeft.classList.add('td-left');
|
`Normal range: ${setting.min} - ${setting.max}`;
|
||||||
tdLeft.innerText = i;
|
|
||||||
tr.appendChild(tdLeft);
|
|
||||||
|
|
||||||
const tdMiddle = document.createElement('td');
|
const acceptedValuesOutsideRange = [];
|
||||||
tdMiddle.classList.add('td-middle');
|
if (setting.hasOwnProperty('value_names')) {
|
||||||
const range = document.createElement('input');
|
Object.keys(setting.value_names).forEach((specialName) => {
|
||||||
range.setAttribute('type', 'range');
|
if (
|
||||||
range.setAttribute('id', `${this.name}-${settingName}-${i}-range`);
|
(setting.value_names[specialName] < setting.min) ||
|
||||||
range.setAttribute('data-game', this.name);
|
(setting.value_names[specialName] > setting.max)
|
||||||
range.setAttribute('data-setting', settingName);
|
) {
|
||||||
range.setAttribute('data-option', i);
|
|
||||||
range.setAttribute('min', 0);
|
|
||||||
range.setAttribute('max', 50);
|
|
||||||
range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
|
|
||||||
range.value = this.current[settingName][i] || 0;
|
|
||||||
tdMiddle.appendChild(range);
|
|
||||||
tr.appendChild(tdMiddle);
|
|
||||||
|
|
||||||
const tdRight = document.createElement('td');
|
|
||||||
tdRight.setAttribute('id', `${this.name}-${settingName}-${i}`)
|
|
||||||
tdRight.classList.add('td-right');
|
|
||||||
tdRight.innerText = range.value;
|
|
||||||
tr.appendChild(tdRight);
|
|
||||||
|
|
||||||
rangeTbody.appendChild(tr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const hintText = document.createElement('p');
|
|
||||||
hintText.classList.add('hint-text');
|
|
||||||
hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' +
|
|
||||||
`below, then press the "Add" button to add a weight for it.<br />Minimum value: ${setting.min}<br />` +
|
|
||||||
`Maximum value: ${setting.max}`;
|
|
||||||
|
|
||||||
if (setting.hasOwnProperty('value_names')) {
|
|
||||||
hintText.innerHTML += '<br /><br />Certain values have special meaning:';
|
|
||||||
Object.keys(setting.value_names).forEach((specialName) => {
|
|
||||||
hintText.innerHTML += `<br />${specialName}: ${setting.value_names[specialName]}`;
|
hintText.innerHTML += `<br />${specialName}: ${setting.value_names[specialName]}`;
|
||||||
});
|
acceptedValuesOutsideRange.push(setting.value_names[specialName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
settingWrapper.appendChild(hintText);
|
|
||||||
|
|
||||||
const addOptionDiv = document.createElement('div');
|
|
||||||
addOptionDiv.classList.add('add-option-div');
|
|
||||||
const optionInput = document.createElement('input');
|
|
||||||
optionInput.setAttribute('id', `${this.name}-${settingName}-option`);
|
|
||||||
optionInput.setAttribute('placeholder', `${setting.min} - ${setting.max}`);
|
|
||||||
addOptionDiv.appendChild(optionInput);
|
|
||||||
const addOptionButton = document.createElement('button');
|
|
||||||
addOptionButton.innerText = 'Add';
|
|
||||||
addOptionDiv.appendChild(addOptionButton);
|
|
||||||
settingWrapper.appendChild(addOptionDiv);
|
|
||||||
optionInput.addEventListener('keydown', (evt) => {
|
|
||||||
if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addOptionButton.addEventListener('click', () => {
|
hintText.innerHTML += '<br /><br />Certain values have special meaning:';
|
||||||
const optionInput = document.getElementById(`${this.name}-${settingName}-option`);
|
Object.keys(setting.value_names).forEach((specialName) => {
|
||||||
let option = optionInput.value;
|
hintText.innerHTML += `<br />${specialName}: ${setting.value_names[specialName]}`;
|
||||||
if (!option || !option.trim()) { return; }
|
|
||||||
option = parseInt(option, 10);
|
|
||||||
if ((option < setting.min) || (option > setting.max)) { return; }
|
|
||||||
optionInput.value = '';
|
|
||||||
if (document.getElementById(`${this.name}-${settingName}-${option}-range`)) { return; }
|
|
||||||
|
|
||||||
const tr = document.createElement('tr');
|
|
||||||
const tdLeft = document.createElement('td');
|
|
||||||
tdLeft.classList.add('td-left');
|
|
||||||
tdLeft.innerText = option;
|
|
||||||
tr.appendChild(tdLeft);
|
|
||||||
|
|
||||||
const tdMiddle = document.createElement('td');
|
|
||||||
tdMiddle.classList.add('td-middle');
|
|
||||||
const range = document.createElement('input');
|
|
||||||
range.setAttribute('type', 'range');
|
|
||||||
range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
|
|
||||||
range.setAttribute('data-game', this.name);
|
|
||||||
range.setAttribute('data-setting', settingName);
|
|
||||||
range.setAttribute('data-option', option);
|
|
||||||
range.setAttribute('min', 0);
|
|
||||||
range.setAttribute('max', 50);
|
|
||||||
range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
|
|
||||||
range.value = this.current[settingName][parseInt(option, 10)];
|
|
||||||
tdMiddle.appendChild(range);
|
|
||||||
tr.appendChild(tdMiddle);
|
|
||||||
|
|
||||||
const tdRight = document.createElement('td');
|
|
||||||
tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
|
|
||||||
tdRight.classList.add('td-right');
|
|
||||||
tdRight.innerText = range.value;
|
|
||||||
tr.appendChild(tdRight);
|
|
||||||
|
|
||||||
const tdDelete = document.createElement('td');
|
|
||||||
tdDelete.classList.add('td-delete');
|
|
||||||
const deleteButton = document.createElement('span');
|
|
||||||
deleteButton.classList.add('range-option-delete');
|
|
||||||
deleteButton.innerText = '❌';
|
|
||||||
deleteButton.addEventListener('click', () => {
|
|
||||||
range.value = 0;
|
|
||||||
range.dispatchEvent(new Event('change'));
|
|
||||||
rangeTbody.removeChild(tr);
|
|
||||||
});
|
|
||||||
tdDelete.appendChild(deleteButton);
|
|
||||||
tr.appendChild(tdDelete);
|
|
||||||
|
|
||||||
rangeTbody.appendChild(tr);
|
|
||||||
|
|
||||||
// Save new option to settings
|
|
||||||
range.dispatchEvent(new Event('change'));
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(this.current[settingName]).forEach((option) => {
|
|
||||||
// These options are statically generated below, and should always appear even if they are deleted
|
|
||||||
// from localStorage
|
|
||||||
if (['random-low', 'random', 'random-high'].includes(option)) { return; }
|
|
||||||
|
|
||||||
const tr = document.createElement('tr');
|
|
||||||
const tdLeft = document.createElement('td');
|
|
||||||
tdLeft.classList.add('td-left');
|
|
||||||
tdLeft.innerText = option;
|
|
||||||
tr.appendChild(tdLeft);
|
|
||||||
|
|
||||||
const tdMiddle = document.createElement('td');
|
|
||||||
tdMiddle.classList.add('td-middle');
|
|
||||||
const range = document.createElement('input');
|
|
||||||
range.setAttribute('type', 'range');
|
|
||||||
range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
|
|
||||||
range.setAttribute('data-game', this.name);
|
|
||||||
range.setAttribute('data-setting', settingName);
|
|
||||||
range.setAttribute('data-option', option);
|
|
||||||
range.setAttribute('min', 0);
|
|
||||||
range.setAttribute('max', 50);
|
|
||||||
range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
|
|
||||||
range.value = this.current[settingName][parseInt(option, 10)];
|
|
||||||
tdMiddle.appendChild(range);
|
|
||||||
tr.appendChild(tdMiddle);
|
|
||||||
|
|
||||||
const tdRight = document.createElement('td');
|
|
||||||
tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
|
|
||||||
tdRight.classList.add('td-right');
|
|
||||||
tdRight.innerText = range.value;
|
|
||||||
tr.appendChild(tdRight);
|
|
||||||
|
|
||||||
const tdDelete = document.createElement('td');
|
|
||||||
tdDelete.classList.add('td-delete');
|
|
||||||
const deleteButton = document.createElement('span');
|
|
||||||
deleteButton.classList.add('range-option-delete');
|
|
||||||
deleteButton.innerText = '❌';
|
|
||||||
deleteButton.addEventListener('click', () => {
|
|
||||||
range.value = 0;
|
|
||||||
const changeEvent = new Event('change');
|
|
||||||
changeEvent.action = 'rangeDelete';
|
|
||||||
range.dispatchEvent(changeEvent);
|
|
||||||
rangeTbody.removeChild(tr);
|
|
||||||
});
|
|
||||||
tdDelete.appendChild(deleteButton);
|
|
||||||
tr.appendChild(tdDelete);
|
|
||||||
|
|
||||||
rangeTbody.appendChild(tr);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
['random', 'random-low', 'random-high'].forEach((option) => {
|
settingWrapper.appendChild(hintText);
|
||||||
|
|
||||||
|
const addOptionDiv = document.createElement('div');
|
||||||
|
addOptionDiv.classList.add('add-option-div');
|
||||||
|
const optionInput = document.createElement('input');
|
||||||
|
optionInput.setAttribute('id', `${this.name}-${settingName}-option`);
|
||||||
|
let placeholderText = `${setting.min} - ${setting.max}`;
|
||||||
|
acceptedValuesOutsideRange.forEach((aVal) => placeholderText += `, ${aVal}`);
|
||||||
|
optionInput.setAttribute('placeholder', placeholderText);
|
||||||
|
addOptionDiv.appendChild(optionInput);
|
||||||
|
const addOptionButton = document.createElement('button');
|
||||||
|
addOptionButton.innerText = 'Add';
|
||||||
|
addOptionDiv.appendChild(addOptionButton);
|
||||||
|
settingWrapper.appendChild(addOptionDiv);
|
||||||
|
optionInput.addEventListener('keydown', (evt) => {
|
||||||
|
if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); }
|
||||||
|
});
|
||||||
|
|
||||||
|
addOptionButton.addEventListener('click', () => {
|
||||||
|
const optionInput = document.getElementById(`${this.name}-${settingName}-option`);
|
||||||
|
let option = optionInput.value;
|
||||||
|
if (!option || !option.trim()) { return; }
|
||||||
|
option = parseInt(option, 10);
|
||||||
|
|
||||||
|
let optionAcceptable = false;
|
||||||
|
if ((option > setting.min) && (option < setting.max)) {
|
||||||
|
optionAcceptable = true;
|
||||||
|
}
|
||||||
|
if (setting.hasOwnProperty('value_names') && Object.values(setting.value_names).includes(option)){
|
||||||
|
optionAcceptable = true;
|
||||||
|
}
|
||||||
|
if (!optionAcceptable) { return; }
|
||||||
|
|
||||||
|
optionInput.value = '';
|
||||||
|
if (document.getElementById(`${this.name}-${settingName}-${option}-range`)) { return; }
|
||||||
|
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const tdLeft = document.createElement('td');
|
||||||
|
tdLeft.classList.add('td-left');
|
||||||
|
tdLeft.innerText = option;
|
||||||
|
if (
|
||||||
|
setting.hasOwnProperty('value_names') &&
|
||||||
|
Object.values(setting.value_names).includes(parseInt(option, 10))
|
||||||
|
) {
|
||||||
|
const optionName = Object.keys(setting.value_names).find(
|
||||||
|
(key) => setting.value_names[key] === parseInt(option, 10)
|
||||||
|
);
|
||||||
|
tdLeft.innerText += ` [${optionName}]`;
|
||||||
|
}
|
||||||
|
tr.appendChild(tdLeft);
|
||||||
|
|
||||||
|
const tdMiddle = document.createElement('td');
|
||||||
|
tdMiddle.classList.add('td-middle');
|
||||||
|
const range = document.createElement('input');
|
||||||
|
range.setAttribute('type', 'range');
|
||||||
|
range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
|
||||||
|
range.setAttribute('data-game', this.name);
|
||||||
|
range.setAttribute('data-setting', settingName);
|
||||||
|
range.setAttribute('data-option', option);
|
||||||
|
range.setAttribute('min', 0);
|
||||||
|
range.setAttribute('max', 50);
|
||||||
|
range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
|
||||||
|
range.value = this.current[settingName][parseInt(option, 10)];
|
||||||
|
tdMiddle.appendChild(range);
|
||||||
|
tr.appendChild(tdMiddle);
|
||||||
|
|
||||||
|
const tdRight = document.createElement('td');
|
||||||
|
tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
|
||||||
|
tdRight.classList.add('td-right');
|
||||||
|
tdRight.innerText = range.value;
|
||||||
|
tr.appendChild(tdRight);
|
||||||
|
|
||||||
|
const tdDelete = document.createElement('td');
|
||||||
|
tdDelete.classList.add('td-delete');
|
||||||
|
const deleteButton = document.createElement('span');
|
||||||
|
deleteButton.classList.add('range-option-delete');
|
||||||
|
deleteButton.innerText = '❌';
|
||||||
|
deleteButton.addEventListener('click', () => {
|
||||||
|
range.value = 0;
|
||||||
|
range.dispatchEvent(new Event('change'));
|
||||||
|
rangeTbody.removeChild(tr);
|
||||||
|
});
|
||||||
|
tdDelete.appendChild(deleteButton);
|
||||||
|
tr.appendChild(tdDelete);
|
||||||
|
|
||||||
|
rangeTbody.appendChild(tr);
|
||||||
|
|
||||||
|
// Save new option to settings
|
||||||
|
range.dispatchEvent(new Event('change'));
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(this.current[settingName]).forEach((option) => {
|
||||||
|
// These options are statically generated below, and should always appear even if they are deleted
|
||||||
|
// from localStorage
|
||||||
|
if (['random', 'random-low', 'random-middle', 'random-high'].includes(option)) { return; }
|
||||||
|
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const tdLeft = document.createElement('td');
|
||||||
|
tdLeft.classList.add('td-left');
|
||||||
|
tdLeft.innerText = option;
|
||||||
|
if (
|
||||||
|
setting.hasOwnProperty('value_names') &&
|
||||||
|
Object.values(setting.value_names).includes(parseInt(option, 10))
|
||||||
|
) {
|
||||||
|
const optionName = Object.keys(setting.value_names).find(
|
||||||
|
(key) => setting.value_names[key] === parseInt(option, 10)
|
||||||
|
);
|
||||||
|
tdLeft.innerText += ` [${optionName}]`;
|
||||||
|
}
|
||||||
|
tr.appendChild(tdLeft);
|
||||||
|
|
||||||
|
const tdMiddle = document.createElement('td');
|
||||||
|
tdMiddle.classList.add('td-middle');
|
||||||
|
const range = document.createElement('input');
|
||||||
|
range.setAttribute('type', 'range');
|
||||||
|
range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
|
||||||
|
range.setAttribute('data-game', this.name);
|
||||||
|
range.setAttribute('data-setting', settingName);
|
||||||
|
range.setAttribute('data-option', option);
|
||||||
|
range.setAttribute('min', 0);
|
||||||
|
range.setAttribute('max', 50);
|
||||||
|
range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
|
||||||
|
range.value = this.current[settingName][parseInt(option, 10)];
|
||||||
|
tdMiddle.appendChild(range);
|
||||||
|
tr.appendChild(tdMiddle);
|
||||||
|
|
||||||
|
const tdRight = document.createElement('td');
|
||||||
|
tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
|
||||||
|
tdRight.classList.add('td-right');
|
||||||
|
tdRight.innerText = range.value;
|
||||||
|
tr.appendChild(tdRight);
|
||||||
|
|
||||||
|
const tdDelete = document.createElement('td');
|
||||||
|
tdDelete.classList.add('td-delete');
|
||||||
|
const deleteButton = document.createElement('span');
|
||||||
|
deleteButton.classList.add('range-option-delete');
|
||||||
|
deleteButton.innerText = '❌';
|
||||||
|
deleteButton.addEventListener('click', () => {
|
||||||
|
range.value = 0;
|
||||||
|
const changeEvent = new Event('change');
|
||||||
|
changeEvent.action = 'rangeDelete';
|
||||||
|
range.dispatchEvent(changeEvent);
|
||||||
|
rangeTbody.removeChild(tr);
|
||||||
|
});
|
||||||
|
tdDelete.appendChild(deleteButton);
|
||||||
|
tr.appendChild(tdDelete);
|
||||||
|
|
||||||
|
rangeTbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
['random', 'random-low', 'random-middle', 'random-high'].forEach((option) => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
const tdLeft = document.createElement('td');
|
const tdLeft = document.createElement('td');
|
||||||
tdLeft.classList.add('td-left');
|
tdLeft.classList.add('td-left');
|
||||||
|
@ -704,6 +712,9 @@ class GameSettings {
|
||||||
case 'random-low':
|
case 'random-low':
|
||||||
tdLeft.innerText = "Random (Low)";
|
tdLeft.innerText = "Random (Low)";
|
||||||
break;
|
break;
|
||||||
|
case 'random-middle':
|
||||||
|
tdLeft.innerText = 'Random (Middle)';
|
||||||
|
break;
|
||||||
case 'random-high':
|
case 'random-high':
|
||||||
tdLeft.innerText = "Random (High)";
|
tdLeft.innerText = "Random (High)";
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -160,18 +160,18 @@ html{
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player-options table .special-range-container{
|
#player-options table .named-range-container{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player-options table .special-range-wrapper{
|
#player-options table .named-range-wrapper{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player-options table .special-range-wrapper input[type=range]{
|
#player-options table .named-range-wrapper input[type=range]{
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,13 +144,20 @@ A numeric option allowing a variety of integers including the endpoints. Has a d
|
||||||
`range_end` of 1. Allows for negative values as well. This will always be an integer and has no methods for string
|
`range_end` of 1. Allows for negative values as well. This will always be an integer and has no methods for string
|
||||||
comparisons.
|
comparisons.
|
||||||
|
|
||||||
### SpecialRange
|
### NamedRange
|
||||||
Like range but also allows you to define a dictionary of special names the user can use to equate to a specific value.
|
Like range but also allows you to define a dictionary of special names the user can use to equate to a specific value.
|
||||||
|
`special_range_names` can be used to
|
||||||
|
- give descriptive names to certain values from within the range
|
||||||
|
- add option values above or below the regular range, to be associated with a special meaning
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```python
|
```python
|
||||||
|
range_start = 1
|
||||||
|
range_end = 99
|
||||||
special_range_names: {
|
special_range_names: {
|
||||||
"normal": 20,
|
"normal": 20,
|
||||||
"extreme": 99,
|
"extreme": 99,
|
||||||
|
"unlimited": -1,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -79,9 +79,9 @@ the options and the values are the values to be set for that option. These prese
|
||||||
|
|
||||||
Note: The values must be a non-aliased value for the option type and can only include the following option types:
|
Note: The values must be a non-aliased value for the option type and can only include the following option types:
|
||||||
|
|
||||||
- If you have a `Range`/`SpecialRange` option, the value should be an `int` between the `range_start` and `range_end`
|
- If you have a `Range`/`NamedRange` option, the value should be an `int` between the `range_start` and `range_end`
|
||||||
values.
|
values.
|
||||||
- If you have a `SpecialRange` option, the value can alternatively be a `str` that is one of the
|
- If you have a `NamedRange` option, the value can alternatively be a `str` that is one of the
|
||||||
`special_range_names` keys.
|
`special_range_names` keys.
|
||||||
- If you have a `Choice` option, the value should be a `str` that is one of the `option_<name>` values.
|
- If you have a `Choice` option, the value should be a `str` that is one of the `option_<name>` values.
|
||||||
- If you have a `Toggle`/`DefaultOnToggle` option, the value should be a `bool`.
|
- If you have a `Toggle`/`DefaultOnToggle` option, the value should be a `bool`.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from worlds import AutoWorldRegister
|
from worlds import AutoWorldRegister
|
||||||
from Options import Choice, SpecialRange, Toggle, Range
|
from Options import Choice, NamedRange, Toggle, Range
|
||||||
|
|
||||||
|
|
||||||
class TestOptionPresets(unittest.TestCase):
|
class TestOptionPresets(unittest.TestCase):
|
||||||
|
@ -14,7 +14,7 @@ class TestOptionPresets(unittest.TestCase):
|
||||||
with self.subTest(game=game_name, preset=preset_name, option=option_name):
|
with self.subTest(game=game_name, preset=preset_name, option=option_name):
|
||||||
try:
|
try:
|
||||||
option = world_type.options_dataclass.type_hints[option_name].from_any(option_value)
|
option = world_type.options_dataclass.type_hints[option_name].from_any(option_value)
|
||||||
supported_types = [Choice, Toggle, Range, SpecialRange]
|
supported_types = [Choice, Toggle, Range, NamedRange]
|
||||||
if not any([issubclass(option.__class__, t) for t in supported_types]):
|
if not any([issubclass(option.__class__, t) for t in supported_types]):
|
||||||
self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' "
|
self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' "
|
||||||
f"is not a supported type for webhost. "
|
f"is not a supported type for webhost. "
|
||||||
|
@ -46,8 +46,8 @@ class TestOptionPresets(unittest.TestCase):
|
||||||
|
|
||||||
# Check for from_text resolving to a different value. ("random" is allowed though.)
|
# Check for from_text resolving to a different value. ("random" is allowed though.)
|
||||||
if option_value != "random" and isinstance(option_value, str):
|
if option_value != "random" and isinstance(option_value, str):
|
||||||
# Allow special named values for SpecialRange option presets.
|
# Allow special named values for NamedRange option presets.
|
||||||
if isinstance(option, SpecialRange):
|
if isinstance(option, NamedRange):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
option_value in option.special_range_names,
|
option_value in option.special_range_names,
|
||||||
f"Invalid preset '{option_name}': '{option_value}' in preset '{preset_name}' "
|
f"Invalid preset '{option_name}': '{option_value}' in preset '{preset_name}' "
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from Options import Choice, DeathLink, PerGameCommonOptions, SpecialRange
|
from Options import Choice, DeathLink, NamedRange, PerGameCommonOptions
|
||||||
|
|
||||||
|
|
||||||
class DoubleJumpGlitch(Choice):
|
class DoubleJumpGlitch(Choice):
|
||||||
|
@ -33,7 +33,7 @@ class CoinSanity(Choice):
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class CoinSanityRange(SpecialRange):
|
class CoinSanityRange(NamedRange):
|
||||||
"""This is the amount of coins in a coin bundle
|
"""This is the amount of coins in a coin bundle
|
||||||
You need to collect that number of coins to get a location check, and when receiving coin items, you will get bundles of this size
|
You need to collect that number of coins to get a location check, and when receiving coin items, you will get bundles of this size
|
||||||
It is highly recommended to not set this value below 10, as it generates a very large number of boring locations and items.
|
It is highly recommended to not set this value below 10, as it generates a very large number of boring locations and items.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from Options import SpecialRange
|
from Options import NamedRange
|
||||||
from .option_names import options_to_include
|
from .option_names import options_to_include
|
||||||
from .checks.world_checks import assert_can_win, assert_same_number_items_locations
|
from .checks.world_checks import assert_can_win, assert_same_number_items_locations
|
||||||
from . import DLCQuestTestBase, setup_dlc_quest_solo_multiworld
|
from . import DLCQuestTestBase, setup_dlc_quest_solo_multiworld
|
||||||
|
@ -14,7 +14,7 @@ def basic_checks(tester: DLCQuestTestBase, multiworld: MultiWorld):
|
||||||
|
|
||||||
|
|
||||||
def get_option_choices(option) -> Dict[str, int]:
|
def get_option_choices(option) -> Dict[str, int]:
|
||||||
if issubclass(option, SpecialRange):
|
if issubclass(option, NamedRange):
|
||||||
return option.special_range_names
|
return option.special_range_names
|
||||||
elif option.options:
|
elif option.options:
|
||||||
return option.options
|
return option.options
|
||||||
|
|
|
@ -2,7 +2,7 @@ import typing
|
||||||
from .ExtractedData import logic_options, starts, pool_options
|
from .ExtractedData import logic_options, starts, pool_options
|
||||||
from .Rules import cost_terms
|
from .Rules import cost_terms
|
||||||
|
|
||||||
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, SpecialRange
|
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange
|
||||||
from .Charms import vanilla_costs, names as charm_names
|
from .Charms import vanilla_costs, names as charm_names
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
|
@ -242,7 +242,7 @@ class MaximumGeoPrice(Range):
|
||||||
default = 400
|
default = 400
|
||||||
|
|
||||||
|
|
||||||
class RandomCharmCosts(SpecialRange):
|
class RandomCharmCosts(NamedRange):
|
||||||
"""Total Notch Cost of all Charms together. Vanilla sums to 90.
|
"""Total Notch Cost of all Charms together. Vanilla sums to 90.
|
||||||
This value is distributed among all charms in a random fashion.
|
This value is distributed among all charms in a random fashion.
|
||||||
Special Cases:
|
Special Cases:
|
||||||
|
@ -250,7 +250,7 @@ class RandomCharmCosts(SpecialRange):
|
||||||
Set to -2 or shuffle to shuffle around the vanilla costs to different charms."""
|
Set to -2 or shuffle to shuffle around the vanilla costs to different charms."""
|
||||||
|
|
||||||
display_name = "Randomize Charm Notch Costs"
|
display_name = "Randomize Charm Notch Costs"
|
||||||
range_start = -2
|
range_start = 0
|
||||||
range_end = 240
|
range_end = 240
|
||||||
default = -1
|
default = -1
|
||||||
vanilla_costs: typing.List[int] = vanilla_costs
|
vanilla_costs: typing.List[int] = vanilla_costs
|
||||||
|
|
|
@ -7,8 +7,8 @@ from dataclasses import dataclass
|
||||||
from itertools import accumulate, chain, combinations
|
from itertools import accumulate, chain, combinations
|
||||||
from typing import Any, cast, Dict, Iterator, List, Mapping, Optional, Set, Tuple, Type, TYPE_CHECKING, Union
|
from typing import Any, cast, Dict, Iterator, List, Mapping, Optional, Set, Tuple, Type, TYPE_CHECKING, Union
|
||||||
|
|
||||||
from Options import AssembleOptions, Choice, DeathLink, ItemDict, OptionDict, PerGameCommonOptions, Range, \
|
from Options import AssembleOptions, Choice, DeathLink, ItemDict, NamedRange, OptionDict, PerGameCommonOptions, Range, \
|
||||||
SpecialRange, TextChoice, Toggle
|
TextChoice, Toggle
|
||||||
from .Enemies import enemy_name_to_sprite
|
from .Enemies import enemy_name_to_sprite
|
||||||
from .Items import ItemType, l2ac_item_table
|
from .Items import ItemType, l2ac_item_table
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ class CapsuleCravingsJPStyle(Toggle):
|
||||||
display_name = "Capsule cravings JP style"
|
display_name = "Capsule cravings JP style"
|
||||||
|
|
||||||
|
|
||||||
class CapsuleStartingForm(SpecialRange):
|
class CapsuleStartingForm(NamedRange):
|
||||||
"""The starting form of your capsule monsters.
|
"""The starting form of your capsule monsters.
|
||||||
|
|
||||||
Supported values: 1 – 4, m
|
Supported values: 1 – 4, m
|
||||||
|
@ -266,7 +266,6 @@ class CapsuleStartingForm(SpecialRange):
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 5
|
range_end = 5
|
||||||
default = 1
|
default = 1
|
||||||
special_range_cutoff = 1
|
|
||||||
special_range_names = {
|
special_range_names = {
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"m": 5,
|
"m": 5,
|
||||||
|
@ -280,7 +279,7 @@ class CapsuleStartingForm(SpecialRange):
|
||||||
return self.value - 1
|
return self.value - 1
|
||||||
|
|
||||||
|
|
||||||
class CapsuleStartingLevel(LevelMixin, SpecialRange):
|
class CapsuleStartingLevel(LevelMixin, NamedRange):
|
||||||
"""The starting level of your capsule monsters.
|
"""The starting level of your capsule monsters.
|
||||||
|
|
||||||
Can be set to the special value party_starting_level to make it the same value as the party_starting_level option.
|
Can be set to the special value party_starting_level to make it the same value as the party_starting_level option.
|
||||||
|
@ -289,10 +288,9 @@ class CapsuleStartingLevel(LevelMixin, SpecialRange):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
display_name = "Capsule monster starting level"
|
display_name = "Capsule monster starting level"
|
||||||
range_start = 0
|
range_start = 1
|
||||||
range_end = 99
|
range_end = 99
|
||||||
default = 1
|
default = 1
|
||||||
special_range_cutoff = 1
|
|
||||||
special_range_names = {
|
special_range_names = {
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"party_starting_level": 0,
|
"party_starting_level": 0,
|
||||||
|
@ -685,7 +683,7 @@ class RunSpeed(Choice):
|
||||||
default = option_disabled
|
default = option_disabled
|
||||||
|
|
||||||
|
|
||||||
class ShopInterval(SpecialRange):
|
class ShopInterval(NamedRange):
|
||||||
"""Place shops after a certain number of floors.
|
"""Place shops after a certain number of floors.
|
||||||
|
|
||||||
E.g., if you set this to 5, then you will be given the opportunity to shop after completing B5, B10, B15, etc.,
|
E.g., if you set this to 5, then you will be given the opportunity to shop after completing B5, B10, B15, etc.,
|
||||||
|
@ -698,10 +696,9 @@ class ShopInterval(SpecialRange):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
display_name = "Shop interval"
|
display_name = "Shop interval"
|
||||||
range_start = 0
|
range_start = 1
|
||||||
range_end = 10
|
range_end = 10
|
||||||
default = 0
|
default = 0
|
||||||
special_range_cutoff = 1
|
|
||||||
special_range_names = {
|
special_range_names = {
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from Options import Toggle, Choice, Range, SpecialRange, TextChoice, DeathLink
|
from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink
|
||||||
|
|
||||||
|
|
||||||
class GameVersion(Choice):
|
class GameVersion(Choice):
|
||||||
|
@ -285,7 +285,7 @@ class AllPokemonSeen(Toggle):
|
||||||
display_name = "All Pokemon Seen"
|
display_name = "All Pokemon Seen"
|
||||||
|
|
||||||
|
|
||||||
class DexSanity(SpecialRange):
|
class DexSanity(NamedRange):
|
||||||
"""Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to
|
"""Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to
|
||||||
have checks added. If Accessibility is set to locations, this will be the percentage of all logically reachable
|
have checks added. If Accessibility is set to locations, this will be the percentage of all logically reachable
|
||||||
Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage
|
Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage
|
||||||
|
@ -412,7 +412,7 @@ class LevelScaling(Choice):
|
||||||
default = 1
|
default = 1
|
||||||
|
|
||||||
|
|
||||||
class ExpModifier(SpecialRange):
|
class ExpModifier(NamedRange):
|
||||||
"""Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16."""
|
"""Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16."""
|
||||||
display_name = "Exp Modifier"
|
display_name = "Exp Modifier"
|
||||||
default = 16
|
default = 16
|
||||||
|
@ -607,8 +607,8 @@ class RandomizeTMMoves(Toggle):
|
||||||
display_name = "Randomize TM Moves"
|
display_name = "Randomize TM Moves"
|
||||||
|
|
||||||
|
|
||||||
class TMHMCompatibility(SpecialRange):
|
class TMHMCompatibility(NamedRange):
|
||||||
range_start = -1
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
special_range_names = {
|
special_range_names = {
|
||||||
"vanilla": -1,
|
"vanilla": -1,
|
||||||
|
@ -675,12 +675,12 @@ class RandomizeMoveTypes(Toggle):
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class SecondaryTypeChance(SpecialRange):
|
class SecondaryTypeChance(NamedRange):
|
||||||
"""If randomize_pokemon_types is on, this is the chance each Pokemon will have a secondary type. If follow_evolutions
|
"""If randomize_pokemon_types is on, this is the chance each Pokemon will have a secondary type. If follow_evolutions
|
||||||
is selected, it is the chance a second type will be added at each evolution stage. vanilla will give secondary types
|
is selected, it is the chance a second type will be added at each evolution stage. vanilla will give secondary types
|
||||||
to Pokemon that normally have a secondary type."""
|
to Pokemon that normally have a secondary type."""
|
||||||
display_name = "Secondary Type Chance"
|
display_name = "Secondary Type Chance"
|
||||||
range_start = -1
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
default = -1
|
default = -1
|
||||||
special_range_names = {
|
special_range_names = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from Options import Range, SpecialRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink, Option
|
from Options import Range, NamedRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink, Option
|
||||||
from .mods.mod_data import ModNames
|
from .mods.mod_data import ModNames
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,12 +48,12 @@ class Goal(Choice):
|
||||||
return super().get_option_name(value)
|
return super().get_option_name(value)
|
||||||
|
|
||||||
|
|
||||||
class StartingMoney(SpecialRange):
|
class StartingMoney(NamedRange):
|
||||||
"""Amount of gold when arriving at the farm.
|
"""Amount of gold when arriving at the farm.
|
||||||
Set to -1 or unlimited for infinite money"""
|
Set to -1 or unlimited for infinite money"""
|
||||||
internal_name = "starting_money"
|
internal_name = "starting_money"
|
||||||
display_name = "Starting Gold"
|
display_name = "Starting Gold"
|
||||||
range_start = -1
|
range_start = 0
|
||||||
range_end = 50000
|
range_end = 50000
|
||||||
default = 5000
|
default = 5000
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class StartingMoney(SpecialRange):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProfitMargin(SpecialRange):
|
class ProfitMargin(NamedRange):
|
||||||
"""Multiplier over all gold earned in-game by the player."""
|
"""Multiplier over all gold earned in-game by the player."""
|
||||||
internal_name = "profit_margin"
|
internal_name = "profit_margin"
|
||||||
display_name = "Profit Margin"
|
display_name = "Profit Margin"
|
||||||
|
@ -283,7 +283,7 @@ class SpecialOrderLocations(Choice):
|
||||||
option_board_qi = 2
|
option_board_qi = 2
|
||||||
|
|
||||||
|
|
||||||
class HelpWantedLocations(SpecialRange):
|
class HelpWantedLocations(NamedRange):
|
||||||
"""Include location checks for Help Wanted quests
|
"""Include location checks for Help Wanted quests
|
||||||
Out of every 7 quests, 4 will be item deliveries, and then 1 of each for: Fishing, Gathering and Slaying Monsters.
|
Out of every 7 quests, 4 will be item deliveries, and then 1 of each for: Fishing, Gathering and Slaying Monsters.
|
||||||
Choosing a multiple of 7 is recommended."""
|
Choosing a multiple of 7 is recommended."""
|
||||||
|
@ -429,7 +429,7 @@ class MultipleDaySleepEnabled(Toggle):
|
||||||
default = 1
|
default = 1
|
||||||
|
|
||||||
|
|
||||||
class MultipleDaySleepCost(SpecialRange):
|
class MultipleDaySleepCost(NamedRange):
|
||||||
"""How much gold it will cost to use MultiSleep. You will have to pay that amount for each day skipped."""
|
"""How much gold it will cost to use MultiSleep. You will have to pay that amount for each day skipped."""
|
||||||
internal_name = "multiple_day_sleep_cost"
|
internal_name = "multiple_day_sleep_cost"
|
||||||
display_name = "Multiple Day Sleep Cost"
|
display_name = "Multiple Day Sleep Cost"
|
||||||
|
@ -446,7 +446,7 @@ class MultipleDaySleepCost(SpecialRange):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ExperienceMultiplier(SpecialRange):
|
class ExperienceMultiplier(NamedRange):
|
||||||
"""How fast you want to earn skill experience.
|
"""How fast you want to earn skill experience.
|
||||||
A lower setting mean less experience.
|
A lower setting mean less experience.
|
||||||
A higher setting means more experience."""
|
A higher setting means more experience."""
|
||||||
|
@ -466,7 +466,7 @@ class ExperienceMultiplier(SpecialRange):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FriendshipMultiplier(SpecialRange):
|
class FriendshipMultiplier(NamedRange):
|
||||||
"""How fast you want to earn friendship points with villagers.
|
"""How fast you want to earn friendship points with villagers.
|
||||||
A lower setting mean less friendship per action.
|
A lower setting mean less friendship per action.
|
||||||
A higher setting means more friendship per action."""
|
A higher setting means more friendship per action."""
|
||||||
|
|
|
@ -4,7 +4,7 @@ from random import random
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from BaseClasses import ItemClassification, MultiWorld
|
from BaseClasses import ItemClassification, MultiWorld
|
||||||
from Options import SpecialRange
|
from Options import NamedRange
|
||||||
from . import setup_solo_multiworld, SVTestBase, SVTestCase, allsanity_options_without_mods, allsanity_options_with_mods
|
from . import setup_solo_multiworld, SVTestBase, SVTestCase, allsanity_options_without_mods, allsanity_options_with_mods
|
||||||
from .. import StardewItem, items_by_group, Group, StardewValleyWorld
|
from .. import StardewItem, items_by_group, Group, StardewValleyWorld
|
||||||
from ..locations import locations_by_tag, LocationTags, location_table
|
from ..locations import locations_by_tag, LocationTags, location_table
|
||||||
|
@ -42,7 +42,7 @@ def check_no_ginger_island(tester: unittest.TestCase, multiworld: MultiWorld):
|
||||||
|
|
||||||
|
|
||||||
def get_option_choices(option) -> Dict[str, int]:
|
def get_option_choices(option) -> Dict[str, int]:
|
||||||
if issubclass(option, SpecialRange):
|
if issubclass(option, NamedRange):
|
||||||
return option.special_range_names
|
return option.special_range_names
|
||||||
elif option.options:
|
elif option.options:
|
||||||
return option.options
|
return option.options
|
||||||
|
@ -53,7 +53,7 @@ class TestGenerateDynamicOptions(SVTestCase):
|
||||||
def test_given_special_range_when_generate_then_basic_checks(self):
|
def test_given_special_range_when_generate_then_basic_checks(self):
|
||||||
options = StardewValleyWorld.options_dataclass.type_hints
|
options = StardewValleyWorld.options_dataclass.type_hints
|
||||||
for option_name, option in options.items():
|
for option_name, option in options.items():
|
||||||
if not isinstance(option, SpecialRange):
|
if not isinstance(option, NamedRange):
|
||||||
continue
|
continue
|
||||||
for value in option.special_range_names:
|
for value in option.special_range_names:
|
||||||
with self.subTest(f"{option_name}: {value}"):
|
with self.subTest(f"{option_name}: {value}"):
|
||||||
|
@ -152,7 +152,7 @@ class TestGenerateAllOptionsWithExcludeGingerIsland(SVTestCase):
|
||||||
def test_given_special_range_when_generate_exclude_ginger_island(self):
|
def test_given_special_range_when_generate_exclude_ginger_island(self):
|
||||||
options = StardewValleyWorld.options_dataclass.type_hints
|
options = StardewValleyWorld.options_dataclass.type_hints
|
||||||
for option_name, option in options.items():
|
for option_name, option in options.items():
|
||||||
if not isinstance(option, SpecialRange) or option_name == ExcludeGingerIsland.internal_name:
|
if not isinstance(option, NamedRange) or option_name == ExcludeGingerIsland.internal_name:
|
||||||
continue
|
continue
|
||||||
for value in option.special_range_names:
|
for value in option.special_range_names:
|
||||||
with self.subTest(f"{option_name}: {value}"):
|
with self.subTest(f"{option_name}: {value}"):
|
||||||
|
|
|
@ -2,7 +2,7 @@ import unittest
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from Options import SpecialRange
|
from Options import NamedRange
|
||||||
from .option_names import options_to_include
|
from .option_names import options_to_include
|
||||||
from worlds.stardew_valley.test.checks.world_checks import assert_can_win, assert_same_number_items_locations
|
from worlds.stardew_valley.test.checks.world_checks import assert_can_win, assert_same_number_items_locations
|
||||||
from .. import setup_solo_multiworld, SVTestCase
|
from .. import setup_solo_multiworld, SVTestCase
|
||||||
|
@ -14,7 +14,7 @@ def basic_checks(tester: unittest.TestCase, multiworld: MultiWorld):
|
||||||
|
|
||||||
|
|
||||||
def get_option_choices(option) -> Dict[str, int]:
|
def get_option_choices(option) -> Dict[str, int]:
|
||||||
if issubclass(option, SpecialRange):
|
if issubclass(option, NamedRange):
|
||||||
return option.special_range_names
|
return option.special_range_names
|
||||||
elif option.options:
|
elif option.options:
|
||||||
return option.options
|
return option.options
|
||||||
|
|
|
@ -2,7 +2,7 @@ from typing import Dict
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from Options import SpecialRange, Range
|
from Options import NamedRange, Range
|
||||||
from .option_names import options_to_include
|
from .option_names import options_to_include
|
||||||
from .. import setup_solo_multiworld, SVTestCase
|
from .. import setup_solo_multiworld, SVTestCase
|
||||||
from ..checks.goal_checks import assert_perfection_world_is_valid, assert_goal_world_is_valid
|
from ..checks.goal_checks import assert_perfection_world_is_valid, assert_goal_world_is_valid
|
||||||
|
@ -12,7 +12,7 @@ from ..checks.world_checks import assert_same_number_items_locations, assert_vic
|
||||||
|
|
||||||
|
|
||||||
def get_option_choices(option) -> Dict[str, int]:
|
def get_option_choices(option) -> Dict[str, int]:
|
||||||
if issubclass(option, SpecialRange):
|
if issubclass(option, NamedRange):
|
||||||
return option.special_range_names
|
return option.special_range_names
|
||||||
if issubclass(option, Range):
|
if issubclass(option, Range):
|
||||||
return {f"{val}": val for val in range(option.range_start, option.range_end + 1)}
|
return {f"{val}": val for val in range(option.range_start, option.range_end + 1)}
|
||||||
|
|
|
@ -3,7 +3,7 @@ from dataclasses import dataclass
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
from typing_extensions import TypeGuard # remove when Python >= 3.10
|
from typing_extensions import TypeGuard # remove when Python >= 3.10
|
||||||
|
|
||||||
from Options import DefaultOnToggle, PerGameCommonOptions, Range, SpecialRange, Toggle, Choice
|
from Options import DefaultOnToggle, NamedRange, PerGameCommonOptions, Range, Toggle, Choice
|
||||||
|
|
||||||
from zilliandomizer.options import \
|
from zilliandomizer.options import \
|
||||||
Options as ZzOptions, char_to_gun, char_to_jump, ID, \
|
Options as ZzOptions, char_to_gun, char_to_jump, ID, \
|
||||||
|
@ -11,7 +11,7 @@ from zilliandomizer.options import \
|
||||||
from zilliandomizer.options.parsing import validate as zz_validate
|
from zilliandomizer.options.parsing import validate as zz_validate
|
||||||
|
|
||||||
|
|
||||||
class ZillionContinues(SpecialRange):
|
class ZillionContinues(NamedRange):
|
||||||
"""
|
"""
|
||||||
number of continues before game over
|
number of continues before game over
|
||||||
|
|
||||||
|
@ -218,7 +218,7 @@ class ZillionSkill(Range):
|
||||||
default = 2
|
default = 2
|
||||||
|
|
||||||
|
|
||||||
class ZillionStartingCards(SpecialRange):
|
class ZillionStartingCards(NamedRange):
|
||||||
"""
|
"""
|
||||||
how many ID Cards to start the game with
|
how many ID Cards to start the game with
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue