Create test pages for sprites

Similar to styles/STYLING.html and styles/hpbartest.html, test pages
prove to be more robust and convenient for testing than the unit
tests (which are already broken).

These pages are primarily intended to help with delivering #1369,
but can be used to test improvements to sprites across the board.
This commit is contained in:
Kirk Scheibelhut 2020-04-01 00:19:07 -07:00
parent 730d1ffd1a
commit d76cd108dd
5 changed files with 1115 additions and 121 deletions

View File

@ -10,8 +10,7 @@
},
"scripts": {
"lint": "eslint --config=.eslintrc.js --cache --cache-file=eslint-cache/base js/ data/ && eslint --config=build-tools/.eslintrc.js --cache --cache-file=eslint-cache/build build-tools/update build-tools/build-indexes && tslint --project .",
"test": "npm run lint && tsc && node build && mocha test/*.test.js",
"full-test": "npm run lint && tsc && node build indexes && mocha test/*.js",
"test": "npm run lint && tsc && node build && mocha test/*.js",
"fix": "eslint --config=.eslintrc.js --fix js/ && eslint --config=build-tools/.eslintrc.js --fix build-tools/update build-tools/build-indexes",
"build": "node build",
"build-full": "node build full"

316
sprites/gallery-test.html Normal file
View File

@ -0,0 +1,316 @@
<!doctype html>
<html lang=en>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sprites</title>
<style>
body {
font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
table {
border-collapse: collapse;
}
tr {
min-height: 96px;
}
td,
th {
border: 5px solid black;
}
.outline img {
border: 1px solid black;
margin: 2px;
}
img {
margin: 3px;
}
.picon {
display: inline-block;
width: 40px;
height: 30px;
}
th {
font-size: 2em;
position: sticky;
top: 0;
z-index: 10;
background: black;
color: white;
}
ul {
margin: 10px auto;
padding: 0;
display: block;
width: 100%;
text-align: center;
}
li {
/* font-size: 13px; */
display: inline-block;
padding: 0 5px;
}
button {
align-items: normal;
background-color: rgba(0, 0, 0, 0);
border-style: none;
box-sizing: content-box;
cursor: pointer;
display: inline;
font: inherit;
padding: 0;
perspective-origin: 0 0;
text-align: start;
transform-origin: 0 0;
-moz-appearance: none;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
exports = window;
</script>
<script src="../data/pokedex-mini.js"></script>
<script src="../data/pokedex-mini-bw.js"></script>
<script src="../data/teambuilder-tables.js"></script>
<script src="../data/abilities.js"></script>
<script src="../data/aliases.js"></script>
<script src="../data/items.js"></script>
<script src="../data/moves.js"></script>
<script src="../data/pokedex.js"></script>
<script src="../data/typechart.js"></script>
<script src="../js/battle-dex-data.js"></script>
<script src="../js/battle-dex.js"></script>
<script src="../js/battle-scene-stub.js"></script>
<script src="../data/text.js"></script>
<script src="../js/battle-text-parser.js"></script>
<script src="../js/battle.js"></script>
<script>
//#region VARIABLES
const ROOT = document.getElementById('root');
const PAGE_SIZE = 25;
const GRAPHICS = {
// 'gen1rg': 1,
// 'gen1rb': 1,
'gen1': 1,
// 'gen2g': 2,
// 'gen2s': 2,
'gen2': 2,
// 'gen3rs': 3,
'gen3': 3,
// 'gen3frlg': 3,
// 'gen4dp': 4,
'gen4': 4,
'gen5': 5,
'gen5ani': 5,
'dex': 6,
'ani': 6,
};
var PAGE;
var PAGES = 0;
const SPECIES = [];
let i = 0;
for (let baseid in BattlePokedex) {
if (baseid.endsWith('totem')) continue;
const species = Dex.getSpecies(baseid);
for (let formid of [''].concat(species.cosmeticFormes || [])) {
let spriteid = species.spriteid;
if (formid) spriteid += '-' + formid.slice(species.id.length);
const id = toID(spriteid);
SPECIES.push([id, species]);
if (i++ % PAGE_SIZE === 0) PAGES++;
}
}
SPECIES.sort(([, a], [, b]) => {
if (a.gen - b.gen) return a.gen - b.gen;
return Math.abs(a.num) - Math.abs(b.num);
});
//#endregion
//#region FUNCTIONS
function e(type, options) {
const tag = document.createElement(type);
if (!options) return tag;
if (typeof options === 'string' || typeof options === 'number') {
tag.textContent = options;
return tag;
} else if (options instanceof Node) {
tag.appendChild(options);
return tag;
}
if (options.content) {
if (typeof options.content === 'string' || typeof options.content === 'number') {
tag.textContent = options.content;
} else if (tag instanceof Node) {
tag.appendChild(options.content);
}
}
if (options.id) tag.setAttribute('id', options.id);
if (options.class) tag.classList.add(options.class);
return tag;
}
function picons(spriteid) {
const styles = new Set([
Dex.getPokemonIcon({ id: spriteid }),
Dex.getPokemonIcon({ id: spriteid, gender: 'F' }),
Dex.getPokemonIcon({ id: spriteid }, true), // facingLeft
]);
const icons = [];
for (const style of styles) {
const span = e('span', { class: 'picon' });
span.style = style;
icons.push(span);
}
return icons;
};
function sprites(graphics, spriteid, name, species) {
const gen = GRAPHICS[graphics];
const imgs = [];
if (graphics === 'dex') {
// no gender or back sprites
const data = Dex.getTeambuilderSpriteData({ species: name }, gen);
for (const shiny of (gen > 1 ? ['', '-shiny'] : [''])) {
const img = e('img');
img.src = `${Dex.resourcePrefix}${data.spriteDir}${shiny}/${data.spriteid}.png`;
if (gen < 5) img.style = 'image-rendering: pixelated;';
imgs.push(img);
}
return imgs;
}
withPrefs({ noanim: !graphics.endsWith('ani') }, () => {
for (const shiny of (gen > 1 ? [false, true] : [false])) {
for (const siden of [1, 0]) {
for (const gender of (gen === 1 || species.gender ? [undefined] : ['M', 'F'])) {
const data = Dex.getSpriteData(spriteid, siden, { gen, noScale: true, shiny, gender });
const img = e('img');
img.src = data.url;
img.width = data.w;
img.height = data.h;
if (data.pixelated) img.style = 'image-rendering: pixelated;';
imgs.push(img);
}
}
}
});
return imgs;
};
function withPrefs(prefs, fn) {
const saved = Dex.prefs;
Dex.prefs = s => prefs[s];
fn();
Dex.prefs = saved;
}
function renderRow(i) {
const [spriteid, species] = SPECIES[i];
const tr = e('tr')
const td = e('td');
for (const icon of picons(spriteid)) {
td.appendChild(icon);
}
tr.appendChild(td);
const name = species.id !== spriteid ? formeName(species, spriteid) : species.name;
tr.appendChild(e('td', name));
for (const graphics in GRAPHICS) {
const td = e('td');
for (const sprite of sprites(graphics, spriteid, name, species)) {
td.appendChild(sprite);
}
tr.appendChild(td);
}
return tr;
}
function formeName(species, spriteid) {
if (species.name === 'Toxtricity-Low-Key-Gmax') return species.name;
let forme = spriteid.slice(species.id.length);
forme = forme.charAt(0).toUpperCase() + forme.substr(1);
return `${species.name}-${forme}`;
}
function createNav(page) {
const ul = e('ul');
for (let i = 0; i < PAGES; i++) {
const li = e('li');
if (page === i) li.style = 'background-color: yellow;'
const button = e('button', e(page === i ? 'b' : 'span', `${i + 1}`));
button.addEventListener('click', () => displayPage(i));
li.appendChild(button);
ul.appendChild(li);
}
return ul;
}
function displayPage(page) {
PAGE = page;
if (ROOT.firstChild) ROOT.removeChild(ROOT.firstChild);
const table = e('table')
const tr = e('tr');
tr.appendChild(e('th'));
tr.appendChild(e('th'));
for (const graphics in GRAPHICS) {
tr.appendChild(e('th', graphics));
}
table.appendChild(tr);
for (let i = page * PAGE_SIZE; i < Math.min(SPECIES.length, (page + 1) * PAGE_SIZE); i++) {
const row = renderRow(i);
table.appendChild(row);
}
const div = e('div');
div.appendChild(createNav(page));
div.appendChild(table);
div.appendChild(createNav(page));
ROOT.appendChild(div);
window.scrollTo(0, 0);
};
//#endregion
//#region MAIN
displayPage(0);
document.addEventListener('keydown', e => {
if (e.keyCode === 37 && PAGE !== 0) {
displayPage(PAGE - 1);
} else if (e.keyCode === 39 && PAGE !== PAGES) {
displayPage(PAGE + 1);
} else if (e.keyCode === 219) {
ROOT.classList.toggle('outline');
}
});
//#endregion
</script>
</body>
</html>

790
sprites/scene-test.html Normal file
View File

@ -0,0 +1,790 @@
<!doctype html>
<html lang=en>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sprites</title>
<link rel="stylesheet" href="../style/battle.css" />
<style>
body {
font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
max-width: 1365px;
}
.picon {
width: 40px;
height: 30px;
display: inline-block;
}
fieldset {
float: left;
height: 100px;
}
.dex {
height: 85px;
width: 115px;
border: 1px solid black;
}
fieldset legend {
font-weight: bold;
}
textarea {
display: block;
margin: 0px auto;
width: 432px;
height: 50px;
}
.sprites {
background-color: rgb(218, 229, 240);
border-left: 2px groove threedface;
height: 107px;
width: 180px;
float: right;
margin: -14px -12px;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.pixelated {
image-rendering: pixelated;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
exports = window;
</script>
<script src="../data/pokedex-mini.js"></script>
<script src="../data/pokedex-mini-bw.js"></script>
<script src="../data/teambuilder-tables.js"></script>
<script src="../data/abilities.js"></script>
<script src="../data/aliases.js"></script>
<script src="../data/items.js"></script>
<script src="../data/moves.js"></script>
<script src="../data/pokedex.js"></script>
<script src="../data/typechart.js"></script>
<script src="../js/battle-dex-data.js"></script>
<script src="../js/battle-dex.js"></script>
<script src="../js/battle-scene-stub.js"></script>
<script src="../data/text.js"></script>
<script src="../js/battle-text-parser.js"></script>
<script src="../js/battle.js"></script>
<script>
//#region VARIABLES
const GRAPHIC_NAMES = {
'Default': '',
'Green': 'gen1rg',
'Red/Blue': 'gen1rb',
'Yellow': 'gen1',
'Gold': 'gen2g',
'Silver': 'gen2s',
'Crystal': 'gen2',
'Ruby/Sapphire': 'gen3rs',
'FireRed/LeafGreen': 'gen3frlg',
'Emerald': 'gen3',
'Diamond/Pearl': 'gen4dp',
'Platinum': 'gen4',
'Black/White': 'gen5',
'Black/White (Animated)': 'gen5ani',
'Modern (Animated)': 'ani',
};
const GRAPHICS = {
// 'gen1rg': 1,
// 'gen1rb': 1,
'gen1': 1,
// 'gen2g': 2,
// 'gen2s': 2,
'gen2': 2,
// 'gen3rs': 3,
'gen3': 3,
// 'gen3frlg': 3,
// 'gen4dp': 4,
'gen4': 4,
'gen5': 5,
'gen5ani': 5,
'ani': 6,
};
var SETTINGS, SIDES, SCENE, STATE, BACKDROP;
const ROOT = document.getElementById('root');
const SPECIES = [];
for (let baseid in BattlePokedex) {
const species = Dex.getSpecies(baseid);
for (let formid of [''].concat(species.cosmeticFormes || [])) {
let spriteid = species.spriteid;
if (formid) spriteid += '-' + formid.slice(species.id.length);
const id = toID(spriteid);
SPECIES.push([formeName(species, id), species]);
}
}
SPECIES.sort(([, a], [, b]) => {
if (a.gen - b.gen) return a.gen - b.gen;
return Math.abs(a.num) - Math.abs(b.num);
});
//#endregion
//#region FUNCTIONS
function e(type, options) {
const tag = document.createElement(type);
if (!options) return tag;
if (typeof options === 'string' || typeof options === 'number') {
tag.textContent = options;
return tag;
} else if (options instanceof Node) {
tag.appendChild(options);
return tag;
}
if (options.content) {
if (typeof options.content === 'string' || typeof options.content === 'number') {
tag.textContent = options.content;
} else if (tag instanceof Node) {
tag.appendChild(options.content);
}
}
if (options.id) tag.setAttribute('id', options.id);
if (options.class) tag.classList.add(options.class);
return tag;
}
function formeName(species, spriteid) {
if (species.name === 'Toxtricity-Low-Key-Gmax') return species.name;
let forme = spriteid.slice(species.id.length);
if (!forme) return species.name;
forme = forme.charAt(0).toUpperCase() + forme.substr(1);
return `${species.name}-${forme}`;
}
function selector(label, selected, generator) {
label = e('label', label);
const select = e('select');
for (const [name, value] of generator()) {
const option = e('option', name);
option.value = value;
select.appendChild(option);
}
select.value = selected;
select.addEventListener('change', onChange);
label.appendChild(select);
return { div: e('div', label), select };
}
function genSelector(selected) {
return selector('Generation: ', selected, function* () {
for (let gen = 1; gen <= 8; gen++) {
yield [`${gen}`, gen];
}
});
}
function graphicsSelector(selected) {
return selector('Graphics: ', selected, function* () {
for (let name in GRAPHIC_NAMES) {
const val = GRAPHIC_NAMES[name];
if (!GRAPHICS[val]) continue;
yield [name, val];
}
});
}
function pokemonSelector(selected) {
return selector('Pokemon: ', selected, function* () {
for (const [name,] of SPECIES) {
yield [name, name];
}
});
}
function checkbox(label, name, checked) {
label = e('label', label);
const input = e('input');
input.type = 'checkbox';
input.name = name;
if (checked) input.checked = 'checked';
input.addEventListener('change', onChange);
label.appendChild(input);
return { div: e('div', label), input };
}
function activeRadios() {
const inputs = [];
const div = e('div');
div.textContent = 'Active: ';
for (let active = 1; active <= 3; active++) {
const input = e('input', {id: `radio${active}`});
input.type = 'radio';
input.name = 'active';
input.value = active;
input.addEventListener('change', onChange);
if (active === 1) input.checked = true;
inputs.push(input);
const label = e('label', active);
label.for =`radio${active}`;
div.appendChild(input);
div.appendChild(label);
}
return {inputs, div};
}
function createSettingsControls(selected) {
const fieldset = e('fieldset', e('legend', 'Settings'));
const fields = {
gen: genSelector(selected.gen),
graphics: graphicsSelector(selected.graphics),
scale: checkbox('Scale: ', 'scale', selected.scale),
oversize: checkbox('Oversize BW: ', 'oversize', selected.oversize),
active: activeRadios(),
};
const checkboxes = e('div');
checkboxes.style.height = '21px';
fields.scale.div.style.display = 'inline-block';
fields.oversize.div.style.display = 'inline-block';
fields.oversize.div.style.paddingLeft = '20px';
checkboxes.appendChild(fields.scale.div);
checkboxes.appendChild(fields.oversize.div);
fields.active.div.style.height = '21px';
fieldset.appendChild(fields.gen.div);
fieldset.appendChild(fields.graphics.div);
fieldset.appendChild(checkboxes);
fieldset.appendChild(fields.active.div);
fieldset.style.width = '240px';
return { fieldset, ...fields };
}
function createSideControls(legend, selected) {
const fieldset = e('fieldset', e('legend', legend));
const fields = {
pokemon: pokemonSelector(selected.pokemon),
female: checkbox('Female', 'female', selected.shiny),
shiny: checkbox('Shiny', 'shiny', selected.shiny),
dynamax: checkbox('Dynamax', 'dynamax', selected.dynamax),
picon: e('div', { class: 'picon' }),
dex: e('div', { class: 'dex' }),
};
const controls = e('div');
controls.style = 'float: left;';
controls.appendChild(fields.pokemon.div);
controls.appendChild(fields.female.div);
controls.appendChild(fields.shiny.div);
controls.appendChild(fields.dynamax.div);
const sprites = e('div', { class: 'sprites' });
sprites.appendChild(fields.picon);
sprites.appendChild(fields.dex);
fieldset.appendChild(controls);
fieldset.appendChild(sprites);
fieldset.style.width = '440px';
return { fieldset, ...fields };
}
function toState() {
const state = {
gen: SETTINGS.gen.select.value,
graphics: SETTINGS.graphics.select.value,
scale: SETTINGS.scale.input.checked,
oversize: SETTINGS.oversize.input.checked,
active: SETTINGS.active.inputs.findIndex(input => input.checked) + 1,
};
for (const s in SIDES) {
state[s] = {};
state[s].pokemon = SIDES[s].pokemon.select.value;
state[s].female = SIDES[s].female.input.checked;
state[s].shiny = SIDES[s].shiny.input.checked;
state[s].dynamax = SIDES[s].dynamax.input.checked;
}
return state;
}
function fromState(state) {
if ('gen' in state) SETTINGS.gen.select.value = state.gen;
if ('graphics' in state) SETTINGS.graphics.select.value = state.graphics;
if ('scale' in state) SETTINGS.scale.input.checked = state.scale;
if ('oversize' in state) SETTINGS.oversize.input.checked = state.oversize;
if ('active' in state) null; // TODO
for (const s in SIDES) {
if (!state[s]) continue;
if ('pokemon' in state[s]) SIDES[s].pokemon.select.value = state[s].pokemon
if ('female' in state[s]) SIDES[s].female.input.checked = state[s].female;
if ('shiny' in state[s]) SIDES[s].shiny.input.checked = state[s].shiny;
if ('dynamax' in state[s]) SIDES[s].dynamax.input.checked = state[s].dynamax;
}
onChange();
}
function onChange() {
const gen = Number(SETTINGS.gen.select.value);
SETTINGS.oversize.input.disabled = !SETTINGS.graphics.select.value.startsWith('gen5');
SETTINGS.active.inputs[1].disabled = gen < 3;
SETTINGS.active.inputs[2].disabled = gen > 6 || gen < 5;
for (const side in SIDES) {
const species = Dex.getSpecies(SIDES[side].pokemon.select.value);
if (gen === 1) {
SIDES[side].female.input.disabled = true;
} else if (species.gender) {
SIDES[side].female.input.checked = SIDES[side].female.input.checked = species.gender === 'F';
SIDES[side].female.input.disabled = true;
} else {
SIDES[side].female.input.disabled = false;
}
SIDES[side].shiny.input.disabled = gen === 1;
SIDES[side].dynamax.input.disabled = gen !== 8;
}
render();
}
function changeBackdrop(backdrop, gen) {
if (BACKDROP && BACKDROP.gen === gen) {
backdrop.style = BACKDROP.style;
return;
}
BACKDROP = gen;
const BattleBackdropsThree = [
'bg-gen3.png',
'bg-gen3-cave.png',
'bg-gen3-ocean.png',
'bg-gen3-sand.png',
'bg-gen3-forest.png',
'bg-gen3-arena.png',
];
const BattleBackdropsFour = [
'bg-gen4.png',
'bg-gen4-cave.png',
'bg-gen4-snow.png',
'bg-gen4-indoors.png',
'bg-gen4-water.png',
];
const BattleBackdropsFive = [
'bg-beach.png',
'bg-beachshore.png',
'bg-desert.png',
'bg-meadow.png',
'bg-thunderplains.png',
'bg-city.png',
'bg-earthycave.png',
'bg-mountain.png',
'bg-volcanocave.png',
'bg-dampcave.png',
'bg-forest.png',
'bg-river.png',
'bg-deepsea.png',
'bg-icecave.png',
'bg-route.png',
];
const BattleBackdrops = [
'bg-aquacordetown.jpg',
'bg-beach.jpg',
'bg-city.jpg',
'bg-dampcave.jpg',
'bg-darkbeach.jpg',
'bg-darkcity.jpg',
'bg-darkmeadow.jpg',
'bg-deepsea.jpg',
'bg-desert.jpg',
'bg-earthycave.jpg',
'bg-elite4drake.jpg',
'bg-forest.jpg',
'bg-icecave.jpg',
'bg-leaderwallace.jpg',
'bg-library.jpg',
'bg-meadow.jpg',
'bg-orasdesert.jpg',
'bg-orassea.jpg',
'bg-skypillar.jpg',
];
let bg;
if (gen <= 1) bg = 'fx/bg-gen1.png';
else if (gen <= 2) bg = 'fx/bg-gen2.png';
else if (gen <= 3) bg = 'fx/' + sample(BattleBackdropsThree);
else if (gen <= 4) bg = 'fx/' + sample(BattleBackdropsFour);
else if (gen <= 5) bg = 'fx/' + sample(BattleBackdropsFive);
else bg = 'sprites/gen6bgs/' + sample(BattleBackdrops);
const style = `background-image:url(${Dex.resourcePrefix}${bg});display:block;opacity:0.8`;
backdrop.style = style;
BACKDROP = {gen, style};
}
function locToPos(loc, obj) {
loc = {
x: 0,
y: 0,
z: 0,
scale: 1,
opacity: 1,
...loc,
};
if (!loc.xscale && loc.xscale !== 0) loc.xscale = loc.scale;
if (!loc.yscale && loc.yscale !== 0) loc.yscale = loc.scale;
let left = 210;
let top = 245;
let scale = (obj.gen === 5
? 2.0 - ((loc.z) / 200)
: 1.5 - 0.5 * ((loc.z) / 200));
if (scale < .1) scale = .1;
left += (410 - 190) * ((loc.z) / 200);
top += (135 - 245) * ((loc.z) / 200);
left += Math.floor(loc.x * scale);
top -= Math.floor(loc.y * scale /* - loc.x * scale / 4 */);
let width = Math.floor(obj.w * scale * loc.xscale);
let height = Math.floor(obj.h * scale * loc.yscale);
let hoffset = Math.floor((obj.h - (obj.y || 0) * 2) * scale * loc.yscale);
left -= Math.floor(width / 2);
top -= Math.floor(hoffset / 2);
let pos = {
left,
top,
width,
height,
opacity: loc.opacity,
};
if (loc.display) pos.display = loc.display;
return pos;
}
function calculatePos(slot, z, gen, activeCount, pixelated, isBackSprite) {
const moreActive = activeCount - 1;
let statbarOffset = 0;
let x = 0, y = 0;
if (gen <= 4 && moreActive) {
x = (slot - 0.52) * (isBackSprite ? -1 : 1) * -55;
y = (isBackSprite ? -1 : 1) + 1;
if (!isBackSprite) statbarOffset = 30 * slot;
if (isBackSprite) statbarOffset = -28 * slot;
} else {
switch (moreActive) {
case 0:
x = 0;
break;
case 1:
if (pixelated) {
x = (slot * -100 + 18) * (isBackSprite ? -1 : 1);
} else {
x = (slot * -75 + 18) * (isBackSprite ? -1 : 1);
}
break;
case 2:
x = (slot * -70 + 20) * (isBackSprite ? -1 : 1);
break;
}
y = (slot * 10) * (isBackSprite ? -1 : 1);
if (!isBackSprite) statbarOffset = 17 * slot;
if (!isBackSprite && !moreActive && pixelated) statbarOffset = 15;
if (isBackSprite) statbarOffset = -7 * slot;
if (!isBackSprite && moreActive === 2) statbarOffset = 14 * slot - 10;
}
if (gen <= 2) {
statbarOffset += isBackSprite ? 1 : 20;
} else if (gen <= 3) {
statbarOffset += isBackSprite ? 5 : 30;
} else if (gen !== 5) {
statbarOffset += isBackSprite ? 20 : 30;
}
let pos = locToPos({x, y , z}, {w: 0, h: 96});
pos.top += 40;
const statbarLeft = pos.left - 80;
const statbarTop = pos.top - 73 - statbarOffset;
return {x, y, z, left: statbarLeft, top: statbarTop};
}
function leftof(side, offset) {
return (side === 'p1' ? -1 : 1) * offset;
}
function getHPColor(pokemon) {
let ratio = pokemon.hp / pokemon.maxhp;
if (ratio > 0.5) return 'g';
if (ratio > 0.2) return 'y';
return 'r';
}
function hpWidth(hp, maxhp, maxwidth) {
// special case for low health...
if (this.hp === 1 && this.maxhp > 45) return 1;
let percentage = Math.ceil(100 * hp / maxhp);
if ((percentage === 100) && (hp < maxhp)) {
percentage = 99;
}
return percentage * maxwidth / 100;
}
function displayHP(div, hp, maxhp, c) {
let hpcolor = getHPColor({hp, maxhp});
let w = hpWidth(hp, maxhp, 150);
div.style = `width:${w}px;border-right-width:${w ? 1 : 0}px;`;
if (hpcolor === 'y') div.classList.add(`${c}-yellow`);
else if (hpcolor === 'r') div.classList.add(`${c}-yellow`, `${c}-red`);
}
function renderStatbar(side, pokemon, gender) {
const statbar = e('div');
statbar.classList.add('statbar', (side === 'p2' ? 'lstatbar' : 'rstatbar'));
statbar.style = 'display: block';
const strong = e('strong', pokemon);
if (gender === 'M' || gender === 'F') {
const img = e('img', {class: 'pixelated'});
img.width = 7;
img.height = 10;
img.src = `${Dex.resourcePrefix}fx/gender-${gender.toLowerCase()}.png`;
strong.textContent += ' ';
strong.appendChild(img);
}
let symbol = '';
if (pokemon.indexOf('-Mega') >= 0) symbol = 'mega';
else if (pokemon === 'Kyogre-Primal') symbol = 'alpha';
else if (pokemon === 'Groudon-Primal') symbol = 'omega';
if (symbol) {
const img = e('img', {class: 'pixelated'});
img.src = `${Dex.resourcePrefix}sprites/misc/${symbol}.png`;
img.style = 'vertical-align:text-bottom;';
strong.textContent += ' ';
strong.appendChild(img);
}
const maxhp = 100;
const hp = Math.floor(Math.random() * (maxhp - 1)) + 0;
const prevhp = Math.floor(Math.random() * (maxhp - hp + 1)) + hp;
const hpbar = e('div', {class: 'hpbar'});
const hptext = e('div', {class: 'hptext', content: `${hpWidth(hp, maxhp, 100)}%`});
hpbar.appendChild(hptext);
hpbar.appendChild(e('div', {class: 'hptextborder'}));
const prevhpDiv = e('div', {class: 'prevhp'});
const hpDiv = e('div', {class: 'hp'});
displayHP(hpDiv, hp, maxhp, 'hp');
displayHP(prevhpDiv, prevhp, maxhp, 'prevhp');
prevhpDiv.appendChild(hpDiv);
hpbar.appendChild(prevhpDiv);
hpbar.appendChild(e('div', {class: 'status'}));
statbar.appendChild(strong);
statbar.appendChild(hpbar);
return statbar;
}
function getGender(gen, pokemon, female) {
if (gen === 1) return undefined;
const species = Dex.getSpecies(pokemon);
if (species.gender) return species.gender;
return female ? 'F' : 'M';
}
function render() {
const gen = Number(SETTINGS.gen.select.value);
const graphicsGen = GRAPHICS[SETTINGS.graphics.select.value];
if (SCENE) ROOT.removeChild(SCENE);
if (STATE) ROOT.removeChild(STATE);
SCENE = e('div', {class: 'battle'});
const innerbattle = e('div', {class: 'innerbattle'});
const backdrop = e('div', {class: 'backdrop'});
const main = e('div');
changeBackdrop(backdrop, gen);
innerbattle.appendChild(backdrop);
innerbattle.appendChild(e('div', {class: 'leftbar'}));
innerbattle.appendChild(e('div', {class: 'rightbar'}));
innerbattle.appendChild(main);
const statbarsDiv = e('div');
const spritesDiv = e('div');
for (const side of ['p2', 'p1']) {
const pokemon = SIDES[side].pokemon.select.value;
const gender = getGender(gen, pokemon, SIDES[side].female.input.checked);
const shiny = SIDES[side].shiny.input.checked;
SIDES[side].picon.style = Dex.getPokemonIcon(
{id: pokemon, gender},
side === 'p2' // facingLeft
);
SIDES[side].dex.style = getTeambuilderSprite({
species: pokemon,
shiny,
}, graphicsGen);
const {statbars, sprites} = renderSide(gen, side, pokemon, shiny, gender);
for (const statbar of statbars) statbarsDiv.appendChild(statbar);
for (const sprite of sprites) spritesDiv.appendChild(sprite);
}
main.appendChild(spritesDiv);
main.appendChild(statbarsDiv);
SCENE.style = 'position:relative;margin:50px auto;'
SCENE.appendChild(innerbattle);
STATE = e('textarea');
STATE.value = JSON.stringify(toState());
STATE.addEventListener('change', () => fromState(JSON.parse(STATE.value)));
ROOT.appendChild(SCENE);
ROOT.appendChild(STATE);
}
function renderSide(gen, side, pokemon, shiny, gender) {
const graphics = SETTINGS.graphics.select.value;
const graphicsGen = GRAPHICS[SETTINGS.graphics.select.value];
const active = SETTINGS.active.inputs.findIndex(input => input.checked) + 1;
const siden = +(side === 'p2');
const noScale = !SETTINGS.scale.input.checked;
const dynamax = SIDES[side].dynamax.input.checked;
const isBackSprite = side === 'p1';
const prefs = {
noanim: !graphics.endsWith('ani'),
nopastgens: graphicsGen > 5,
};
const statbars = [];
const sprites = [];
for (let slot = 0; slot < active; slot++) {
withPrefs(prefs, () => {
const data = Dex.getSpriteData(pokemon, siden, { gen, noScale, shiny, gender, dynamax });
const img = e('img');
img.src = data.url;
img.style.position = 'absolute';
if (data.pixelated) img.classList.add('pixelated');
const {x, y, z, left, top} =
calculatePos(slot, (siden ? 200 : 0), gen, active, data.pixelated, isBackSprite);
const css = locToPos({x, y, z, display: 'block'}, data);
for (let k in css) img.style[k] = k === 'opacity' ? css[k] : `${css[k]}px`;
const statbar = renderStatbar(side, pokemon, gender);
statbar.style.left = `${left}px`;
statbar.style.top = `${top}px`;
statbars.push(statbar);
sprites.push(img);
});
}
// God only knows why this particular z-ordering is desirable...
if (siden) sprites.reverse();
if (active === 2) {
statbars.reverse();
} else if (active === 3) {
if (!siden) {
statbars.reverse();
[sprites[1], sprites[2]] = [sprites[2], sprites[1]];
}
}
return {statbars, sprites};
}
function getTeambuilderSprite(pokemon, gen) {
if (!pokemon) return '';
const data = Dex.getTeambuilderSpriteData(pokemon, gen);
const shiny = (data.shiny ? '-shiny' : '');
return 'background-image:url(' + Dex.resourcePrefix + data.spriteDir + shiny + '/' + data.spriteid + '.png);background-position:' + (data.x) + 'px ' + (data.y - 15) + 'px;background-repeat:no-repeat';
}
function withPrefs(prefs, fn) {
const saved = Dex.prefs;
Dex.prefs = s => prefs[s];
fn();
Dex.prefs = saved;
}
function sample(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function randomSelections() {
const gen = sample([1, 2, 3, 4, 5, 6, 7, 8]);
const graphics = sample(Object.keys(GRAPHICS));
const oversize = graphics.startsWith('gen5') ? sample([true, false]) : false;
const scale = sample([true, false]);
const sides = { p1: {}, p2: {} };
for (side in sides) {
const [name, species] = sample(SPECIES);
sides[side].pokemon = name;
if (gen == 1) {
sides[side].female = null;
} else if (species.gender) {
sides[side].female = species.gender === 'F' ? true : false;
} else {
sides[side].female = sample([true, false]);
}
sides[side].shiny = gen > 1 ? sample([true, false]) : false;
sides[side].dynamax = gen === 8 ? sample([true, false]) : false;
}
return {
gen,
graphics,
scale,
oversize,
...sides
};
}
function renderRandom() {
if (ROOT.firstChild) ROOT.removeChild(ROOT.firstChild);
const div = e('div');
const selected = randomSelections();
SETTINGS = createSettingsControls(selected);
SIDES = {
p1: createSideControls('Player 1', selected.p1),
p2: createSideControls('Player 2', selected.p2),
};
div.appendChild(SETTINGS.fieldset);
div.appendChild(SIDES.p1.fieldset);
div.appendChild(SIDES.p2.fieldset);
const randomize = e('button', 'Randomize');
randomize.addEventListener('click', () => renderRandom());
randomize.style = 'padding: 10px; margin: 45px 25px';
div.appendChild(randomize);
root.appendChild(div);
onChange();
}
//#endregion
//#region MAIN
renderRandom();
//#endregion
</script>
</body>
</html>

View File

@ -559,10 +559,16 @@ const Dex = new class implements ModdedDex {
document.getElementsByTagName('body')[0].appendChild(el);
}
getSpriteData(pokemon: Pokemon | Species | string, siden: number, options: {
gen?: number, shiny?: boolean, gender?: GenderName, afd?: boolean, noScale?: boolean, mod?: string,
gen?: number,
shiny?: boolean,
gender?: GenderName,
afd?: boolean,
noScale?: boolean,
mod?: string,
dynamax?: boolean,
} = {gen: 6}) {
const mechanicsGen = options.gen || 6;
let isDynamax = false;
let isDynamax = !!options.dynamax;
if (pokemon instanceof Pokemon) {
if (pokemon.volatiles.transform) {
options.shiny = pokemon.volatiles.transform[2];

View File

@ -1,117 +0,0 @@
const assert = require('assert').strict;
window = global;
window.BattleTeambuilderTable = require('../data/teambuilder-tables.js').BattleTeambuilderTable;
window.BattleAbilities = require('../data/abilities.js').BattleAbilities;
window.BattleItems = require('../data/items.js').BattleItems;
window.BattleMovedex = require('../data/moves.js').BattleMovedex;
window.BattlePokdex = require('../data/pokedex.js').BattlePokdex;
window.BattleTypeChart = require('../data/typechart.js').BattleTypeChart;
require('../js/battle-dex-data.js');
require('../js/battle-dex.js');
require('../js/battle-scene-stub.js');
global.BattleText = require('../data/text.js').BattleText;
require('../js/battle-text-parser.js');
require('../js/battle.js');
const withPrefs = (prefs, fn) => {
const saved = Dex.prefs;
Dex.prefs = s => prefs[s];
fn();
Dex.prefs = saved;
};
const spriteData = override => {
const url = Dex.resourcePrefix + 'sprites/' + (override.url || '');
delete override.url;
return Object.assign({
gen: 6,
w: 96,
h: 96,
y: 0,
url,
cryurl: '',
shiny: undefined,
}, override);
};
describe('Dex', () => {
it('getAbility', () => {
assert.equal(Dex.getAbility('Sturdy').shortDesc, 'If this Pokemon is at full HP, it survives one hit with at least 1 HP. Immune to OHKO.');
assert.equal(Dex.forGen(3).getAbility('s turdy').shortDesc, 'OHKO moves fail when used against this Pokemon.');
});
it('getItem', () => {
assert(Dex.getItem('Aerodactylite').megaEvolves, 'Aerodactyl');
});
it('getMove', () => {
assert(Dex.getMove('Draco Meteor').basePower, 130);
assert(Dex.forGen(4).getMove('DracoMeteor').basePower, 140);
assert(Dex.getMove('Crunch').category, 'Physical');
assert(Dex.forGen(2).getMove('CRUNCH').category, 'Special');
});
it('getTemplate', () => {
assert.equal(Dex.getTemplate('Alakazam').baseStats.spd, 95);
assert.equal(Dex.forGen(3).getTemplate('Alakazam').baseStats.spd, 85);
});
it('getType', () => {
assert(Dex.getType('Fairy').exists);
assert(!Dex.forGen(1).getType('steel').exists);
assert.equal(Dex.forGen(1).getType('Psychic').damageTaken['Ghost'], 3);
assert.equal(Dex.getType('Psychic').damageTaken['Ghost'], 1);
assert.equal(Dex.getType('Fire').damageTaken['Water'], 1);
assert.equal(Dex.getType('Water').damageTaken['Fire'], 2);
assert.equal(Dex.getType('Ground').damageTaken['Electric'], 3);
});
it('getSpriteData', () => {
// TODO Transform
// TODO Dynamax
it('default prefs', () => {
// withPrefs({}) or no withPrefs accomplishes the same, but this is explict
withPrefs({nopastgens: false, bwgfx: false, noanim: false, nogif: false}, () => {
assert.deepEqual(Dex.getSpriteData('pikachu', 0), spriteData({url: 'gen5-back/pikachu.png', pixelated: false, isBackSprite: true}));
assert.deepEqual(Dex.getSpriteData('pikachu', 1, {gen: 4, shiny: true}), spriteData({url: 'gen4/pikachu-shiny.png', pixelated: true, isBackSprite: false}));
});
})
// TODO oodles more
});
it('getTeambuilderSprite', () => {
assert.equal(Dex.getTeambuilderSprite({species: 'foobar'}), 'background-image:url(https://play.pokemonshowdown.com/sprites/gen5/0.png);background-position:10px 5px;background-repeat:no-repeat');
assert.equal(Dex.getTeambuilderSprite({species: 'pikachu'}), 'background-image:url(https://play.pokemonshowdown.com/sprites/dex/pikachu.png);background-position:-2px -3px;background-repeat:no-repeat');
assert.equal(Dex.getTeambuilderSprite({species: 'pikachu'}, 1), 'background-image:url(https://play.pokemonshowdown.com/sprites/gen1/pikachu.png);background-position:10px 5px;background-repeat:no-repeat');
assert.equal(Dex.getTeambuilderSprite({species: 'gyarados', shiny: true}, 3), 'background-image:url(https://play.pokemonshowdown.com/sprites/gen3-shiny/gyarados.png);background-position:10px 5px;background-repeat:no-repeat');
});
it('getTeambuilderSpriteData', () => {
// Basic
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'foobar'}), {spriteDir: 'sprites/gen5', spriteid: '0', x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'pikachu'}), {spriteDir: 'sprites/dex', spriteid: 'pikachu', x: -2, y: -3});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'pikachu'}, 1), { spriteDir: 'sprites/gen1', spriteid: 'pikachu', x: 10, y: 5 });
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'gyarados', shiny: true}, 3), {spriteDir: 'sprites/gen3', spriteid: 'gyarados', shiny: true, x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'eiscue', spriteid: 'pikachu'}), {spriteDir: 'sprites/gen5', spriteid: 'pikachu', x: 10, y: 5});
// XY Dex
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'eiscue'}), {spriteDir: 'sprites/gen5', spriteid: 'eiscue', x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'melmetal'}, 7), {spriteDir: 'sprites/dex', spriteid: 'melmetal', x: -6, y: -7});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Araquanid-Totem'}), {spriteDir: 'sprites/dex', spriteid: 'araquanid', x: -6, y: -7});
// Special XY Offsets
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Arceus-Grass'}, 7), {spriteDir: 'sprites/dex', spriteid: 'arceus-grass', x: -2, y: 7});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Arceus-Electric'}, 4), {spriteDir: 'sprites/gen4', spriteid: 'arceus-electric', x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Garchomp'}), {spriteDir: 'sprites/dex', spriteid: 'garchomp', x: -2, y: 2});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Garchomp'}, 5), {spriteDir: 'sprites/gen5', spriteid: 'garchomp', x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Garchomp-Mega'}), {spriteDir: 'sprites/dex', spriteid: 'garchomp-mega', x: -2, y: 0});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Garchomp-Mega'}, 5), {spriteDir: 'sprites/gen5', spriteid: 'garchomp-mega', x: 10, y: 5});
// Rollup
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Garchomp'}, 1), {spriteDir: 'sprites/gen4', spriteid: 'garchomp', x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Froakie'}, 2), {spriteDir: 'sprites/gen5', spriteid: 'froakie', x: 10, y: 5});
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'Tyranitar'}, 1), {spriteDir: 'sprites/gen2', spriteid: 'tyranitar', x: 10, y: 5});
// No Past Gens
withPrefs({nopastgens: true}, () => {
assert.deepEqual(Dex.getTeambuilderSpriteData({species: 'pikachu'}, 1), {spriteDir: 'sprites/dex', spriteid: 'pikachu', x: -2, y: -3});
});
});
});