From 4b74f4d750833317cdbce8b2874cbc7dd16b1328 Mon Sep 17 00:00:00 2001 From: Clement Balea Date: Thu, 27 Jun 2024 01:38:12 +0200 Subject: [PATCH] Feature/add classic (#60) * add getImageExtension * manage classic views * add classic doc * change port to be aligned with demo --- README.md | 95 +++++++++++++++++++++++------------ character_modeling.js | 47 ++++++++++++----- index.js | 23 +++++++-- tests/setup.test.js | 1 + types/character_modeling.d.ts | 52 ++++++++++++++++++- types/index.d.ts | 6 ++- 6 files changed, 172 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 91ee2b9..0da9e54 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ This library allows eu to generate a 3d model character with his customization a use [Wowhead](https://classic.wowhead.com/) libraries to generate the rendering. This lib works with WotLK and Retail wow versions. ## 🛠 Requirements -To utilize this library effectively, you need a copy of specific files sourced from Wowhead. While there are various methods +To utilize this library effectively, you need a copy of specific files sourced from Wowhead. While there are various methods to obtain these files, this document demonstrates the use of a replica server. > ⚠️ Warning: The gaming data belongs to Wowhead. It's recommended to use them for personal purposes or small-scale projects, and definitely not for large commercial endeavors. ### CORS policies Bypass using a replica server. This lib calls the replica, which fetches files from the gaming repository. -For this purpose, you can use [bypass-cors-policies](https://github.com/Miorey/bypass-cors-policies), which is specifically designed for such use cases. +For this purpose, you can use [bypass-cors-policies](https://github.com/Miorey/bypass-cors-policies), which is specifically designed for such use cases. You can use it with docker or node.js. #### 🐳 Using Docker @@ -50,7 +50,7 @@ Regarding the `viewer.min.js` you can download it from zaming or from your local ``` AND ```html - + ``` OR ```html @@ -60,8 +60,8 @@ OR ### Setup #### WotLK or Retail ? -The library works well with both retail and WotLK. -For WotLK, some adjustments need to be made. +The library works well with both retail and WotLK. +For WotLK, some adjustments need to be made. By default, the library is set up to work with WotLK by using this URL to retrieve the display ID for old items. ```js window.WOTLK_TO_RETAIL_DISPLAY_ID_API=`https://wotlk.murlocvillage.com/api/items` @@ -74,12 +74,12 @@ When you want to use the lib for wow retail you just need to set this var to `un #### Data Path -Lastly, you must set up the `CONTENT_PATH` environment variable. -This indicates the location of the data required to render the canvas. +Lastly, you must set up the `CONTENT_PATH` environment variable. +This indicates the location of the data required to render the canvas. If you're using the provided `docker-compose` example, then: ```js - window.CONTENT_PATH = `http://localhost:2999/modelviewer/live/` + window.CONTENT_PATH = `http://localhost:3000/modelviewer/live/` ``` @@ -157,7 +157,7 @@ To load the character my beautiful female gnome warlock - + @@ -191,14 +191,14 @@ The description of the character is ```json { - "race":7, - "gender":1, - "skin":4, - "face":0, - "hairStyle":5, - "hairColor":5, - "facialStyle":5, - "items": [[1,1170],[3,4925],[5,9575],[6,25235],[7,2311],[8,21154],[9,14618],[10,9534],[15,17238],[21,20379],[22,28787]] + "race":7, + "gender":1, + "skin":4, + "face":0, + "hairStyle":5, + "hairColor":5, + "facialStyle":5, + "items": [[1,1170],[3,4925],[5,9575],[6,25235],[7,2311],[8,21154],[9,14618],[10,9534],[15,17238],[21,20379],[22,28787]] } ``` @@ -386,32 +386,61 @@ To get the npc display ids you can go [here](https://wow.tools/dbc/?dbc=creature Prince of Lordaeron: ```js -const model = await generateModels(1, `#model_3d`, {type: 8, id: 24949}); +const model = await generateModels(1, `#model_3d`, {type: modelingType.NPC, id: 24949}); ``` To display Atiesh, Greatstaff of the Guardian ```js -const model = await generateModels(1, `#model_3d`, {type: 1, id: 193841}); +const model = await generateModels(1, `#model_3d`, {type: modelingType.ITEM, id: 193841}); ``` #### Table of types -Description | Type -:--------------:|:----:| -Item | 1 | -Helm | 2 | -Shoulder | 4 | -NPC | 8 | -Character | 16 | -Humanoidnpc | 32 | -Object | 64 | -Armor | 128 | -Path | 256 | -Itemvisual | 512 | -Collection | 102 | +Description | Type | `modelingType` | +:--------------:|:----:|:-----------------:| +Item | 1 | `.ITEM` | +Helm | 2 | `.HELM` | +Shoulder | 4 | `.SHOULDER` | +NPC | 8 | `.NPC` | +Character | 16 | `.CHARACTER` | +Humanoidnpc | 32 | `.HUMANOIDNPC` | +Object | 64 | `.OBJECT` | +Armor | 128 | `.ARMOR` | +Path | 256 | `.PATH` | +Itemvisual | 512 | `.ITEMVISUAL` | +Collection | 1024 | `.COLLECTION` | + +### Classic character display. +To display the character of WoW classic you need to change your scripts: +```html + +``` +and change following vars to: +```js + window.CONTENT_PATH = `http://localhost:3000/modelviewer/classic/` + window.WOTLK_TO_RETAIL_DISPLAY_ID_API = undefined +``` +when you call `generateModels` you need to add a new param at the end `"classic"` + +```js +generateModels(1.5, `#model_3d1`, character, "classic"); +``` +⚠️For some items you can't display them with a customize character look, in this case you need to use +a new option `"noCharCustomization": true`. + +Ex: +```js +const character2 = { + "race": 1, + "gender": 0, + "items": [[21, 679611]], + "noCharCustomization": true +} +window.model2 = await generateModels(1.5, `#model_3d2`, character2, "classic"); +``` # Updates -As this library is based on a minified version of the Wowhead model viewer, regular upgrades of this library may require you to clear your cached data. +As this library is based on a minified version of the Wowhead model viewer, regular upgrades of this library may require you to clear your cached data. If you are using a Docker `bypass-cors-policies` container, you can follow these steps to clean up the cache: ```sh diff --git a/character_modeling.js b/character_modeling.js index 33f8bd6..cf1995a 100644 --- a/character_modeling.js +++ b/character_modeling.js @@ -9,6 +9,20 @@ const NOT_DISPLAYED_SLOTS = [ 14, // trinket2 ] +const modelingType = { + ARMOR: 128, + CHARACTER: 16, + COLLECTION: 1024, + HELM: 2, + HUMANOIDNPC: 32, + ITEM: 1, + ITEMVISUAL: 512, + NPC: 8, + OBJECT: 64, + PATH: 256, + SHOULDER: 4 +} + const characterPart = () => { const ret = { Face: `face`, @@ -108,18 +122,20 @@ function optionsFromModel(model, fullOptions) { // slot ids on model viewer const characterItems = (model.items) ? model.items.filter(e => !NOT_DISPLAYED_SLOTS.includes(e[0])) : [] const options = getCharacterOptions(model, fullOptions) - - - return { + let charCustomization = { + options: options + } + const ret = { items: characterItems, - charCustomization: { - options: options - }, models: { id: race*2-1+gender, - type: 16 + type: modelingType.CHARACTER }, } + if(!model.noCharCustomization) { + ret.charCustomization = charCustomization + } + return ret } @@ -129,9 +145,10 @@ function optionsFromModel(model, fullOptions) { * @param item{number}: Item id * @param slot{number}: Item slot number * @param displayId{number}: DisplayId of the item + * @param env {('classic'|'live')}: select game env * @return {Promise} */ -async function getDisplaySlot(item, slot, displayId) { +async function getDisplaySlot(item, slot, displayId, env=`live`) { if (typeof item !== `number`) { throw new Error(`item must be a number`) } @@ -145,7 +162,10 @@ async function getDisplaySlot(item, slot, displayId) { } try { - await fetch(`${window.CONTENT_PATH}meta/armor/${slot}/${displayId}.json`) + const jsonPath = (env === `classic` && [21, 22].includes(slot)) ? + `${window.CONTENT_PATH}meta/item/${displayId}.json` : + `${window.CONTENT_PATH}meta/armor/${slot}/${displayId}.json` + await fetch(jsonPath) .then(response => response.json()) return { @@ -195,9 +215,10 @@ async function getDisplaySlot(item, slot, displayId) { * Returns a 2-dimensional list the inner list contains on first position the item slot, the second the item * display-id ex: [[1,1170],[3,4925]] * @param {*[{item: {entry: number, displayid: number}, transmog: {entry: number, displayid: number}, slot: number}]} equipments + * @param env {('classic'|'live')}: select game enve * @returns {Promise} */ -async function findItemsInEquipments(equipments) { +async function findItemsInEquipments(equipments, env=`live`) { for (const equipment of equipments) { if (NOT_DISPLAYED_SLOTS.includes(equipment.slot)) { continue @@ -207,7 +228,8 @@ async function findItemsInEquipments(equipments) { const displaySlot = await getDisplaySlot( displayedItem.entry, equipment.slot, - displayedItem.displayid + displayedItem.displayid, + env ) equipment.displaySlot = displaySlot.displaySlot equipment.displayId = displaySlot.displayId @@ -248,5 +270,6 @@ export { findItemsInEquipments, getDisplaySlot, getCharacterOptions, - characterPart + characterPart, + modelingType } diff --git a/index.js b/index.js index f2c1911..de1409e 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,8 @@ import { findRaceGenderOptions, optionsFromModel, getDisplaySlot, - findItemsInEquipments + findItemsInEquipments, + modelingType } from "./character_modeling.js" import "./setup.js" @@ -13,9 +14,10 @@ import "./setup.js" * @param aspect {number}: Size of the character * @param containerSelector {string}: jQuery selector on the container * @param model {{}|{id: number, type: number}}: A json representation of a character + * @param env {('classic'|'live')}: select game enve * @returns {Promise} */ -async function generateModels(aspect, containerSelector, model) { +async function generateModels(aspect, containerSelector, model, env=`live`) { let modelOptions let fullOptions if (model.id && model.type) { @@ -32,15 +34,29 @@ async function generateModels(aspect, containerSelector, model) { ) modelOptions = optionsFromModel(model, fullOptions) } + if(env === `classic`) { + modelOptions = { + dataEnv: `classic`, + env: `classic`, + gameDataEnv: `classic`, + hd: false, + ...modelOptions + } + } else { + modelOptions = { + hd: true, + ...modelOptions + } + } const models = { type: 2, contentPath: window.CONTENT_PATH, // eslint-disable-next-line no-undef container: jQuery(containerSelector), aspect: aspect, - hd: true, ...modelOptions } + console.log(`Creating viewer with options`, models) // eslint-disable-next-line no-undef const wowModelViewer = await new WowModelViewer(models) @@ -58,4 +74,5 @@ export { generateModels, getDisplaySlot, findItemsInEquipments, + modelingType } diff --git a/tests/setup.test.js b/tests/setup.test.js index 60288ab..7c10152 100644 --- a/tests/setup.test.js +++ b/tests/setup.test.js @@ -17,3 +17,4 @@ describe(`WH`, () => { expect(expect(WH.WebP.getImageExtension()).toEqual(`.webp`)) }) }) + diff --git a/types/character_modeling.d.ts b/types/character_modeling.d.ts index 7114fc3..cad1336 100644 --- a/types/character_modeling.d.ts +++ b/types/character_modeling.d.ts @@ -30,17 +30,19 @@ export function findRaceGenderOptions(race: number, gender: number): Promise} */ -export function findItemsInEquipments(equipments: any): Promise; +export function findItemsInEquipments(equipments: any, env?: ('classic' | 'live')): Promise; /** * * @param item{number}: Item id * @param slot{number}: Item slot number * @param displayId{number}: DisplayId of the item + * @param env {('classic'|'live')}: select game env * @return {Promise} */ -export function getDisplaySlot(item: number, slot: number, displayId: number): Promise; +export function getDisplaySlot(item: number, slot: number, displayId: number, env?: ('classic' | 'live')): Promise; /** * * @param {Object} character - The character object. @@ -65,3 +67,49 @@ export function getCharacterOptions(character: { race: number; skin: number; }, fullOptions: any): []; +export function characterPart(): { + Face: string; + "Skin Color": string; + "Hair Style": string; + "Hair Color": string; + "Facial Hair": string; + Mustache: string; + Beard: string; + Sideburns: string; + "Face Shape": string; + Eyebrow: string; + "Jaw Features": any; + "Face Features": any; + "Skin Type": any; + Ears: string; + "Fur Color": string; + Snout: string; + Blindfold: any; + Tattoo: any; + "Eye Color": any; + "Tattoo Color": any; + Armbands: any; + "Jewelry Color": any; + Bracelets: any; + Necklace: any; + Earring: any; + "Primary Color": string; + "Secondary Color Strength": string; + "Secondary Color": string; + "Horn Color": string; + Horns: string; + "Body Size": string; +}; +export namespace modelingType { + let ARMOR: number; + let CHARACTER: number; + let COLLECTION: number; + let HELM: number; + let HUMANOIDNPC: number; + let ITEM: number; + let ITEMVISUAL: number; + let NPC: number; + let OBJECT: number; + let PATH: number; + let SHOULDER: number; +} diff --git a/types/index.d.ts b/types/index.d.ts index 1de0353..eb17bc7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -4,13 +4,15 @@ import { findRaceGenderOptions } from "./character_modeling.js"; * @param aspect {number}: Size of the character * @param containerSelector {string}: jQuery selector on the container * @param model {{}|{id: number, type: number}}: A json representation of a character + * @param env {('classic'|'live')}: select game enve * @returns {Promise} */ export function generateModels(aspect: number, containerSelector: string, model: {} | { id: number; type: number; -}): Promise; +}, env?: ('classic' | 'live')): Promise; import { getDisplaySlot } from "./character_modeling.js"; import { findItemsInEquipments } from "./character_modeling.js"; +import { modelingType } from "./character_modeling.js"; import { WowModelViewer } from './wow_model_viewer.js'; -export { findRaceGenderOptions, getDisplaySlot, findItemsInEquipments }; +export { findRaceGenderOptions, getDisplaySlot, findItemsInEquipments, modelingType };