feat: create item variants
This commit is contained in:
177
src/core/config/ItemVariants.ts
Normal file
177
src/core/config/ItemVariants.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import type { ItemType } from "../types";
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Variant Stat Modifiers
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export type VariantStatModifiers = Partial<{
|
||||||
|
defense: number;
|
||||||
|
attack: number;
|
||||||
|
speed: number;
|
||||||
|
maxHp: number;
|
||||||
|
critChance: number;
|
||||||
|
critMultiplier: number;
|
||||||
|
accuracy: number;
|
||||||
|
evasion: number;
|
||||||
|
blockChance: number;
|
||||||
|
lifesteal: number;
|
||||||
|
luck: number;
|
||||||
|
// Consumable-specific multiplier
|
||||||
|
effectMultiplier: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export interface ItemVariant {
|
||||||
|
prefix: string;
|
||||||
|
glowColor: number;
|
||||||
|
statModifiers: VariantStatModifiers;
|
||||||
|
applicableTo: ItemType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Armour Variants
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const ARMOUR_VARIANTS = {
|
||||||
|
heavy: {
|
||||||
|
prefix: "Heavy",
|
||||||
|
glowColor: 0x4488ff, // Blue
|
||||||
|
statModifiers: { defense: 2, speed: -1 },
|
||||||
|
applicableTo: ["BodyArmour", "Helmet", "Gloves", "Boots"] as ItemType[],
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
prefix: "Light",
|
||||||
|
glowColor: 0x44ff88, // Green
|
||||||
|
statModifiers: { speed: 1, evasion: 5, defense: -1 },
|
||||||
|
applicableTo: ["BodyArmour", "Helmet", "Gloves", "Boots"] as ItemType[],
|
||||||
|
},
|
||||||
|
reinforced: {
|
||||||
|
prefix: "Reinforced",
|
||||||
|
glowColor: 0xcccccc, // Silver
|
||||||
|
statModifiers: { defense: 1, blockChance: 5 },
|
||||||
|
applicableTo: ["BodyArmour", "Helmet", "Gloves", "Boots"] as ItemType[],
|
||||||
|
},
|
||||||
|
blessed: {
|
||||||
|
prefix: "Blessed",
|
||||||
|
glowColor: 0xffd700, // Gold
|
||||||
|
statModifiers: { maxHp: 5, defense: 1 },
|
||||||
|
applicableTo: ["BodyArmour", "Helmet", "Gloves", "Boots"] as ItemType[],
|
||||||
|
},
|
||||||
|
cursed: {
|
||||||
|
prefix: "Cursed",
|
||||||
|
glowColor: 0x8844ff, // Purple
|
||||||
|
statModifiers: { defense: 3, luck: -10 },
|
||||||
|
applicableTo: ["BodyArmour", "Helmet", "Gloves", "Boots"] as ItemType[],
|
||||||
|
},
|
||||||
|
spiked: {
|
||||||
|
prefix: "Spiked",
|
||||||
|
glowColor: 0xff4444, // Red
|
||||||
|
statModifiers: { attack: 1, defense: 1 },
|
||||||
|
applicableTo: ["BodyArmour", "Helmet", "Gloves", "Boots"] as ItemType[],
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Weapon Variants
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const WEAPON_VARIANTS = {
|
||||||
|
sharp: {
|
||||||
|
prefix: "Sharp",
|
||||||
|
glowColor: 0xffffff, // White
|
||||||
|
statModifiers: { attack: 2, critChance: 5 },
|
||||||
|
applicableTo: ["Weapon"] as ItemType[],
|
||||||
|
},
|
||||||
|
heavy_weapon: {
|
||||||
|
prefix: "Heavy",
|
||||||
|
glowColor: 0x4488ff, // Blue
|
||||||
|
statModifiers: { attack: 3, speed: -1 },
|
||||||
|
applicableTo: ["Weapon"] as ItemType[],
|
||||||
|
},
|
||||||
|
balanced: {
|
||||||
|
prefix: "Balanced",
|
||||||
|
glowColor: 0x44ffff, // Cyan
|
||||||
|
statModifiers: { attack: 1, accuracy: 10 },
|
||||||
|
applicableTo: ["Weapon"] as ItemType[],
|
||||||
|
},
|
||||||
|
venomous: {
|
||||||
|
prefix: "Venomous",
|
||||||
|
glowColor: 0x88ff44, // Toxic green
|
||||||
|
statModifiers: { attack: 1 },
|
||||||
|
applicableTo: ["Weapon"] as ItemType[],
|
||||||
|
},
|
||||||
|
vampiric: {
|
||||||
|
prefix: "Vampiric",
|
||||||
|
glowColor: 0xcc2222, // Crimson
|
||||||
|
statModifiers: { lifesteal: 5 },
|
||||||
|
applicableTo: ["Weapon"] as ItemType[],
|
||||||
|
},
|
||||||
|
brutal: {
|
||||||
|
prefix: "Brutal",
|
||||||
|
glowColor: 0xff8844, // Orange
|
||||||
|
statModifiers: { critMultiplier: 0.5 },
|
||||||
|
applicableTo: ["Weapon"] as ItemType[],
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Consumable Variants
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const CONSUMABLE_VARIANTS = {
|
||||||
|
potent: {
|
||||||
|
prefix: "Potent",
|
||||||
|
glowColor: 0xff6644, // Red-orange
|
||||||
|
statModifiers: { effectMultiplier: 1.5 },
|
||||||
|
applicableTo: ["Consumable"] as ItemType[],
|
||||||
|
},
|
||||||
|
diluted: {
|
||||||
|
prefix: "Diluted",
|
||||||
|
glowColor: 0xaaaaaa, // Pale gray
|
||||||
|
statModifiers: { effectMultiplier: 0.5 },
|
||||||
|
applicableTo: ["Consumable"] as ItemType[],
|
||||||
|
},
|
||||||
|
enchanted: {
|
||||||
|
prefix: "Enchanted",
|
||||||
|
glowColor: 0xff44ff, // Magenta
|
||||||
|
statModifiers: { effectMultiplier: 2 },
|
||||||
|
applicableTo: ["Consumable"] as ItemType[],
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Combined Variant Lookup
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const ALL_VARIANTS = {
|
||||||
|
...ARMOUR_VARIANTS,
|
||||||
|
...WEAPON_VARIANTS,
|
||||||
|
...CONSUMABLE_VARIANTS,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ArmourVariantId = keyof typeof ARMOUR_VARIANTS;
|
||||||
|
export type WeaponVariantId = keyof typeof WEAPON_VARIANTS;
|
||||||
|
export type ConsumableVariantId = keyof typeof CONSUMABLE_VARIANTS;
|
||||||
|
export type ItemVariantId = keyof typeof ALL_VARIANTS;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Helper Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export function getVariant(variantId: ItemVariantId): ItemVariant {
|
||||||
|
return ALL_VARIANTS[variantId];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVariantGlowColor(variantId: ItemVariantId): number {
|
||||||
|
return ALL_VARIANTS[variantId].glowColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVariantApplicable(variantId: ItemVariantId, itemType: ItemType): boolean {
|
||||||
|
const variant = ALL_VARIANTS[variantId];
|
||||||
|
return variant.applicableTo.includes(itemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getApplicableVariants(itemType: ItemType): ItemVariantId[] {
|
||||||
|
return (Object.keys(ALL_VARIANTS) as ItemVariantId[]).filter(
|
||||||
|
(id) => isVariantApplicable(id, itemType)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -94,36 +94,66 @@ export type ItemTemplateId = keyof typeof ALL_TEMPLATES;
|
|||||||
// Factory Functions
|
// Factory Functions
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
export function createConsumable(id: ConsumableId, quantity = 1): ConsumableItem {
|
import {
|
||||||
|
ALL_VARIANTS,
|
||||||
|
type ArmourVariantId,
|
||||||
|
type WeaponVariantId,
|
||||||
|
type ConsumableVariantId
|
||||||
|
} from "./ItemVariants";
|
||||||
|
|
||||||
|
export function createConsumable(
|
||||||
|
id: ConsumableId,
|
||||||
|
quantity = 1,
|
||||||
|
variant?: ConsumableVariantId
|
||||||
|
): ConsumableItem {
|
||||||
const t = CONSUMABLES[id];
|
const t = CONSUMABLES[id];
|
||||||
|
const v = variant ? ALL_VARIANTS[variant] : null;
|
||||||
|
|
||||||
|
// Apply effect multiplier for consumables
|
||||||
|
const effectMult = v?.statModifiers.effectMultiplier ?? 1;
|
||||||
|
const baseHealAmount = "healAmount" in t ? t.healAmount : undefined;
|
||||||
|
const finalHealAmount = baseHealAmount ? Math.floor(baseHealAmount * effectMult) : undefined;
|
||||||
|
|
||||||
|
const name = v ? `${v.prefix} ${t.name}` : t.name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: t.name,
|
name,
|
||||||
type: "Consumable",
|
type: "Consumable",
|
||||||
textureKey: t.textureKey,
|
textureKey: t.textureKey,
|
||||||
spriteIndex: t.spriteIndex,
|
spriteIndex: t.spriteIndex,
|
||||||
stackable: t.stackable ?? false,
|
stackable: t.stackable ?? false,
|
||||||
quantity,
|
quantity,
|
||||||
|
variant,
|
||||||
stats: {
|
stats: {
|
||||||
hp: "healAmount" in t ? t.healAmount : undefined,
|
hp: finalHealAmount,
|
||||||
attack: "attack" in t ? t.attack : undefined,
|
attack: "attack" in t ? t.attack : undefined,
|
||||||
},
|
},
|
||||||
throwable: "throwable" in t ? t.throwable : undefined,
|
throwable: "throwable" in t ? t.throwable : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRangedWeapon(id: RangedWeaponId): RangedWeaponItem {
|
export function createRangedWeapon(
|
||||||
|
id: RangedWeaponId,
|
||||||
|
variant?: WeaponVariantId
|
||||||
|
): RangedWeaponItem {
|
||||||
const t = RANGED_WEAPONS[id];
|
const t = RANGED_WEAPONS[id];
|
||||||
|
const v = variant ? ALL_VARIANTS[variant] : null;
|
||||||
|
|
||||||
|
const name = v ? `${v.prefix} ${t.name}` : t.name;
|
||||||
|
const attackBonus = (v?.statModifiers as { attack?: number })?.attack ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: t.name,
|
name,
|
||||||
type: "Weapon",
|
type: "Weapon",
|
||||||
weaponType: "ranged",
|
weaponType: "ranged",
|
||||||
textureKey: t.textureKey,
|
textureKey: t.textureKey,
|
||||||
spriteIndex: t.spriteIndex,
|
spriteIndex: t.spriteIndex,
|
||||||
currentAmmo: t.magazineSize,
|
currentAmmo: t.magazineSize,
|
||||||
|
variant,
|
||||||
stats: {
|
stats: {
|
||||||
attack: t.attack,
|
attack: t.attack + attackBonus,
|
||||||
range: t.range,
|
range: t.range,
|
||||||
magazineSize: t.magazineSize,
|
magazineSize: t.magazineSize,
|
||||||
ammoType: t.ammoType,
|
ammoType: t.ammoType,
|
||||||
@@ -133,17 +163,26 @@ export function createRangedWeapon(id: RangedWeaponId): RangedWeaponItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMeleeWeapon(id: MeleeWeaponId): MeleeWeaponItem {
|
export function createMeleeWeapon(
|
||||||
|
id: MeleeWeaponId,
|
||||||
|
variant?: WeaponVariantId
|
||||||
|
): MeleeWeaponItem {
|
||||||
const t = MELEE_WEAPONS[id];
|
const t = MELEE_WEAPONS[id];
|
||||||
|
const v = variant ? ALL_VARIANTS[variant] : null;
|
||||||
|
|
||||||
|
const name = v ? `${v.prefix} ${t.name}` : t.name;
|
||||||
|
const attackBonus = (v?.statModifiers as { attack?: number })?.attack ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: t.name,
|
name,
|
||||||
type: "Weapon",
|
type: "Weapon",
|
||||||
weaponType: "melee",
|
weaponType: "melee",
|
||||||
textureKey: t.textureKey,
|
textureKey: t.textureKey,
|
||||||
spriteIndex: t.spriteIndex,
|
spriteIndex: t.spriteIndex,
|
||||||
|
variant,
|
||||||
stats: {
|
stats: {
|
||||||
attack: t.attack,
|
attack: t.attack + attackBonus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -162,19 +201,29 @@ export function createAmmo(id: AmmoId, quantity = 10): AmmoItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createArmour(id: ArmourId): ArmourItem {
|
export function createArmour(
|
||||||
|
id: ArmourId,
|
||||||
|
variant?: ArmourVariantId
|
||||||
|
): ArmourItem {
|
||||||
const t = ARMOUR[id];
|
const t = ARMOUR[id];
|
||||||
|
const v = variant ? ALL_VARIANTS[variant] : null;
|
||||||
|
|
||||||
|
const name = v ? `${v.prefix} ${t.name}` : t.name;
|
||||||
|
const defenseBonus = v?.statModifiers.defense ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: t.name,
|
name,
|
||||||
type: "BodyArmour",
|
type: "BodyArmour",
|
||||||
textureKey: t.textureKey,
|
textureKey: t.textureKey,
|
||||||
spriteIndex: t.spriteIndex,
|
spriteIndex: t.spriteIndex,
|
||||||
|
variant,
|
||||||
stats: {
|
stats: {
|
||||||
defense: t.defense,
|
defense: t.defense + defenseBonus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy export for backward compatibility during migration
|
// Legacy export for backward compatibility during migration
|
||||||
export const ITEMS = ALL_TEMPLATES;
|
export const ITEMS = ALL_TEMPLATES;
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export interface BaseItem {
|
|||||||
spriteIndex: number;
|
spriteIndex: number;
|
||||||
quantity?: number;
|
quantity?: number;
|
||||||
stackable?: boolean;
|
stackable?: boolean;
|
||||||
|
variant?: string; // ItemVariantId - stored as string to avoid circular imports
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeleeWeaponItem extends BaseItem {
|
export interface MeleeWeaponItem extends BaseItem {
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
|||||||
// Add starting items for testing if empty
|
// Add starting items for testing if empty
|
||||||
...(runState.inventory.items.length === 0 ? [
|
...(runState.inventory.items.length === 0 ? [
|
||||||
createConsumable("health_potion", 2),
|
createConsumable("health_potion", 2),
|
||||||
createMeleeWeapon("iron_sword"),
|
createMeleeWeapon("iron_sword", "sharp"), // Sharp sword variant
|
||||||
createConsumable("throwing_dagger", 3),
|
createConsumable("throwing_dagger", 3),
|
||||||
createRangedWeapon("pistol"),
|
createRangedWeapon("pistol"),
|
||||||
createArmour("leather_armor")
|
createArmour("leather_armor", "heavy") // Heavy armour variant
|
||||||
] : [])
|
] : [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ALL_TEMPLATES } from "../core/config/Items";
|
|||||||
import { FovManager } from "./FovManager";
|
import { FovManager } from "./FovManager";
|
||||||
import { MinimapRenderer } from "./MinimapRenderer";
|
import { MinimapRenderer } from "./MinimapRenderer";
|
||||||
import { FxRenderer } from "./FxRenderer";
|
import { FxRenderer } from "./FxRenderer";
|
||||||
|
import { ItemSpriteFactory } from "./ItemSpriteFactory";
|
||||||
|
|
||||||
export class DungeonRenderer {
|
export class DungeonRenderer {
|
||||||
private scene: Phaser.Scene;
|
private scene: Phaser.Scene;
|
||||||
@@ -16,7 +17,7 @@ export class DungeonRenderer {
|
|||||||
private playerSprite?: Phaser.GameObjects.Sprite;
|
private playerSprite?: Phaser.GameObjects.Sprite;
|
||||||
private enemySprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();
|
private enemySprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();
|
||||||
private orbSprites: Map<EntityId, Phaser.GameObjects.Arc> = new Map();
|
private orbSprites: Map<EntityId, Phaser.GameObjects.Arc> = new Map();
|
||||||
private itemSprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();
|
private itemSprites: Map<EntityId, Phaser.GameObjects.Container> = new Map();
|
||||||
|
|
||||||
private fovManager: FovManager;
|
private fovManager: FovManager;
|
||||||
private minimapRenderer: MinimapRenderer;
|
private minimapRenderer: MinimapRenderer;
|
||||||
@@ -228,19 +229,20 @@ export class DungeonRenderer {
|
|||||||
if (!isVis) continue;
|
if (!isVis) continue;
|
||||||
|
|
||||||
activeItemIds.add(a.id);
|
activeItemIds.add(a.id);
|
||||||
let itemSprite = this.itemSprites.get(a.id);
|
let itemContainer = this.itemSprites.get(a.id);
|
||||||
if (!itemSprite) {
|
if (!itemContainer) {
|
||||||
itemSprite = this.scene.add.sprite(0, 0, a.item.textureKey, a.item.spriteIndex);
|
// Use ItemSpriteFactory to create sprite with optional glow
|
||||||
itemSprite.setDepth(40);
|
itemContainer = ItemSpriteFactory.createItemSprite(this.scene, a.item, 0, 0, 1);
|
||||||
this.itemSprites.set(a.id, itemSprite);
|
itemContainer.setDepth(40);
|
||||||
|
this.itemSprites.set(a.id, itemContainer);
|
||||||
}
|
}
|
||||||
const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
|
const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
|
||||||
const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
|
const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
|
||||||
itemSprite.setPosition(tx, ty);
|
itemContainer.setPosition(tx, ty);
|
||||||
itemSprite.setVisible(true);
|
itemContainer.setVisible(true);
|
||||||
|
|
||||||
// bobbing effect?
|
// bobbing effect on the container
|
||||||
itemSprite.y += Math.sin(this.scene.time.now / 300) * 2;
|
itemContainer.y += Math.sin(this.scene.time.now / 300) * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
118
src/rendering/ItemSpriteFactory.ts
Normal file
118
src/rendering/ItemSpriteFactory.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import Phaser from "phaser";
|
||||||
|
import type { Item } from "../core/types";
|
||||||
|
import { ALL_VARIANTS, type ItemVariantId } from "../core/config/ItemVariants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating item sprites with optional variant glow effects.
|
||||||
|
* Centralizes item rendering logic to ensure consistent glow styling across
|
||||||
|
* inventory, quick slots, and world drops.
|
||||||
|
*/
|
||||||
|
export class ItemSpriteFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an item sprite with optional glow effect for variants.
|
||||||
|
* Returns a container with the glow (if applicable) and main sprite.
|
||||||
|
*/
|
||||||
|
static createItemSprite(
|
||||||
|
scene: Phaser.Scene,
|
||||||
|
item: Item,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
scale: number = 1
|
||||||
|
): Phaser.GameObjects.Container {
|
||||||
|
const container = scene.add.container(x, y);
|
||||||
|
|
||||||
|
// Create glow effect if item has a variant
|
||||||
|
if (item.variant) {
|
||||||
|
const glowColor = this.getGlowColor(item.variant as ItemVariantId);
|
||||||
|
if (glowColor !== null) {
|
||||||
|
const glow = this.createGlow(scene, item, scale, glowColor);
|
||||||
|
container.add(glow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create main item sprite
|
||||||
|
const sprite = scene.add.sprite(0, 0, item.textureKey, item.spriteIndex);
|
||||||
|
sprite.setScale(scale);
|
||||||
|
container.add(sprite);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates just a sprite (no container) for simpler use cases like drag icons.
|
||||||
|
* Does not include glow - use createItemSprite for full effect.
|
||||||
|
*/
|
||||||
|
static createSimpleSprite(
|
||||||
|
scene: Phaser.Scene,
|
||||||
|
item: Item,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
scale: number = 1
|
||||||
|
): Phaser.GameObjects.Sprite {
|
||||||
|
const sprite = scene.add.sprite(x, y, item.textureKey, item.spriteIndex);
|
||||||
|
sprite.setScale(scale);
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a soft glow effect behind the item using graphics.
|
||||||
|
* Uses a radial gradient-like effect with multiple circles.
|
||||||
|
*/
|
||||||
|
private static createGlow(
|
||||||
|
scene: Phaser.Scene,
|
||||||
|
_item: Item,
|
||||||
|
scale: number,
|
||||||
|
color: number
|
||||||
|
): Phaser.GameObjects.Graphics {
|
||||||
|
const glow = scene.add.graphics();
|
||||||
|
|
||||||
|
// Base size for the glow (16x16 sprite scaled)
|
||||||
|
const baseSize = 16 * scale;
|
||||||
|
const glowRadius = baseSize * 0.8;
|
||||||
|
|
||||||
|
// Extract RGB from hex color
|
||||||
|
const r = (color >> 16) & 0xff;
|
||||||
|
const g = (color >> 8) & 0xff;
|
||||||
|
const b = color & 0xff;
|
||||||
|
|
||||||
|
// Draw multiple circles with decreasing alpha for soft glow effect
|
||||||
|
const layers = 5;
|
||||||
|
for (let i = layers; i >= 1; i--) {
|
||||||
|
const layerRadius = glowRadius * (i / layers) * 1.2;
|
||||||
|
const layerAlpha = 0.15 * (1 - (i - 1) / layers);
|
||||||
|
|
||||||
|
glow.fillStyle(Phaser.Display.Color.GetColor(r, g, b), layerAlpha);
|
||||||
|
glow.fillCircle(0, 0, layerRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add pulsing animation to the glow
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: glow,
|
||||||
|
alpha: { from: 0.7, to: 1.0 },
|
||||||
|
scaleX: { from: 0.9, to: 1.1 },
|
||||||
|
scaleY: { from: 0.9, to: 1.1 },
|
||||||
|
duration: 800,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut'
|
||||||
|
});
|
||||||
|
|
||||||
|
return glow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the glow color for a variant.
|
||||||
|
*/
|
||||||
|
private static getGlowColor(variantId: ItemVariantId): number | null {
|
||||||
|
const variant = ALL_VARIANTS[variantId];
|
||||||
|
return variant?.glowColor ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an item has a variant with a glow.
|
||||||
|
*/
|
||||||
|
static hasGlow(item: Item): boolean {
|
||||||
|
return !!item.variant && !!ALL_VARIANTS[item.variant as ItemVariantId];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { OverlayComponent } from "./OverlayComponent";
|
import { OverlayComponent } from "./OverlayComponent";
|
||||||
import { type CombatantActor } from "../../core/types";
|
import { type CombatantActor } from "../../core/types";
|
||||||
|
import { ItemSpriteFactory } from "../../rendering/ItemSpriteFactory";
|
||||||
|
|
||||||
export class InventoryOverlay extends OverlayComponent {
|
export class InventoryOverlay extends OverlayComponent {
|
||||||
private equipmentSlots: Map<string, Phaser.GameObjects.Container> = new Map();
|
private equipmentSlots: Map<string, Phaser.GameObjects.Container> = new Map();
|
||||||
@@ -330,13 +331,9 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
|
|
||||||
const slot = this.backpackSlots[index];
|
const slot = this.backpackSlots[index];
|
||||||
|
|
||||||
const texture = item.textureKey;
|
// Use ItemSpriteFactory for glow effect on variants
|
||||||
const frame = item.spriteIndex;
|
const itemContainer = ItemSpriteFactory.createItemSprite(this.scene, item, 0, 0, 2.2);
|
||||||
|
slot.add(itemContainer);
|
||||||
const sprite = this.scene.add.sprite(0, 0, texture, frame);
|
|
||||||
sprite.setScale(2.2); // Scale to fit nicely in 44px slots
|
|
||||||
|
|
||||||
slot.add(sprite);
|
|
||||||
|
|
||||||
// Add Count Label (Bottom-Right)
|
// Add Count Label (Bottom-Right)
|
||||||
let labelText = "";
|
let labelText = "";
|
||||||
@@ -384,9 +381,9 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
const slot = this.equipmentSlots.get(key);
|
const slot = this.equipmentSlots.get(key);
|
||||||
if (!slot) return;
|
if (!slot) return;
|
||||||
|
|
||||||
const sprite = this.scene.add.sprite(0, 0, item.textureKey, item.spriteIndex);
|
// Use ItemSpriteFactory for glow effect on variants
|
||||||
sprite.setScale(2.2);
|
const itemContainer = ItemSpriteFactory.createItemSprite(this.scene, item, 0, 0, 2.2);
|
||||||
slot.add(sprite);
|
slot.add(itemContainer);
|
||||||
|
|
||||||
// Add interactivity
|
// Add interactivity
|
||||||
const size = (key === "bodyArmour") ? 58 : (key === "belt") ? 32 : (key === "boots") ? 46 : (key.startsWith("ring")) ? 38 : 46;
|
const size = (key === "bodyArmour") ? 58 : (key === "belt") ? 32 : (key === "boots") ? 46 : (key.startsWith("ring")) ? 38 : 46;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import type { CombatantActor, Item } from "../../core/types";
|
import type { CombatantActor, Item } from "../../core/types";
|
||||||
|
import { ItemSpriteFactory } from "../../rendering/ItemSpriteFactory";
|
||||||
|
|
||||||
export class QuickSlotComponent {
|
export class QuickSlotComponent {
|
||||||
private scene: Phaser.Scene;
|
private scene: Phaser.Scene;
|
||||||
@@ -220,11 +221,11 @@ export class QuickSlotComponent {
|
|||||||
bgGraphics.strokeRect(0, 0, slotSize, slotSize);
|
bgGraphics.strokeRect(0, 0, slotSize, slotSize);
|
||||||
|
|
||||||
if (foundItem) {
|
if (foundItem) {
|
||||||
const texture = foundItem.textureKey ?? "items";
|
// Use ItemSpriteFactory for glow effect on variants
|
||||||
const sprite = this.scene.add.sprite(slotSize / 2, slotSize / 2, texture, foundItem.spriteIndex);
|
const itemContainer = ItemSpriteFactory.createItemSprite(
|
||||||
// PD items are 16x16, slot is 48x48. Scale up slightly
|
this.scene, foundItem, slotSize / 2, slotSize / 2, 2.5
|
||||||
sprite.setScale(2.5);
|
);
|
||||||
slot.add(sprite);
|
slot.add(itemContainer);
|
||||||
|
|
||||||
// Unified Label (Bottom-Right)
|
// Unified Label (Bottom-Right)
|
||||||
let labelText = "";
|
let labelText = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user