You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 15:06:20 +00:00
@@ -76,6 +76,7 @@ export class RoomObjectVariable
|
|||||||
public static FURNITURE_BRANDING_OFFSET_X: string = 'furniture_branding_offset_x';
|
public static FURNITURE_BRANDING_OFFSET_X: string = 'furniture_branding_offset_x';
|
||||||
public static FURNITURE_BRANDING_OFFSET_Y: string = 'furniture_branding_offset_y';
|
public static FURNITURE_BRANDING_OFFSET_Y: string = 'furniture_branding_offset_y';
|
||||||
public static FURNITURE_BRANDING_OFFSET_Z: string = 'furniture_branding_offset_z';
|
public static FURNITURE_BRANDING_OFFSET_Z: string = 'furniture_branding_offset_z';
|
||||||
|
public static FURNITURE_BRANDING_SCALE: string = 'furniture_branding_scale';
|
||||||
public static FURNITURE_BADGE_IMAGE_STATUS: string = 'furniture_badge_image_status';
|
public static FURNITURE_BADGE_IMAGE_STATUS: string = 'furniture_badge_image_status';
|
||||||
public static FURNITURE_BADGE_ASSET_NAME: string = 'furniture_badge_asset_name';
|
public static FURNITURE_BADGE_ASSET_NAME: string = 'furniture_badge_asset_name';
|
||||||
public static FURNITURE_BADGE_VISIBLE_IN_STATE: string = 'furniture_badge_visible_in_state';
|
public static FURNITURE_BADGE_VISIBLE_IN_STATE: string = 'furniture_badge_visible_in_state';
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface IRoomObjectSprite
|
|||||||
texture: Texture;
|
texture: Texture;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
scale: number;
|
||||||
offsetX: number;
|
offsetX: number;
|
||||||
offsetY: number;
|
offsetY: number;
|
||||||
flipH: boolean;
|
flipH: boolean;
|
||||||
|
|||||||
@@ -200,7 +200,11 @@ export class AssetManager implements IAssetManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const texture = await Assets.load<Texture>(url);
|
// External raster images (png/jpg/webp/…). Pixi's Assets.load does
|
||||||
|
// not reliably load arbitrary cross-origin images in this setup, so
|
||||||
|
// load them through a CORS-enabled <img> + Texture.from — the same
|
||||||
|
// approach the badge / dynamic-thumbnail visualizations use.
|
||||||
|
const texture = await this.loadExternalImageTexture(url);
|
||||||
|
|
||||||
if(texture) this.setTexture(url, texture);
|
if(texture) this.setTexture(url, texture);
|
||||||
}
|
}
|
||||||
@@ -213,6 +217,47 @@ export class AssetManager implements IAssetManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadExternalImageTexture(url: string): Promise<Texture>
|
||||||
|
{
|
||||||
|
return new Promise<Texture>((resolve, reject) =>
|
||||||
|
{
|
||||||
|
const image = new Image();
|
||||||
|
|
||||||
|
// crossOrigin must be set BEFORE src so the request is made with CORS
|
||||||
|
// and the resulting texture isn't tainted (WebGL would refuse it).
|
||||||
|
image.crossOrigin = 'anonymous';
|
||||||
|
|
||||||
|
image.onload = () =>
|
||||||
|
{
|
||||||
|
image.onload = null;
|
||||||
|
image.onerror = null;
|
||||||
|
|
||||||
|
if(image.complete && (image.width > 0) && (image.height > 0))
|
||||||
|
{
|
||||||
|
const texture = Texture.from(image);
|
||||||
|
|
||||||
|
if(texture.source) texture.source.scaleMode = 'linear';
|
||||||
|
|
||||||
|
resolve(texture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reject(new Error(`image had no dimensions`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
image.onerror = () =>
|
||||||
|
{
|
||||||
|
image.onload = null;
|
||||||
|
image.onerror = null;
|
||||||
|
|
||||||
|
reject(new Error(`image element failed to load (CORS / network / 404)`));
|
||||||
|
};
|
||||||
|
|
||||||
|
image.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async loadLocalRoom(): Promise<void>
|
private async loadLocalRoom(): Promise<void>
|
||||||
{
|
{
|
||||||
const roomDataModule = await import('./assets/room/room.asset.json');
|
const roomDataModule = await import('./assets/room/room.asset.json');
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class FurnitureRoomBrandingLogic extends FurnitureLogic
|
|||||||
public static OFFSETX_KEY: string = 'offsetX';
|
public static OFFSETX_KEY: string = 'offsetX';
|
||||||
public static OFFSETY_KEY: string = 'offsetY';
|
public static OFFSETY_KEY: string = 'offsetY';
|
||||||
public static OFFSETZ_KEY: string = 'offsetZ';
|
public static OFFSETZ_KEY: string = 'offsetZ';
|
||||||
|
public static SCALE_KEY: string = 'scale';
|
||||||
|
|
||||||
protected _disableFurnitureSelection: boolean;
|
protected _disableFurnitureSelection: boolean;
|
||||||
protected _hasClickUrl: boolean;
|
protected _hasClickUrl: boolean;
|
||||||
@@ -93,6 +94,10 @@ export class FurnitureRoomBrandingLogic extends FurnitureLogic
|
|||||||
if(!isNaN(offsetY)) this.object.model.setValue(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Y, offsetY);
|
if(!isNaN(offsetY)) this.object.model.setValue(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Y, offsetY);
|
||||||
if(!isNaN(offsetZ)) this.object.model.setValue(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Z, offsetZ);
|
if(!isNaN(offsetZ)) this.object.model.setValue(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Z, offsetZ);
|
||||||
|
|
||||||
|
const scaleRaw = parseInt(objectData.getValue(FurnitureRoomBrandingLogic.SCALE_KEY));
|
||||||
|
const scale = isNaN(scaleRaw) ? 100 : scaleRaw;
|
||||||
|
this.object.model.setValue(RoomObjectVariable.FURNITURE_BRANDING_SCALE, scale);
|
||||||
|
|
||||||
let options = (((FurnitureRoomBrandingLogic.IMAGEURL_KEY + '=') + ((imageUrl !== null) ? imageUrl : '')) + '\t');
|
let options = (((FurnitureRoomBrandingLogic.IMAGEURL_KEY + '=') + ((imageUrl !== null) ? imageUrl : '')) + '\t');
|
||||||
|
|
||||||
if(this._hasClickUrl) options = (options + (((FurnitureRoomBrandingLogic.CLICKURL_KEY + '=') + ((clickUrl !== null) ? clickUrl : '')) + '\t'));
|
if(this._hasClickUrl) options = (options + (((FurnitureRoomBrandingLogic.CLICKURL_KEY + '=') + ((clickUrl !== null) ? clickUrl : '')) + '\t'));
|
||||||
@@ -100,6 +105,7 @@ export class FurnitureRoomBrandingLogic extends FurnitureLogic
|
|||||||
options = (options + (((FurnitureRoomBrandingLogic.OFFSETX_KEY + '=') + offsetX) + '\t'));
|
options = (options + (((FurnitureRoomBrandingLogic.OFFSETX_KEY + '=') + offsetX) + '\t'));
|
||||||
options = (options + (((FurnitureRoomBrandingLogic.OFFSETY_KEY + '=') + offsetY) + '\t'));
|
options = (options + (((FurnitureRoomBrandingLogic.OFFSETY_KEY + '=') + offsetY) + '\t'));
|
||||||
options = (options + (((FurnitureRoomBrandingLogic.OFFSETZ_KEY + '=') + offsetZ) + '\t'));
|
options = (options + (((FurnitureRoomBrandingLogic.OFFSETZ_KEY + '=') + offsetZ) + '\t'));
|
||||||
|
options = (options + (((FurnitureRoomBrandingLogic.SCALE_KEY + '=') + scale) + '\t'));
|
||||||
|
|
||||||
this.object.model.setValue(RoomWidgetEnumItemExtradataParameter.INFOSTAND_EXTRA_PARAM, (RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS + options));
|
this.object.model.setValue(RoomWidgetEnumItemExtradataParameter.INFOSTAND_EXTRA_PARAM, (RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS + options));
|
||||||
}
|
}
|
||||||
@@ -147,10 +153,25 @@ export class FurnitureRoomBrandingLogic extends FurnitureLogic
|
|||||||
|
|
||||||
if(!texture)
|
if(!texture)
|
||||||
{
|
{
|
||||||
const status = await asset.downloadAsset(imageUrl);
|
// downloadAsset THROWS on failure (it doesn't return false), and this
|
||||||
|
// method is fire-and-forget — so without this try/catch a failed image
|
||||||
if(!status)
|
// download just vanishes and the furni keeps showing its default. Catch
|
||||||
|
// it, surface the real reason, and flag the failed state.
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
const status = await asset.downloadAsset(imageUrl);
|
||||||
|
|
||||||
|
if(!status)
|
||||||
|
{
|
||||||
|
this.processUpdateMessage(new ObjectAdUpdateMessage(ObjectAdUpdateMessage.IMAGE_LOADING_FAILED));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.error(`[Soundboard/Branding] failed to load branding image "${ imageUrl }":`, error);
|
||||||
|
|
||||||
this.processUpdateMessage(new ObjectAdUpdateMessage(ObjectAdUpdateMessage.IMAGE_LOADING_FAILED));
|
this.processUpdateMessage(new ObjectAdUpdateMessage(ObjectAdUpdateMessage.IMAGE_LOADING_FAILED));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export class RoomObjectSprite implements IRoomObjectSprite
|
|||||||
|
|
||||||
private _width: number = 0;
|
private _width: number = 0;
|
||||||
private _height: number = 0;
|
private _height: number = 0;
|
||||||
|
private _scale: number = 1;
|
||||||
private _offsetX: number = 0;
|
private _offsetX: number = 0;
|
||||||
private _offsetY: number = 0;
|
private _offsetY: number = 0;
|
||||||
private _flipH: boolean = false;
|
private _flipH: boolean = false;
|
||||||
@@ -121,6 +122,21 @@ export class RoomObjectSprite implements IRoomObjectSprite
|
|||||||
return this._height;
|
return this._height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-sprite zoom multiplier (1 = native). Applied on top of the room zoom.
|
||||||
|
public get scale(): number
|
||||||
|
{
|
||||||
|
return this._scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set scale(value: number)
|
||||||
|
{
|
||||||
|
if(this._scale === value) return;
|
||||||
|
|
||||||
|
this._scale = value;
|
||||||
|
|
||||||
|
this._updateCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
public get offsetX(): number
|
public get offsetX(): number
|
||||||
{
|
{
|
||||||
return this._offsetX;
|
return this._offsetX;
|
||||||
|
|||||||
+48
@@ -18,6 +18,7 @@ export class FurnitureBrandedImageVisualization extends FurnitureVisualization
|
|||||||
protected _offsetX: number;
|
protected _offsetX: number;
|
||||||
protected _offsetY: number;
|
protected _offsetY: number;
|
||||||
protected _offsetZ: number;
|
protected _offsetZ: number;
|
||||||
|
protected _imageScale: number;
|
||||||
protected _currentFrame: number;
|
protected _currentFrame: number;
|
||||||
protected _totalFrames: number;
|
protected _totalFrames: number;
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ export class FurnitureBrandedImageVisualization extends FurnitureVisualization
|
|||||||
this._offsetX = 0;
|
this._offsetX = 0;
|
||||||
this._offsetY = 0;
|
this._offsetY = 0;
|
||||||
this._offsetZ = 0;
|
this._offsetZ = 0;
|
||||||
|
this._imageScale = 100;
|
||||||
this._currentFrame = -1;
|
this._currentFrame = -1;
|
||||||
this._totalFrames = -1;
|
this._totalFrames = -1;
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,7 @@ export class FurnitureBrandedImageVisualization extends FurnitureVisualization
|
|||||||
this._offsetX = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_X) || 0);
|
this._offsetX = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_X) || 0);
|
||||||
this._offsetY = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Y) || 0);
|
this._offsetY = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Y) || 0);
|
||||||
this._offsetZ = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Z) || 0);
|
this._offsetZ = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_OFFSET_Z) || 0);
|
||||||
|
this._imageScale = (this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_BRANDING_SCALE) || 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this._imageReady)
|
if(!this._imageReady)
|
||||||
@@ -207,4 +210,49 @@ export class FurnitureBrandedImageVisualization extends FurnitureVisualization
|
|||||||
|
|
||||||
return super.getSpriteAssetName(scale, layerId);
|
return super.getSpriteAssetName(scale, layerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isBrandedImageLayer(scale: number, layerId: number): boolean
|
||||||
|
{
|
||||||
|
return this.getLayerTag(scale, this._direction, layerId) === FurnitureBrandedImageVisualization.BRANDED_IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offsetX/Y move ONLY the branded image layer (the furni frame stays put).
|
||||||
|
// Authored at full scale (64) and scaled with the room zoom.
|
||||||
|
protected getLayerXOffset(scale: number, direction: number, layerId: number): number
|
||||||
|
{
|
||||||
|
const offset = super.getLayerXOffset(scale, direction, layerId);
|
||||||
|
|
||||||
|
return this.isBrandedImageLayer(scale, layerId) ? offset + ((this._offsetX || 0) * (scale / 64)) : offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getLayerYOffset(scale: number, direction: number, layerId: number): number
|
||||||
|
{
|
||||||
|
const offset = super.getLayerYOffset(scale, direction, layerId);
|
||||||
|
|
||||||
|
return this.isBrandedImageLayer(scale, layerId) ? offset + ((this._offsetY || 0) * (scale / 64)) : offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offsetZ is the z-index / depth (overlap order, like CSS z-index).
|
||||||
|
protected getLayerZOffset(scale: number, direction: number, layerId: number): number
|
||||||
|
{
|
||||||
|
const offset = super.getLayerZOffset(scale, direction, layerId);
|
||||||
|
|
||||||
|
return this.isBrandedImageLayer(scale, layerId) ? offset + (this._offsetZ || 0) : offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `scale` zooms the branded image (percent, 100 = 1x). Applied via the
|
||||||
|
// sprite's scale (a real Pixi sprite scale) — NOT by writing width/height,
|
||||||
|
// which are read-only on RoomObjectSprite and would throw every frame.
|
||||||
|
protected updateSprite(scale: number, layerId: number): void
|
||||||
|
{
|
||||||
|
super.updateSprite(scale, layerId);
|
||||||
|
|
||||||
|
const sprite = this.getSprite(layerId);
|
||||||
|
|
||||||
|
if(!sprite) return;
|
||||||
|
|
||||||
|
sprite.scale = this.isBrandedImageLayer(scale, layerId)
|
||||||
|
? Math.max(0.1, (this._imageScale || 100) / 100)
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -76,6 +76,12 @@ export class FurnitureRoomBackgroundVisualization extends FurnitureBrandedImageV
|
|||||||
|
|
||||||
if(this.getLayerTag(scale, direction, layerId) === FurnitureBrandedImageVisualization.BRANDED_IMAGE)
|
if(this.getLayerTag(scale, direction, layerId) === FurnitureBrandedImageVisualization.BRANDED_IMAGE)
|
||||||
{
|
{
|
||||||
|
// The parent (FurnitureBrandedImageVisualization) now ADDS offsetZ to the
|
||||||
|
// branded layer as a z-index (for the MPU/billboard editor). The room
|
||||||
|
// background instead uses offsetZ as an INVERSE depth push — the classic
|
||||||
|
// "play with Z to make the floor/walls go transparent" trick — so cancel
|
||||||
|
// the parent's +offsetZ to restore the original net (base - offsetZ).
|
||||||
|
zOffset += (-(this._offsetZ));
|
||||||
zOffset += FurnitureRoomBackgroundVisualization.BRANDED_IMAGE_LAYER_DEPTH_BIAS;
|
zOffset += FurnitureRoomBackgroundVisualization.BRANDED_IMAGE_LAYER_DEPTH_BIAS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -702,11 +702,13 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas
|
|||||||
if(extendedSprite.texture !== objectSprite.texture) extendedSprite.setTexture(objectSprite.texture);
|
if(extendedSprite.texture !== objectSprite.texture) extendedSprite.setTexture(objectSprite.texture);
|
||||||
|
|
||||||
|
|
||||||
const sx = Math.abs(extendedSprite.scale.x) || 1;
|
// Per-sprite zoom (objectSprite.scale, default 1) combined with flip.
|
||||||
const sy = Math.abs(extendedSprite.scale.y) || 1;
|
// Setting the magnitude directly (instead of reading the previous
|
||||||
|
// scale) avoids compounding across frames.
|
||||||
|
const magnitude = (objectSprite.scale && (objectSprite.scale > 0)) ? objectSprite.scale : 1;
|
||||||
|
|
||||||
extendedSprite.scale.x = objectSprite.flipH ? -sx : sx;
|
extendedSprite.scale.x = objectSprite.flipH ? -magnitude : magnitude;
|
||||||
extendedSprite.scale.y = objectSprite.flipV ? -sy : sy;
|
extendedSprite.scale.y = objectSprite.flipV ? -magnitude : magnitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedSprite.x = Math.round(sprite.x);
|
extendedSprite.x = Math.round(sprite.x);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ConfigJsonError, fetchConfigJson, isMissingResource } from './JsonParser';
|
import { ConfigJsonError, fetchConfigJson, isMissingResource, resolveJsonMode } from './JsonParser';
|
||||||
import { NitroLogger } from './NitroLogger';
|
import { NitroLogger } from './NitroLogger';
|
||||||
|
|
||||||
export const DEFAULT_TIERS = [ 'core', 'custom', 'seasonal' ] as const;
|
export const DEFAULT_TIERS = [ 'core', 'custom', 'seasonal' ] as const;
|
||||||
@@ -45,10 +45,20 @@ const tryFetchManifest = async <T = any>(url: string): Promise<T | null> =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try .json5 first, then .json — both treated as optional. Anything other
|
// Pick the manifest extension from the active JSON mode instead of always
|
||||||
// than 404 on either bubbles up.
|
// probing both — that just doubles the failed requests on startup.
|
||||||
|
// json5 -> only <name>.json5
|
||||||
|
// legacy -> only <name>.json
|
||||||
|
// auto -> try .json5 first, fall back to .json
|
||||||
|
// All treated as optional (a clean 404 -> null); anything else bubbles up.
|
||||||
const tryFetchManifestPair = async <T = any>(baseUrl: string, name: string): Promise<T | null> =>
|
const tryFetchManifestPair = async <T = any>(baseUrl: string, name: string): Promise<T | null> =>
|
||||||
{
|
{
|
||||||
|
const mode = resolveJsonMode();
|
||||||
|
|
||||||
|
if(mode === 'json5') return tryFetchManifest<T>(joinUrl(baseUrl, `${ name }.json5`));
|
||||||
|
|
||||||
|
if(mode === 'legacy') return tryFetchManifest<T>(joinUrl(baseUrl, `${ name }.json`));
|
||||||
|
|
||||||
const json5 = await tryFetchManifest<T>(joinUrl(baseUrl, `${ name }.json5`));
|
const json5 = await tryFetchManifest<T>(joinUrl(baseUrl, `${ name }.json5`));
|
||||||
if(json5 !== null) return json5;
|
if(json5 !== null) return json5;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class ConfigJsonError extends Error
|
|||||||
export const isMissingResource = (err: unknown): boolean =>
|
export const isMissingResource = (err: unknown): boolean =>
|
||||||
err instanceof ConfigJsonError && err.phase === 'fetch' && err.httpStatus === 404;
|
err instanceof ConfigJsonError && err.phase === 'fetch' && err.httpStatus === 404;
|
||||||
|
|
||||||
const resolveJsonMode = (): 'legacy' | 'json5' | 'auto' =>
|
export const resolveJsonMode = (): 'legacy' | 'json5' | 'auto' =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user