Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INHERITING SPRITES/CRIES IS BECOMING REAL #40

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions build-tools/build-indexes
Original file line number Diff line number Diff line change
Expand Up @@ -1760,15 +1760,55 @@ function buildModSprites() {
const subFolders = ['anifront', 'anifront-shiny','aniback','aniback-shiny','front', 'front-shiny', 'back', 'back-shiny', 'icons', 'types', 'items', 'cries'];
for (const j in subFolders) {
const subF = subFolders[j];
const spritePath = 'caches/DH2/data/mods/' + modName + (j === 'cries' ? '/audio/' : '/sprites/') + subF;
const spritePath = 'caches/DH2/data/mods/' + modName + (subF === 'cries' ? '/audio/cries' : ('/sprites/' + subF));
const spriteDir = fs.existsSync(spritePath) ? fs.readdirSync(spritePath) : '';
let inheritDataPresent = false;
for (const sprI in spriteDir) {
let id = spriteDir[sprI];
if (id === 'inherit') {
inheritDataPresent = true;
continue;
}
const ext = id.split(".")[1];
id = toID(id.slice(0, id.length - 4));
modSprites[id] ||= {};
modSprites[id][modName] ||= [];
modSprites[id][modName].push(subF);
modSprites[id][modName][subF] = null;
}
if (!inheritDataPresent) continue;
try {
//We're not outright making "inherit" a JSON because the other files would attempt to copypaste it and it would cause problems
const mappings = JSON.parse(fs.readFileSync(spritePath + "/inherit"));
for (const mon in mappings) {
//null is our indicator for the presence of a custom sprite already being uploaded
//Also you can't inherit from yourself
if (mappings[mon] === null || mappings[mon] === mon) continue;
modSprites[mon] ||= {};
modSprites[mon][modName] ||= {};
//Skip if we already have custom data on this mon
if (modSprites[mon][modName].hasOwnProperty(subF)) continue;
let inherited = mappings[mon];
if (modSprites[inherited] && modSprites[inherited][modName] && modSprites[inherited][modName].hasOwnProperty(subF)) {
//If we already decided that the inheritor inherits in of itself we inherit from that
//(cycles result in neither inheriting)
modSprites[mon][modName][subF] = modSprites[inherited][modName][subF];
} else {
//We don't have inherit data for this mapping, so let us inherit
while (mappings.hasOwnProperty(inherited) && inherited !== mon) {
//If there is a chain of inheritance track it to the source
inherited = mappings[inherited];
}
if (inherited === mon) {
//CYCLE SPOTTED, DO NOT INHERIT
modSprites[mon][modName][subF] = null;
} else {
//Now we inherit
modSprites[mon][modName][subF] = inherited;
}
}
}
} catch (e) {
console.log("WARNING: Failed to read inherit file under " + spritePath + " (it's meant to be formatted like a JSON)");
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions play.pokemonshowdown.com/js/client-teambuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3584,8 +3584,8 @@
var baseid = toID(species.baseSpecies);
var forms = [baseid].concat(species.cosmeticFormes.map(toID));

let modSprite = Dex.getSpriteMod(mod, baseid, 'front', species.exists !== false)
|| Dex.getSpriteMod(mod, species.id, 'front', species.exists !== false);
let modSprite = Dex.getSpriteMod(mod, baseid, 'front', species.exists !== false).mod
|| Dex.getSpriteMod(mod, species.id, 'front', species.exists !== false).mod;
let resourcePrefix;
let d;
if (modSprite) {
Expand Down
176 changes: 104 additions & 72 deletions play.pokemonshowdown.com/src/battle-dex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,18 +475,28 @@ const Dex = new class implements ModdedDex {
// getSpriteMod is used to find the correct mod folder for the sprite url to use
// id is the name of the pokemon, type, or item. folder refers to "front", or "back-shiny" etc. overrideStandard is false for custom elements and true for canon elements
getSpriteMod(optionsMod: string, spriteId: string, filepath: string, overrideStandard: boolean = false) {
if (!window.ModSprites[spriteId]) return '';
if ((!optionsMod || !window.ModSprites[spriteId][optionsMod]) && !overrideStandard) { // for custom elements only, it will use sprites from another mod if the mod provided doesn't have one
for (const modName in window.ModSprites[spriteId]) {
if (window.ModSprites[spriteId][modName].includes(filepath)) return modName;
if (window.ModSprites[spriteId][modName].includes('ani' + filepath)) return modName;
//Setting default value; This is returned if realmon or otherwise no custom sprite data
//(Implementing it this way helps prioritize mods where it has a sprite over mods where it borrows for custom elements)
let pick = {mod: '', inherit: null};
if (!window.ModSprites[spriteId]) return pick;
if (optionsMod && window.ModSprites[spriteId][optionsMod]) {
for (const prefix of ['ani', '']) {
if (window.ModSprites[spriteId][optionsMod].hasOwnProperty(prefix + filepath))
//It won't matter if it inherits in another mod or not, point is it has data in this mod
return {mod: optionsMod, inherit: window.ModSprites[spriteId][optionsMod][prefix + filepath]};
}
}
if (optionsMod && window.ModSprites[spriteId][optionsMod]) {
if (window.ModSprites[spriteId][optionsMod].includes('ani' + filepath)) return optionsMod;
if (window.ModSprites[spriteId][optionsMod].includes(filepath)) return optionsMod;
else if (!overrideStandard) { // for custom elements only, it will use sprites from another mod if the mod provided doesn't have one
for (const modName in window.ModSprites[spriteId]) {
for (const prefix of ['', 'ani']) {
const entry = window.ModSprites[spriteId][modName][prefix + filepath];
//With other mods, we prioritize ones that actually have the sprite
if (entry === null) return {mod: modName, inherit: null};
if (entry) pick = {mod: modName, inherit: entry};
}
}
}
return ''; // must be a real Pokemon or not have custom sprite data
return pick;
}

loadSpriteData(gen: 'xy' | 'bw') {
Expand All @@ -502,6 +512,46 @@ const Dex = new class implements ModdedDex {
document.getElementsByTagName('body')[0].appendChild(el);
}


getCryUrl(miscData: any, species: Species, crymod: string, overrideStandard: boolean = false, resourcePrefix: string) {
const data = this.getSpriteMod(crymod, toID(species.spriteid), 'cries', overrideStandard);
//This is null if we're either using the provided cry or didn't find a cry in the first place
if (data.inherit) {
const newspecies = Dex.species.get(data.inherit);
miscData.num = newspecies.num
return this.getCryUrl(miscData, Dex.species.get(data.inherit), crymod, overrideStandard, resourcePrefix);
}
let url = '';
if (species.exists && miscData.num !== 0 && miscData.num > -5000) {
let baseSpeciesid = toID(species.baseSpecies);
if (BattlePokemonSprites[baseSpeciesid] || BattlePokemonSpritesBW[baseSpeciesid]) {
url = 'audio/cries/' + baseSpeciesid;
let formeid = species.formeid;
if (species.isMega || formeid && (
['-crowned','-eternal','-eternamax','-four','-hangry','-hero',
'-lowkey','-noice','-primal','-rapidstrike', '-roaming', '-school',
'-sky', '-starter', '-super', '-therian', '-unbound'].includes(formeid)
|| ['calyrex','kyurem','cramorant','indeedee','lycanroc','necrozma',
'oinkologne','oricorio','slowpoke','tatsugiri','zygarde'].includes(baseSpeciesid)
)) {
url += formeid;
}
url += '.mp3';
}
}

// Mod Cries
if (crymod === 'digimon') {
url = `sprites/${options.mod}/audio/${toID(species.baseSpecies)}.mp3`;
}
//If we already have a cry url we load from the main server, otherwise we attempt to load from our repository
if ((!(url &&= 'https://' + Config.routes.psmain + '/' + url)) && data.mod) {
url = resourcePrefix + data.mod + '/audio/cries/' + species.id + '.mp3';
}
//console.log ("URL for " + species.id + " cry is " + url);
return url;
}

getSpriteData(pokemon: Pokemon | Species | string, isFront: boolean, options: {
gen?: number,
shiny?: boolean,
Expand Down Expand Up @@ -537,13 +587,16 @@ const Dex = new class implements ModdedDex {
let spriteDir = 'sprites/';
let hasCustomSprite = false;
let modSpriteId = toID(modSpecies.spriteid);
options.mod = this.getSpriteMod(options.mod, modSpriteId, isFront ? 'front' : 'back', modSpecies.exists);
let firstmod = options.mod;
const searchedMod = this.getSpriteMod(options.mod, modSpriteId, isFront ? 'front' : 'back', modSpecies.exists);
options.mod = searchedMod.mod;
let cryResourcePrefix = resourcePrefix;
if (options.mod) {
resourcePrefix = Dex.modResourcePrefix;
spriteDir = `${options.mod}/sprites/`;
hasCustomSprite = true;
if (this.getSpriteMod(options.mod, modSpriteId, (isFront ? 'front' : 'back') + '-shiny', modSpecies.exists) === '') options.shiny = false;
}
if (this.getSpriteMod(options.mod, modSpriteId, (isFront ? 'front' : 'back') + '-shiny', modSpecies.exists).mod === '') options.shiny = false;
} else if (this.getSpriteMod(firstmod, modSpriteId, 'cries', modSpecies.exists).mod) cryResourcePrefix = Dex.modResourcePrefix;

const species = Dex.species.get(pokemon);
// Gmax sprites are already extremely large, so we don't need to double.
Expand Down Expand Up @@ -603,45 +656,22 @@ const Dex = new class implements ModdedDex {
if (!animationData) animationData = {};
if (!miscData) miscData = {};

if (miscData.num !== 0 && miscData.num > -5000) {
let baseSpeciesid = toID(species.baseSpecies);
spriteData.cryurl = 'audio/cries/' + baseSpeciesid;
let formeid = species.formeid;
if (species.isMega || formeid && (
formeid === '-crowned' ||
formeid === '-eternal' ||
formeid === '-eternamax' ||
formeid === '-four' ||
formeid === '-hangry' ||
formeid === '-hero' ||
formeid === '-lowkey' ||
formeid === '-noice' ||
formeid === '-primal' ||
formeid === '-rapidstrike' ||
formeid === '-roaming' ||
formeid === '-school' ||
formeid === '-sky' ||
formeid === '-starter' ||
formeid === '-super' ||
formeid === '-therian' ||
formeid === '-unbound' ||
baseSpeciesid === 'calyrex' ||
baseSpeciesid === 'kyurem' ||
baseSpeciesid === 'cramorant' ||
baseSpeciesid === 'indeedee' ||
baseSpeciesid === 'lycanroc' ||
baseSpeciesid === 'necrozma' ||
baseSpeciesid === 'oinkologne' ||
baseSpeciesid === 'oricorio' ||
baseSpeciesid === 'slowpoke' ||
baseSpeciesid === 'tatsugiri' ||
baseSpeciesid === 'zygarde'
)) {
spriteData.cryurl += formeid;
}
spriteData.cryurl += '.mp3';
spriteData.cryurl = Dex.getCryUrl(miscData, species, firstmod, modSpecies.exists, cryResourcePrefix);
if (searchedMod.inherit) {
let newoptions = {
gen: mechanicsGen,
shiny: spriteData.shiny,
gender: options.gender || 'N',
afd: options.afd || false,
noScale: options.noScale || false,
mod: firstmod,
dynamax: isDynamax
};
let overwriteData = Dex.getSpriteData(searchedMod.inherit, isFront, newoptions);
overwriteData.cryurl = spriteData.cryurl;
return overwriteData;
}

if (options.shiny && mechanicsGen > 1) dir += '-shiny';

// April Fool's 2014
Expand All @@ -661,21 +691,6 @@ const Dex = new class implements ModdedDex {
}
return spriteData;
}

// Mod Cries
if (options.mod === 'digimon') {
spriteData.cryurl = `sprites/${options.mod}/audio/${toID(species.baseSpecies)}.mp3`;
}
//If we already have a cry url we load from the main server, otherwise we try to search for the presence of a custom cry
if (!(spriteData.cryurl &&= 'https://' + Config.routes.psmain + '/' + spriteData.cryurl)) {
//For whatever reason if there is a cry but no true sprite data then options.mod becomes '' regardless of mod
//TODO: Possibly fix that? I wouldn't prioritize it though
if (window.ModSprites[modSpriteId]?.[options.mod]?.includes('cries')) {
spriteData.cryurl = resourcePrefix + options.mod + '/audio/cries/' + speciesid + '.mp3';
} else { //We couldn't find a cry
spriteData.cryurl = '';
}
}

let hasCustomAnim = false;
if (hasCustomSprite && window.ModSprites[modSpriteId][options.mod].includes('ani' + facing)){
Expand All @@ -694,7 +709,6 @@ const Dex = new class implements ModdedDex {
spriteData.w = animationData[facing].w;
spriteData.h = animationData[facing].h;
spriteData.url += dir + '/' + name + '.gif';
console.log(animationData[facing]);
} else {
// There is no entry or enough data in pokedex-mini.js
// Handle these in case-by-case basis; either using BW sprites or matching the played gen.
Expand Down Expand Up @@ -807,8 +821,18 @@ const Dex = new class implements ModdedDex {
let fainted = ((pokemon as Pokemon | ServerPokemon)?.fainted ? `;opacity:.3;filter:grayscale(100%) brightness(.5)` : ``);
Dex.species.get(id);
let species = window.BattlePokedexAltForms && window.BattlePokedexAltForms[id] ? window.BattlePokedexAltForms[id] : Dex.species.get(id);
mod = this.getSpriteMod(mod, id, 'icons', species.exists !== false);
if (mod) return `background:transparent url(${this.modResourcePrefix}${mod}/sprites/icons/${id}.png) no-repeat scroll -0px -0px${fainted}`;
const moddata = this.getSpriteMod(mod, id, 'icons', species.exists !== false);
//TODO: Figure out a way for inherited sprites to show up as fainted
//("?" icons will be used as placeholders for fainted inherited sprites for the time being)
if (!moddata.inherit) {
mod = moddata.mod;
if (mod) {
//TODO: If female try and check if "../icons/${id}f" is available, fall back on the default if no such file is found (which is to say there are no gender differences in the icon)
return `background:transparent url(${this.modResourcePrefix}${mod}/sprites/icons/${id}.png) no-repeat scroll -0px -0px${fainted}`;
}
} else if (!fainted) {
return this.getPokemonIcon(moddata.inherit, facingLeft || false, mod);
}
return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-sheet.png?v16) no-repeat scroll -${left}px -${top}px${fainted}`;

}
Expand All @@ -821,12 +845,14 @@ const Dex = new class implements ModdedDex {
spriteid = species.spriteid || toID(pokemon.species);
}
if (mod && window.ModConfig[mod].spriteGen) gen = window.ModConfig[mod].spriteGen;
mod = this.getSpriteMod(mod, id, 'front', species.exists !== false);
const moddata = this.getSpriteMod(mod, id, 'front', species.exists !== false);
if (moddata.inherit) return this.getTeambuilderSpriteData({species: moddata.inherit, spriteid: moddata.inherit, shiny: pokemon.shiny}, gen, mod);
mod = moddata.mod;
if (mod) {
return {
spriteDir: `${mod}/sprites/front`,
spriteid,
shiny: (this.getSpriteMod(mod, id, 'front-shiny', species.exists !== false) !== null && pokemon.shiny),
shiny: (this.getSpriteMod(mod, id, 'front-shiny', species.exists !== false).mod !== null && pokemon.shiny),
x: 10,
y: 5,
};
Expand Down Expand Up @@ -892,7 +918,9 @@ const Dex = new class implements ModdedDex {
getItemIcon(item: any, mod: string = '') {
let num = 0;
if (typeof item === 'string' && exports.BattleItems) item = exports.BattleItems[toID(item)];
mod = this.getSpriteMod(mod, item.id, 'items');
const moddata = this.getSpriteMod(mod, item.id, 'items');
if (moddata.inherit) return this.getItemIcon(mod, moddata.inherit, 'items');
mod = moddata.mod;
if (mod) return `background:transparent url(${this.modResourcePrefix}${mod}/sprites/items/${item.id}.png) no-repeat`;
if (item?.spritenum) num = item.spritenum;

Expand All @@ -905,14 +933,18 @@ const Dex = new class implements ModdedDex {
type = this.types.get(type).name;
if (!type) type = '???';
let sanitizedType = type.replace(/\?/g, '%3f');
mod = this.getSpriteMod(mod, toID(type), 'types');
const moddata = this.getSpriteMod(mod, toID(type), 'types');
//Only real application I could see is mixing up type visuals but eh, consistency
if (moddata.inherit) return this.getTypeIcon(moddata.inherit, mod);
mod = moddata.mod;
if (mod && (type !== '???')) {
return `<img src="${this.modResourcePrefix}${mod}/sprites/types/${toID(type)}.png" alt="${type}" class="pixelated${b ? ' b' : ''}" />`;
} else {
return `<img src="${Dex.resourcePrefix}sprites/types/${sanitizedType}.png" alt="${type}" height="14" width="32" class="pixelated${b ? ' b' : ''}" />`;
}
}

//TODO: Support replaced sprites from mods maybe?
getCategoryIcon(category: string | null) {
const categoryID = toID(category);
let sanitizedCategory = '';
Expand Down
Loading