1876 lines
64 KiB
JavaScript
1876 lines
64 KiB
JavaScript
/**
|
|
* CostumeContexts - Themed costume overlays for the avatar system
|
|
* Depends on: AvatarRenderer, AvatarAnimations
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
// ============================================================================
|
|
// UTILITY FUNCTIONS
|
|
// ============================================================================
|
|
|
|
function roundRect(ctx, x, y, w, h, r) {
|
|
if (w < 2 * r) r = w / 2;
|
|
if (h < 2 * r) r = h / 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + r, y);
|
|
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
ctx.arcTo(x, y + h, x, y, r);
|
|
ctx.arcTo(x, y, x + w, y, r);
|
|
ctx.closePath();
|
|
}
|
|
|
|
function darken(color, amount) {
|
|
var hex = color.replace('#', '');
|
|
var r = Math.max(0, Math.floor(parseInt(hex.substr(0, 2), 16) * (1 - amount)));
|
|
var g = Math.max(0, Math.floor(parseInt(hex.substr(2, 2), 16) * (1 - amount)));
|
|
var b = Math.max(0, Math.floor(parseInt(hex.substr(4, 2), 16) * (1 - amount)));
|
|
return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
|
|
}
|
|
|
|
function lighten(color, amount) {
|
|
var hex = color.replace('#', '');
|
|
var r = Math.min(255, Math.floor(parseInt(hex.substr(0, 2), 16) + (255 - parseInt(hex.substr(0, 2), 16)) * amount));
|
|
var g = Math.min(255, Math.floor(parseInt(hex.substr(2, 2), 16) + (255 - parseInt(hex.substr(2, 2), 16)) * amount));
|
|
var b = Math.min(255, Math.floor(parseInt(hex.substr(4, 2), 16) + (255 - parseInt(hex.substr(4, 2), 16)) * amount));
|
|
return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
|
|
}
|
|
|
|
function getArmTransform(transforms, arm) {
|
|
if (!transforms || !transforms[arm]) return { rotation: 0, x: 0, y: 0 };
|
|
return transforms[arm];
|
|
}
|
|
|
|
// ============================================================================
|
|
// COSTUME: KNIGHT ARMOR
|
|
// ============================================================================
|
|
|
|
var knightArmor = {
|
|
id: 'knight-armor',
|
|
name: 'Knight Armor',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: false,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['rpg', 'adventure', 'action'],
|
|
description: 'Medieval knight in shining armor',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'sword', anchor: 'rightArm', offset: { x: 5, y: 20 } },
|
|
{ id: 'shield', anchor: 'leftArm', offset: { x: -10, y: 0 } }
|
|
],
|
|
contextEffects: {
|
|
attack: { type: 'slash', color: '#c0c0c0' },
|
|
hurt: { type: 'sparks', color: '#ffa500' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var metalColor = '#a8a9ad';
|
|
var metalDark = '#7d7f83';
|
|
var metalLight = '#d4d5d9';
|
|
|
|
// Cape (behind everything)
|
|
ctx.fillStyle = '#8b0000';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 68 * scale);
|
|
ctx.quadraticCurveTo(cx - 25 * scale, y + 100 * scale, cx - 20 * scale + Math.sin(time * 2) * 3 * scale, y + 145 * scale);
|
|
ctx.lineTo(cx + 20 * scale + Math.sin(time * 2 + 1) * 3 * scale, y + 145 * scale);
|
|
ctx.quadraticCurveTo(cx + 25 * scale, y + 100 * scale, cx + 18 * scale, y + 68 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Cape trim
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(cx - 20 * scale, y + 140 * scale, 40 * scale, 5 * scale);
|
|
|
|
// Body armor (chestplate)
|
|
ctx.fillStyle = metalColor;
|
|
roundRect(ctx, cx - 22 * scale, y + 68 * scale, 44 * scale, 50 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Chestplate details
|
|
ctx.strokeStyle = metalDark;
|
|
ctx.lineWidth = 2 * scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx, y + 70 * scale);
|
|
ctx.lineTo(cx, y + 115 * scale);
|
|
ctx.stroke();
|
|
|
|
// Armor ridges
|
|
ctx.strokeStyle = metalLight;
|
|
ctx.lineWidth = 1 * scale;
|
|
for (var i = 0; i < 3; i++) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 78 * scale + i * 12 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 78 * scale + i * 12 * scale);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Leg armor (greaves)
|
|
ctx.fillStyle = metalColor;
|
|
roundRect(ctx, cx - 16 * scale, y + 120 * scale, 12 * scale, 35 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 120 * scale, 12 * scale, 35 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Knee guards
|
|
ctx.fillStyle = metalLight;
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 10 * scale, y + 125 * scale, 5 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 10 * scale, y + 125 * scale, 5 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Armored boots
|
|
ctx.fillStyle = metalDark;
|
|
roundRect(ctx, cx - 18 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
|
|
ctx.fill();
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var metalColor = '#a8a9ad';
|
|
var metalDark = '#7d7f83';
|
|
var metalLight = '#d4d5d9';
|
|
|
|
// Shoulder pauldrons
|
|
ctx.fillStyle = metalColor;
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx - 26 * scale, y + 70 * scale, 12 * scale, 8 * scale, -0.3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx + 26 * scale, y + 70 * scale, 12 * scale, 8 * scale, 0.3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Pauldron spikes
|
|
ctx.fillStyle = metalLight;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 32 * scale, y + 65 * scale);
|
|
ctx.lineTo(cx - 35 * scale, y + 58 * scale);
|
|
ctx.lineTo(cx - 28 * scale, y + 65 * scale);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 32 * scale, y + 65 * scale);
|
|
ctx.lineTo(cx + 35 * scale, y + 58 * scale);
|
|
ctx.lineTo(cx + 28 * scale, y + 65 * scale);
|
|
ctx.fill();
|
|
|
|
// Helmet
|
|
ctx.fillStyle = metalColor;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y + 32 * scale, 24 * scale, Math.PI, 0, false);
|
|
ctx.fill();
|
|
|
|
// Helmet face plate
|
|
ctx.fillStyle = metalDark;
|
|
roundRect(ctx, cx - 18 * scale, y + 20 * scale, 36 * scale, 28 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Visor slits (eyes peek through)
|
|
ctx.fillStyle = '#1a1a1a';
|
|
ctx.fillRect(cx - 14 * scale, y + 28 * scale, 10 * scale, 3 * scale);
|
|
ctx.fillRect(cx + 4 * scale, y + 28 * scale, 10 * scale, 3 * scale);
|
|
|
|
// Helmet crest
|
|
ctx.fillStyle = '#8b0000';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 3 * scale, y + 8 * scale);
|
|
ctx.lineTo(cx, y - 5 * scale);
|
|
ctx.lineTo(cx + 3 * scale, y + 8 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Sword (right hand)
|
|
var rightArm = getArmTransform(transforms, 'rightArm');
|
|
ctx.save();
|
|
ctx.translate(cx + 26 * scale, y + 115 * scale);
|
|
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Sword blade
|
|
ctx.fillStyle = metalLight;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(-2 * scale, -35 * scale);
|
|
ctx.lineTo(0, -45 * scale);
|
|
ctx.lineTo(2 * scale, -35 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Sword guard
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(-6 * scale, -2 * scale, 12 * scale, 4 * scale);
|
|
|
|
// Sword handle
|
|
ctx.fillStyle = '#4a3728';
|
|
ctx.fillRect(-2 * scale, 2 * scale, 4 * scale, 10 * scale);
|
|
|
|
ctx.restore();
|
|
|
|
// Shield (left hand)
|
|
var leftArm = getArmTransform(transforms, 'leftArm');
|
|
ctx.save();
|
|
ctx.translate(cx - 30 * scale, y + 95 * scale);
|
|
ctx.rotate((leftArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Shield body
|
|
ctx.fillStyle = metalColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, -15 * scale);
|
|
ctx.lineTo(12 * scale, -10 * scale);
|
|
ctx.lineTo(12 * scale, 10 * scale);
|
|
ctx.lineTo(0, 20 * scale);
|
|
ctx.lineTo(-12 * scale, 10 * scale);
|
|
ctx.lineTo(-12 * scale, -10 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Shield emblem
|
|
ctx.fillStyle = '#8b0000';
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 6 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Shield cross
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(-1 * scale, -8 * scale, 2 * scale, 16 * scale);
|
|
ctx.fillRect(-6 * scale, -1 * scale, 12 * scale, 2 * scale);
|
|
|
|
ctx.restore();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: SPACE SUIT
|
|
// ============================================================================
|
|
|
|
var spaceSuit = {
|
|
id: 'space-suit',
|
|
name: 'Space Suit',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['action', 'adventure', 'physics'],
|
|
description: 'Astronaut ready for space exploration',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [],
|
|
contextEffects: {
|
|
hurt: { type: 'sparks', color: '#00ffff' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var suitColor = '#f0f0f0';
|
|
var accentColor = '#4a90d9';
|
|
|
|
// Oxygen pack (on back)
|
|
ctx.fillStyle = '#e0e0e0';
|
|
roundRect(ctx, cx - 12 * scale, y + 65 * scale, 24 * scale, 35 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Pack details
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 8 * scale, y + 70 * scale, 16 * scale, 4 * scale);
|
|
ctx.fillRect(cx - 8 * scale, y + 80 * scale, 16 * scale, 4 * scale);
|
|
|
|
// Pack lights
|
|
var blink = Math.sin(time * 3) > 0 ? 1 : 0.3;
|
|
ctx.fillStyle = 'rgba(0, 255, 100, ' + blink + ')';
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 4 * scale, y + 92 * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.fillStyle = 'rgba(255, 100, 0, ' + (1 - blink) + ')';
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 4 * scale, y + 92 * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Suit body
|
|
ctx.fillStyle = suitColor;
|
|
roundRect(ctx, cx - 24 * scale, y + 64 * scale, 48 * scale, 55 * scale, 6 * scale);
|
|
ctx.fill();
|
|
|
|
// Blue accents on torso
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 24 * scale, y + 64 * scale, 8 * scale, 55 * scale);
|
|
ctx.fillRect(cx + 16 * scale, y + 64 * scale, 8 * scale, 55 * scale);
|
|
|
|
// Suit legs
|
|
ctx.fillStyle = suitColor;
|
|
roundRect(ctx, cx - 18 * scale, y + 118 * scale, 14 * scale, 38 * scale, 4 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 118 * scale, 14 * scale, 38 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Blue leg stripes
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 18 * scale, y + 130 * scale, 14 * scale, 4 * scale);
|
|
ctx.fillRect(cx + 4 * scale, y + 130 * scale, 14 * scale, 4 * scale);
|
|
|
|
// Boots with magnetic plates
|
|
ctx.fillStyle = '#555';
|
|
roundRect(ctx, cx - 20 * scale, y + 152 * scale, 16 * scale, 8 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 152 * scale, 16 * scale, 8 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Magnetic plate glow
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 18 * scale, y + 158 * scale, 12 * scale, 2 * scale);
|
|
ctx.fillRect(cx + 6 * scale, y + 158 * scale, 12 * scale, 2 * scale);
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var suitColor = '#f0f0f0';
|
|
var accentColor = '#4a90d9';
|
|
|
|
// Gloves
|
|
ctx.fillStyle = suitColor;
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Glove accents
|
|
ctx.fillStyle = accentColor;
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 26 * scale, y + 115 * scale, 3 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 26 * scale, y + 115 * scale, 3 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Helmet bubble
|
|
ctx.fillStyle = 'rgba(200, 230, 255, 0.3)';
|
|
ctx.strokeStyle = '#ccc';
|
|
ctx.lineWidth = 3 * scale;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y + 32 * scale, 28 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
|
|
// Helmet reflection
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx - 8 * scale, y + 22 * scale, 8 * scale, 12 * scale, -0.3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Helmet ring
|
|
ctx.fillStyle = '#ddd';
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx, y + 55 * scale, 22 * scale, 6 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Helmet accent
|
|
ctx.fillStyle = accentColor;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y + 5 * scale, 4 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Antenna
|
|
ctx.strokeStyle = '#888';
|
|
ctx.lineWidth = 2 * scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 20 * scale, y + 15 * scale);
|
|
ctx.lineTo(cx + 28 * scale, y - 5 * scale);
|
|
ctx.stroke();
|
|
|
|
ctx.fillStyle = '#ff3333';
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 28 * scale, y - 5 * scale, 3 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: NINJA OUTFIT
|
|
// ============================================================================
|
|
|
|
var ninjaOutfit = {
|
|
id: 'ninja-outfit',
|
|
name: 'Ninja Outfit',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: false,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['action', 'adventure', 'rpg'],
|
|
description: 'Stealthy ninja warrior',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'katana', anchor: 'back', offset: { x: 0, y: -10 } }
|
|
],
|
|
contextEffects: {
|
|
attack: { type: 'slash', color: '#333' },
|
|
hurt: { type: 'smoke', color: '#555' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var ninjaBlack = '#1a1a1a';
|
|
var ninjaGray = '#333';
|
|
|
|
// Katana on back
|
|
ctx.save();
|
|
ctx.translate(cx + 15 * scale, y + 50 * scale);
|
|
ctx.rotate(-0.4);
|
|
|
|
// Scabbard
|
|
ctx.fillStyle = '#2c1810';
|
|
roundRect(ctx, -3 * scale, 0, 6 * scale, 50 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Handle wrap
|
|
ctx.fillStyle = '#8b0000';
|
|
for (var i = 0; i < 5; i++) {
|
|
ctx.fillRect(-3 * scale, -10 * scale + i * 4 * scale, 6 * scale, 2 * scale);
|
|
}
|
|
|
|
// Guard
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 0, 5 * scale, 2 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
ctx.restore();
|
|
|
|
// Body suit
|
|
ctx.fillStyle = ninjaBlack;
|
|
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 52 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Belt with throwing stars
|
|
ctx.fillStyle = ninjaGray;
|
|
ctx.fillRect(cx - 22 * scale, y + 100 * scale, 44 * scale, 8 * scale);
|
|
|
|
// Throwing stars on belt
|
|
ctx.fillStyle = '#888';
|
|
for (var s = 0; s < 3; s++) {
|
|
var sx = cx - 12 * scale + s * 12 * scale;
|
|
var sy = y + 104 * scale;
|
|
ctx.beginPath();
|
|
for (var p = 0; p < 4; p++) {
|
|
var angle = p * Math.PI / 2;
|
|
var px = sx + Math.cos(angle) * 3 * scale;
|
|
var py = sy + Math.sin(angle) * 3 * scale;
|
|
if (p === 0) ctx.moveTo(px, py);
|
|
else ctx.lineTo(px, py);
|
|
}
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
|
|
// Legs
|
|
ctx.fillStyle = ninjaBlack;
|
|
roundRect(ctx, cx - 16 * scale, y + 116 * scale, 12 * scale, 36 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 116 * scale, 12 * scale, 36 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Leg wraps
|
|
ctx.strokeStyle = ninjaGray;
|
|
ctx.lineWidth = 2 * scale;
|
|
for (var w = 0; w < 4; w++) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 16 * scale, y + 125 * scale + w * 8 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 128 * scale + w * 8 * scale);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 4 * scale, y + 125 * scale + w * 8 * scale);
|
|
ctx.lineTo(cx + 16 * scale, y + 128 * scale + w * 8 * scale);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Tabi boots
|
|
ctx.fillStyle = ninjaBlack;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 152 * scale);
|
|
ctx.lineTo(cx - 18 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 152 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 4 * scale, y + 152 * scale);
|
|
ctx.lineTo(cx + 4 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx + 12 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 152 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var ninjaBlack = '#1a1a1a';
|
|
|
|
// Arm wraps
|
|
ctx.fillStyle = ninjaBlack;
|
|
roundRect(ctx, cx - 32 * scale, y + 70 * scale, 10 * scale, 45 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 22 * scale, y + 70 * scale, 10 * scale, 45 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Face mask
|
|
ctx.fillStyle = ninjaBlack;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 22 * scale, y + 35 * scale);
|
|
ctx.lineTo(cx - 22 * scale, y + 55 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 60 * scale, cx + 22 * scale, y + 55 * scale);
|
|
ctx.lineTo(cx + 22 * scale, y + 35 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 38 * scale, cx - 22 * scale, y + 35 * scale);
|
|
ctx.fill();
|
|
|
|
// Eye slit
|
|
ctx.fillStyle = '#000';
|
|
ctx.fillRect(cx - 18 * scale, y + 28 * scale, 36 * scale, 6 * scale);
|
|
|
|
// Headband
|
|
ctx.fillStyle = '#8b0000';
|
|
ctx.fillRect(cx - 24 * scale, y + 15 * scale, 48 * scale, 6 * scale);
|
|
|
|
// Headband tail
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 24 * scale, y + 15 * scale);
|
|
ctx.quadraticCurveTo(cx + 35 * scale + Math.sin(time * 3) * 3 * scale, y + 25 * scale, cx + 40 * scale + Math.sin(time * 3) * 5 * scale, y + 35 * scale);
|
|
ctx.lineTo(cx + 36 * scale + Math.sin(time * 3) * 5 * scale, y + 38 * scale);
|
|
ctx.quadraticCurveTo(cx + 32 * scale + Math.sin(time * 3) * 3 * scale, y + 28 * scale, cx + 24 * scale, y + 21 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: WIZARD ROBES
|
|
// ============================================================================
|
|
|
|
var wizardRobes = {
|
|
id: 'wizard-robes',
|
|
name: 'Wizard Robes',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['rpg', 'puzzle', 'adventure'],
|
|
description: 'Mystical wizard with flowing robes',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'staff', anchor: 'rightArm', offset: { x: 5, y: 0 } }
|
|
],
|
|
contextEffects: {
|
|
attack: { type: 'magic', color: '#9b59b6' },
|
|
celebrate: { type: 'sparkles', color: '#ffd700' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var robeColor = '#2c3e50';
|
|
var robeLight = '#3d566e';
|
|
var accent = '#9b59b6';
|
|
|
|
// Main robe body
|
|
ctx.fillStyle = robeColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 22 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx - 30 * scale + Math.sin(time * 1.5) * 2 * scale, y + 158 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 162 * scale, cx + 30 * scale + Math.sin(time * 1.5 + 1) * 2 * scale, y + 158 * scale);
|
|
ctx.lineTo(cx + 22 * scale, y + 64 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Robe inner fold
|
|
ctx.fillStyle = robeLight;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 5 * scale, y + 70 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx + 8 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx + 5 * scale, y + 70 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Belt
|
|
ctx.fillStyle = '#8b4513';
|
|
ctx.fillRect(cx - 22 * scale, y + 100 * scale, 44 * scale, 8 * scale);
|
|
|
|
// Belt buckle
|
|
ctx.fillStyle = '#ffd700';
|
|
roundRect(ctx, cx - 6 * scale, y + 99 * scale, 12 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Belt pouches
|
|
ctx.fillStyle = '#6b3e26';
|
|
roundRect(ctx, cx - 20 * scale, y + 105 * scale, 8 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 12 * scale, y + 105 * scale, 8 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Mystical runes on robe
|
|
ctx.strokeStyle = accent;
|
|
ctx.lineWidth = 1 * scale;
|
|
ctx.globalAlpha = 0.5 + Math.sin(time * 2) * 0.3;
|
|
|
|
// Left rune
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 15 * scale, y + 130 * scale, 5 * scale, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 15 * scale, y + 125 * scale);
|
|
ctx.lineTo(cx - 15 * scale, y + 135 * scale);
|
|
ctx.moveTo(cx - 20 * scale, y + 130 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 130 * scale);
|
|
ctx.stroke();
|
|
|
|
// Right rune
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 15 * scale, y + 130 * scale, 5 * scale, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 12 * scale, y + 127 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 133 * scale);
|
|
ctx.moveTo(cx + 18 * scale, y + 127 * scale);
|
|
ctx.lineTo(cx + 12 * scale, y + 133 * scale);
|
|
ctx.stroke();
|
|
|
|
ctx.globalAlpha = 1;
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var robeColor = '#2c3e50';
|
|
var accent = '#9b59b6';
|
|
|
|
// Sleeves
|
|
ctx.fillStyle = robeColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 22 * scale, y + 68 * scale);
|
|
ctx.quadraticCurveTo(cx - 35 * scale, y + 90 * scale, cx - 32 * scale, y + 118 * scale);
|
|
ctx.lineTo(cx - 25 * scale, y + 115 * scale);
|
|
ctx.quadraticCurveTo(cx - 28 * scale, y + 90 * scale, cx - 18 * scale, y + 72 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 22 * scale, y + 68 * scale);
|
|
ctx.quadraticCurveTo(cx + 35 * scale, y + 90 * scale, cx + 32 * scale, y + 118 * scale);
|
|
ctx.lineTo(cx + 25 * scale, y + 115 * scale);
|
|
ctx.quadraticCurveTo(cx + 28 * scale, y + 90 * scale, cx + 18 * scale, y + 72 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Staff (right hand)
|
|
var rightArm = getArmTransform(transforms, 'rightArm');
|
|
ctx.save();
|
|
ctx.translate(cx + 28 * scale, y + 90 * scale);
|
|
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Staff shaft
|
|
ctx.fillStyle = '#5d4037';
|
|
roundRect(ctx, -3 * scale, -50 * scale, 6 * scale, 80 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Staff orb holder
|
|
ctx.fillStyle = '#3d2817';
|
|
ctx.beginPath();
|
|
ctx.moveTo(-6 * scale, -50 * scale);
|
|
ctx.quadraticCurveTo(-8 * scale, -58 * scale, 0, -62 * scale);
|
|
ctx.quadraticCurveTo(8 * scale, -58 * scale, 6 * scale, -50 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Staff orb
|
|
var orbGlow = ctx.createRadialGradient(0, -68 * scale, 0, 0, -68 * scale, 10 * scale);
|
|
orbGlow.addColorStop(0, '#e0b0ff');
|
|
orbGlow.addColorStop(0.5, accent);
|
|
orbGlow.addColorStop(1, '#4a235a');
|
|
ctx.fillStyle = orbGlow;
|
|
ctx.beginPath();
|
|
ctx.arc(0, -68 * scale, 8 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Orb glow effect
|
|
ctx.globalAlpha = 0.3 + Math.sin(time * 3) * 0.2;
|
|
ctx.fillStyle = accent;
|
|
ctx.beginPath();
|
|
ctx.arc(0, -68 * scale, 12 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.globalAlpha = 1;
|
|
|
|
ctx.restore();
|
|
|
|
// Hood/collar
|
|
ctx.fillStyle = robeColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 24 * scale, y + 55 * scale);
|
|
ctx.quadraticCurveTo(cx - 28 * scale, y + 45 * scale, cx - 20 * scale, y + 40 * scale);
|
|
ctx.lineTo(cx + 20 * scale, y + 40 * scale);
|
|
ctx.quadraticCurveTo(cx + 28 * scale, y + 45 * scale, cx + 24 * scale, y + 55 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 50 * scale, cx - 24 * scale, y + 55 * scale);
|
|
ctx.fill();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: SUPERHERO CAPE
|
|
// ============================================================================
|
|
|
|
var superheroCape = {
|
|
id: 'superhero-cape',
|
|
name: 'Superhero Cape',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['action', 'adventure'],
|
|
description: 'Classic superhero with billowing cape',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [],
|
|
contextEffects: {
|
|
attack: { type: 'impact', color: '#ffff00' },
|
|
celebrate: { type: 'sparkles', color: '#ffd700' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var suitColor = avatar && avatar.base && avatar.base.clothing && avatar.base.clothing.color ? avatar.base.clothing.color : '#c0392b';
|
|
var capeColor = darken(suitColor, 0.3);
|
|
|
|
// Cape (billowing behind)
|
|
ctx.fillStyle = capeColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 20 * scale, y + 62 * scale);
|
|
var wave1 = Math.sin(time * 2) * 5 * scale;
|
|
var wave2 = Math.sin(time * 2 + 1) * 5 * scale;
|
|
var wave3 = Math.sin(time * 2 + 2) * 5 * scale;
|
|
ctx.quadraticCurveTo(cx - 30 * scale + wave1, y + 100 * scale, cx - 35 * scale + wave2, y + 155 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 160 * scale + wave3, cx + 35 * scale + wave2, y + 155 * scale);
|
|
ctx.quadraticCurveTo(cx + 30 * scale + wave1, y + 100 * scale, cx + 20 * scale, y + 62 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Cape inner lining
|
|
ctx.fillStyle = lighten(capeColor, 0.2);
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 15 * scale, y + 65 * scale);
|
|
ctx.quadraticCurveTo(cx - 22 * scale + wave1 * 0.5, y + 100 * scale, cx - 25 * scale + wave2 * 0.5, y + 145 * scale);
|
|
ctx.lineTo(cx + 25 * scale + wave2 * 0.5, y + 145 * scale);
|
|
ctx.quadraticCurveTo(cx + 22 * scale + wave1 * 0.5, y + 100 * scale, cx + 15 * scale, y + 65 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Bodysuit
|
|
ctx.fillStyle = suitColor;
|
|
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 52 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Belt
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(cx - 22 * scale, y + 105 * scale, 44 * scale, 6 * scale);
|
|
|
|
// Belt buckle
|
|
ctx.fillStyle = '#fff';
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y + 108 * scale, 5 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Trunks
|
|
ctx.fillStyle = darken(suitColor, 0.2);
|
|
roundRect(ctx, cx - 18 * scale, y + 108 * scale, 36 * scale, 12 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Legs (suit continues)
|
|
ctx.fillStyle = suitColor;
|
|
roundRect(ctx, cx - 16 * scale, y + 118 * scale, 12 * scale, 36 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 118 * scale, 12 * scale, 36 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Boots
|
|
ctx.fillStyle = darken(suitColor, 0.4);
|
|
roundRect(ctx, cx - 18 * scale, y + 145 * scale, 14 * scale, 15 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 145 * scale, 14 * scale, 15 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Boot tops
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(cx - 18 * scale, y + 145 * scale, 14 * scale, 3 * scale);
|
|
ctx.fillRect(cx + 4 * scale, y + 145 * scale, 14 * scale, 3 * scale);
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var suitColor = avatar && avatar.base && avatar.base.clothing && avatar.base.clothing.color ? avatar.base.clothing.color : '#c0392b';
|
|
|
|
// Arms
|
|
ctx.fillStyle = suitColor;
|
|
roundRect(ctx, cx - 32 * scale, y + 68 * scale, 12 * scale, 45 * scale, 4 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 20 * scale, y + 68 * scale, 12 * scale, 45 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Gloves
|
|
ctx.fillStyle = darken(suitColor, 0.4);
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Glove cuffs
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(cx - 32 * scale, y + 105 * scale, 12 * scale, 4 * scale);
|
|
ctx.fillRect(cx + 20 * scale, y + 105 * scale, 12 * scale, 4 * scale);
|
|
|
|
// Chest emblem
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx, y + 70 * scale);
|
|
ctx.lineTo(cx - 12 * scale, y + 78 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 95 * scale);
|
|
ctx.lineTo(cx, y + 88 * scale);
|
|
ctx.lineTo(cx + 8 * scale, y + 95 * scale);
|
|
ctx.lineTo(cx + 12 * scale, y + 78 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Mask
|
|
ctx.fillStyle = darken(suitColor, 0.3);
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 22 * scale, y + 25 * scale);
|
|
ctx.quadraticCurveTo(cx - 25 * scale, y + 32 * scale, cx - 18 * scale, y + 38 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 32 * scale);
|
|
ctx.lineTo(cx, y + 35 * scale);
|
|
ctx.lineTo(cx + 8 * scale, y + 32 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 38 * scale);
|
|
ctx.quadraticCurveTo(cx + 25 * scale, y + 32 * scale, cx + 22 * scale, y + 25 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 22 * scale, cx - 22 * scale, y + 25 * scale);
|
|
ctx.fill();
|
|
|
|
// Eye holes
|
|
ctx.fillStyle = '#fff';
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx - 12 * scale, y + 30 * scale, 6 * scale, 4 * scale, -0.2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx + 12 * scale, y + 30 * scale, 6 * scale, 4 * scale, 0.2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: ATHLETE UNIFORM
|
|
// ============================================================================
|
|
|
|
var athleteUniform = {
|
|
id: 'athlete-uniform',
|
|
name: 'Athlete Uniform',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['sports', 'action', 'racing'],
|
|
description: 'Athletic sports uniform',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'ball', anchor: 'rightArm', offset: { x: 10, y: 5 } }
|
|
],
|
|
contextEffects: {
|
|
celebrate: { type: 'confetti', color: '#ffd700' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var jerseyColor = '#e74c3c';
|
|
var accentColor = '#fff';
|
|
|
|
// Jersey
|
|
ctx.fillStyle = jerseyColor;
|
|
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 45 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Jersey stripes
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 22 * scale, y + 75 * scale, 44 * scale, 4 * scale);
|
|
ctx.fillRect(cx - 22 * scale, y + 95 * scale, 44 * scale, 4 * scale);
|
|
|
|
// Number on back
|
|
ctx.fillStyle = accentColor;
|
|
ctx.font = 'bold ' + (16 * scale) + 'px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('23', cx, y + 92 * scale);
|
|
|
|
// Shorts
|
|
ctx.fillStyle = '#2c3e50';
|
|
roundRect(ctx, cx - 18 * scale, y + 108 * scale, 36 * scale, 22 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Shorts stripe
|
|
ctx.fillStyle = jerseyColor;
|
|
ctx.fillRect(cx - 18 * scale, y + 108 * scale, 4 * scale, 22 * scale);
|
|
ctx.fillRect(cx + 14 * scale, y + 108 * scale, 4 * scale, 22 * scale);
|
|
|
|
// Legs
|
|
ctx.fillStyle = avatar && avatar.base ? avatar.base.skinTone || '#FFD5B8' : '#FFD5B8';
|
|
roundRect(ctx, cx - 14 * scale, y + 128 * scale, 10 * scale, 25 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 128 * scale, 10 * scale, 25 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Socks
|
|
ctx.fillStyle = accentColor;
|
|
roundRect(ctx, cx - 16 * scale, y + 140 * scale, 12 * scale, 15 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 140 * scale, 12 * scale, 15 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Sock stripes
|
|
ctx.fillStyle = jerseyColor;
|
|
ctx.fillRect(cx - 16 * scale, y + 145 * scale, 12 * scale, 2 * scale);
|
|
ctx.fillRect(cx + 4 * scale, y + 145 * scale, 12 * scale, 2 * scale);
|
|
|
|
// Athletic shoes
|
|
ctx.fillStyle = '#1a1a1a';
|
|
roundRect(ctx, cx - 18 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Shoe accents
|
|
ctx.fillStyle = jerseyColor;
|
|
ctx.fillRect(cx - 16 * scale, y + 154 * scale, 10 * scale, 2 * scale);
|
|
ctx.fillRect(cx + 6 * scale, y + 154 * scale, 10 * scale, 2 * scale);
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var jerseyColor = '#e74c3c';
|
|
var accentColor = '#fff';
|
|
|
|
// Sleeves
|
|
ctx.fillStyle = jerseyColor;
|
|
roundRect(ctx, cx - 30 * scale, y + 66 * scale, 12 * scale, 20 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 18 * scale, y + 66 * scale, 12 * scale, 20 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Sleeve stripes
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 30 * scale, y + 78 * scale, 12 * scale, 3 * scale);
|
|
ctx.fillRect(cx + 18 * scale, y + 78 * scale, 12 * scale, 3 * scale);
|
|
|
|
// Headband
|
|
ctx.fillStyle = jerseyColor;
|
|
ctx.fillRect(cx - 22 * scale, y + 12 * scale, 44 * scale, 6 * scale);
|
|
|
|
// Sweatbands on wrists
|
|
ctx.fillStyle = accentColor;
|
|
ctx.fillRect(cx - 30 * scale, y + 100 * scale, 10 * scale, 6 * scale);
|
|
ctx.fillRect(cx + 20 * scale, y + 100 * scale, 10 * scale, 6 * scale);
|
|
|
|
// Number on front (smaller)
|
|
ctx.fillStyle = accentColor;
|
|
ctx.font = 'bold ' + (12 * scale) + 'px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('23', cx, y + 82 * scale);
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: PIRATE OUTFIT
|
|
// ============================================================================
|
|
|
|
var pirateOutfit = {
|
|
id: 'pirate-outfit',
|
|
name: 'Pirate Outfit',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['adventure', 'action', 'rpg'],
|
|
description: 'Swashbuckling pirate captain',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'cutlass', anchor: 'rightArm', offset: { x: 5, y: 15 } }
|
|
],
|
|
contextEffects: {
|
|
attack: { type: 'slash', color: '#c0c0c0' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var coatColor = '#8b0000';
|
|
var coatDark = '#5c0000';
|
|
|
|
// Long coat (back)
|
|
ctx.fillStyle = coatColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 24 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx - 28 * scale + Math.sin(time * 1.5) * 2 * scale, y + 155 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 158 * scale, cx + 28 * scale + Math.sin(time * 1.5 + 1) * 2 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx + 24 * scale, y + 64 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Coat back detail
|
|
ctx.fillStyle = coatDark;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 5 * scale, y + 90 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 150 * scale);
|
|
ctx.lineTo(cx + 8 * scale, y + 150 * scale);
|
|
ctx.lineTo(cx + 5 * scale, y + 90 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// White shirt
|
|
ctx.fillStyle = '#f5f5dc';
|
|
roundRect(ctx, cx - 18 * scale, y + 64 * scale, 36 * scale, 40 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Shirt ruffles
|
|
ctx.strokeStyle = '#ddd';
|
|
ctx.lineWidth = 1 * scale;
|
|
for (var r = 0; r < 4; r++) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 8 * scale, y + 68 * scale + r * 8 * scale);
|
|
ctx.quadraticCurveTo(cx - 4 * scale, y + 72 * scale + r * 8 * scale, cx, y + 68 * scale + r * 8 * scale);
|
|
ctx.quadraticCurveTo(cx + 4 * scale, y + 72 * scale + r * 8 * scale, cx + 8 * scale, y + 68 * scale + r * 8 * scale);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Belt with buckle
|
|
ctx.fillStyle = '#2c1810';
|
|
ctx.fillRect(cx - 22 * scale, y + 102 * scale, 44 * scale, 8 * scale);
|
|
|
|
ctx.fillStyle = '#ffd700';
|
|
roundRect(ctx, cx - 8 * scale, y + 100 * scale, 16 * scale, 12 * scale, 2 * scale);
|
|
ctx.fill();
|
|
ctx.fillStyle = '#2c1810';
|
|
roundRect(ctx, cx - 4 * scale, y + 103 * scale, 8 * scale, 6 * scale, 1 * scale);
|
|
ctx.fill();
|
|
|
|
// Pants
|
|
ctx.fillStyle = '#2c3e50';
|
|
roundRect(ctx, cx - 16 * scale, y + 108 * scale, 32 * scale, 45 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Boots
|
|
ctx.fillStyle = '#3d2314';
|
|
roundRect(ctx, cx - 18 * scale, y + 140 * scale, 14 * scale, 20 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 140 * scale, 14 * scale, 20 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Boot tops (folded)
|
|
ctx.fillStyle = '#5d4037';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 145 * scale);
|
|
ctx.lineTo(cx - 20 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 145 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 4 * scale, y + 145 * scale);
|
|
ctx.lineTo(cx + 4 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx + 20 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 145 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var coatColor = '#8b0000';
|
|
|
|
// Coat front panels
|
|
ctx.fillStyle = coatColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 22 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx - 26 * scale, y + 110 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 110 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 64 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 22 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx + 26 * scale, y + 110 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 110 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 64 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Gold trim on coat
|
|
ctx.strokeStyle = '#ffd700';
|
|
ctx.lineWidth = 2 * scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 10 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 110 * scale);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 10 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 110 * scale);
|
|
ctx.stroke();
|
|
|
|
// Gold buttons
|
|
ctx.fillStyle = '#ffd700';
|
|
for (var b = 0; b < 3; b++) {
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 14 * scale, y + 72 * scale + b * 12 * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 14 * scale, y + 72 * scale + b * 12 * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
// Cutlass (right hand)
|
|
var rightArm = getArmTransform(transforms, 'rightArm');
|
|
ctx.save();
|
|
ctx.translate(cx + 28 * scale, y + 110 * scale);
|
|
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Blade
|
|
ctx.fillStyle = '#c0c0c0';
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.quadraticCurveTo(8 * scale, -20 * scale, 5 * scale, -40 * scale);
|
|
ctx.lineTo(2 * scale, -40 * scale);
|
|
ctx.quadraticCurveTo(5 * scale, -20 * scale, -2 * scale, 0);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Guard
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 2 * scale, 8 * scale, 4 * scale, 0.3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Handle
|
|
ctx.fillStyle = '#3d2314';
|
|
roundRect(ctx, -2 * scale, 4 * scale, 4 * scale, 12 * scale, 1 * scale);
|
|
ctx.fill();
|
|
|
|
ctx.restore();
|
|
|
|
// Pirate hat
|
|
ctx.fillStyle = '#1a1a1a';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 28 * scale, y + 8 * scale);
|
|
ctx.quadraticCurveTo(cx - 32 * scale, y - 8 * scale, cx - 18 * scale, y - 18 * scale);
|
|
ctx.lineTo(cx, y - 8 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y - 18 * scale);
|
|
ctx.quadraticCurveTo(cx + 32 * scale, y - 8 * scale, cx + 28 * scale, y + 8 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 12 * scale, cx - 28 * scale, y + 8 * scale);
|
|
ctx.fill();
|
|
|
|
// Gold hat trim
|
|
ctx.strokeStyle = '#ffd700';
|
|
ctx.lineWidth = 2 * scale;
|
|
ctx.stroke();
|
|
|
|
// Skull emblem
|
|
ctx.fillStyle = '#fff';
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y - 5 * scale, 5 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.fillStyle = '#000';
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 2 * scale, y - 6 * scale, 1 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 2 * scale, y - 6 * scale, 1 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: SCIENTIST LAB COAT
|
|
// ============================================================================
|
|
|
|
var scientistLabcoat = {
|
|
id: 'scientist-labcoat',
|
|
name: 'Scientist Lab Coat',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['puzzle', 'physics', 'creative'],
|
|
description: 'Mad scientist ready to experiment',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'beaker', anchor: 'rightArm', offset: { x: 5, y: 10 } }
|
|
],
|
|
contextEffects: {
|
|
attack: { type: 'bubbles', color: '#00ff00' }
|
|
},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
|
|
// Shirt underneath
|
|
ctx.fillStyle = '#3498db';
|
|
roundRect(ctx, cx - 18 * scale, y + 64 * scale, 36 * scale, 35 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Tie
|
|
ctx.fillStyle = '#2c3e50';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 4 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx + 4 * scale, y + 64 * scale);
|
|
ctx.lineTo(cx + 3 * scale, y + 72 * scale);
|
|
ctx.lineTo(cx, y + 100 * scale);
|
|
ctx.lineTo(cx - 3 * scale, y + 72 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Pants
|
|
ctx.fillStyle = '#34495e';
|
|
roundRect(ctx, cx - 16 * scale, y + 98 * scale, 32 * scale, 55 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Sensible shoes
|
|
ctx.fillStyle = '#2c1810';
|
|
roundRect(ctx, cx - 18 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
|
|
// Lab coat
|
|
ctx.fillStyle = '#fff';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 24 * scale, y + 62 * scale);
|
|
ctx.lineTo(cx - 26 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 62 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 24 * scale, y + 62 * scale);
|
|
ctx.lineTo(cx + 26 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 140 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 62 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Coat collar
|
|
ctx.fillStyle = '#fff';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 20 * scale, y + 55 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 70 * scale);
|
|
ctx.lineTo(cx - 5 * scale, y + 60 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 58 * scale, cx + 5 * scale, y + 60 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 70 * scale);
|
|
ctx.lineTo(cx + 20 * scale, y + 55 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 50 * scale, cx - 20 * scale, y + 55 * scale);
|
|
ctx.fill();
|
|
|
|
// Coat sleeves
|
|
ctx.fillStyle = '#fff';
|
|
roundRect(ctx, cx - 32 * scale, y + 64 * scale, 12 * scale, 45 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 20 * scale, y + 64 * scale, 12 * scale, 45 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Pocket protector
|
|
ctx.fillStyle = '#ecf0f1';
|
|
roundRect(ctx, cx - 22 * scale, y + 72 * scale, 10 * scale, 15 * scale, 1 * scale);
|
|
ctx.fill();
|
|
|
|
// Pens in pocket
|
|
ctx.fillStyle = '#3498db';
|
|
ctx.fillRect(cx - 21 * scale, y + 70 * scale, 2 * scale, 12 * scale);
|
|
ctx.fillStyle = '#e74c3c';
|
|
ctx.fillRect(cx - 18 * scale, y + 71 * scale, 2 * scale, 11 * scale);
|
|
ctx.fillStyle = '#2c3e50';
|
|
ctx.fillRect(cx - 15 * scale, y + 70 * scale, 2 * scale, 10 * scale);
|
|
|
|
// ID badge
|
|
ctx.fillStyle = '#fff';
|
|
roundRect(ctx, cx + 12 * scale, y + 75 * scale, 12 * scale, 18 * scale, 1 * scale);
|
|
ctx.fill();
|
|
ctx.fillStyle = '#3498db';
|
|
ctx.fillRect(cx + 13 * scale, y + 76 * scale, 10 * scale, 6 * scale);
|
|
ctx.fillStyle = '#ddd';
|
|
ctx.fillRect(cx + 14 * scale, y + 84 * scale, 8 * scale, 2 * scale);
|
|
ctx.fillRect(cx + 14 * scale, y + 88 * scale, 6 * scale, 2 * scale);
|
|
|
|
// Safety goggles (on forehead)
|
|
ctx.fillStyle = '#2c3e50';
|
|
ctx.fillRect(cx - 24 * scale, y + 8 * scale, 48 * scale, 4 * scale);
|
|
ctx.fillStyle = 'rgba(100, 200, 255, 0.6)';
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx - 12 * scale, y + 10 * scale, 10 * scale, 6 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx + 12 * scale, y + 10 * scale, 10 * scale, 6 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#1a1a2e';
|
|
ctx.lineWidth = 2 * scale;
|
|
ctx.stroke();
|
|
|
|
// Beaker in hand
|
|
var rightArm = getArmTransform(transforms, 'rightArm');
|
|
ctx.save();
|
|
ctx.translate(cx + 28 * scale, y + 110 * scale);
|
|
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Beaker glass
|
|
ctx.fillStyle = 'rgba(200, 230, 255, 0.4)';
|
|
ctx.strokeStyle = '#aaa';
|
|
ctx.lineWidth = 1 * scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-5 * scale, -20 * scale);
|
|
ctx.lineTo(-6 * scale, 0);
|
|
ctx.lineTo(6 * scale, 0);
|
|
ctx.lineTo(5 * scale, -20 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
|
|
// Bubbling liquid
|
|
var liquidColor = 'rgba(0, 255, 100, 0.7)';
|
|
ctx.fillStyle = liquidColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-5 * scale, -5 * scale);
|
|
ctx.lineTo(-6 * scale, 0);
|
|
ctx.lineTo(6 * scale, 0);
|
|
ctx.lineTo(5 * scale, -5 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Bubbles
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
|
for (var bub = 0; bub < 3; bub++) {
|
|
var bubY = -8 * scale - ((time * 20 + bub * 30) % 15) * scale;
|
|
var bubX = (bub - 1) * 3 * scale;
|
|
ctx.beginPath();
|
|
ctx.arc(bubX, bubY, 1.5 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: CHEF OUTFIT
|
|
// ============================================================================
|
|
|
|
var chefOutfit = {
|
|
id: 'chef-outfit',
|
|
name: 'Chef Outfit',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['creative', 'puzzle'],
|
|
description: 'Master chef ready to cook',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'spatula', anchor: 'rightArm', offset: { x: 5, y: 15 } }
|
|
],
|
|
contextEffects: {},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
|
|
// Chef jacket
|
|
ctx.fillStyle = '#fff';
|
|
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 50 * scale, 4 * scale);
|
|
ctx.fill();
|
|
|
|
// Double-breasted buttons
|
|
ctx.fillStyle = '#ddd';
|
|
for (var row = 0; row < 2; row++) {
|
|
for (var col = 0; col < 3; col++) {
|
|
ctx.beginPath();
|
|
ctx.arc(cx + (row * 12 - 6) * scale, y + (75 + col * 12) * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
// Apron
|
|
ctx.fillStyle = '#f5f5f5';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 90 * scale);
|
|
ctx.lineTo(cx - 20 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx + 20 * scale, y + 155 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 90 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Apron strings
|
|
ctx.strokeStyle = '#ddd';
|
|
ctx.lineWidth = 2 * scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 90 * scale);
|
|
ctx.lineTo(cx - 25 * scale, y + 95 * scale);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 18 * scale, y + 90 * scale);
|
|
ctx.lineTo(cx + 25 * scale, y + 95 * scale);
|
|
ctx.stroke();
|
|
|
|
// Apron pocket
|
|
ctx.fillStyle = '#eee';
|
|
roundRect(ctx, cx - 10 * scale, y + 115 * scale, 20 * scale, 15 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Pants
|
|
ctx.fillStyle = '#2c3e50';
|
|
roundRect(ctx, cx - 16 * scale, y + 112 * scale, 32 * scale, 42 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Shoes
|
|
ctx.fillStyle = '#1a1a1a';
|
|
roundRect(ctx, cx - 18 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 4 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
|
|
ctx.fill();
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
|
|
// Chef jacket sleeves
|
|
ctx.fillStyle = '#fff';
|
|
roundRect(ctx, cx - 30 * scale, y + 66 * scale, 12 * scale, 40 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 18 * scale, y + 66 * scale, 12 * scale, 40 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Kitchen towel over shoulder
|
|
ctx.fillStyle = '#e74c3c';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 20 * scale, y + 64 * scale);
|
|
ctx.quadraticCurveTo(cx + 30 * scale, y + 70 * scale, cx + 25 * scale, y + 90 * scale);
|
|
ctx.lineTo(cx + 20 * scale, y + 88 * scale);
|
|
ctx.quadraticCurveTo(cx + 24 * scale, y + 72 * scale, cx + 18 * scale, y + 66 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Towel stripes
|
|
ctx.fillStyle = '#fff';
|
|
ctx.fillRect(cx + 21 * scale, y + 72 * scale, 2 * scale, 14 * scale);
|
|
ctx.fillRect(cx + 24 * scale, y + 74 * scale, 2 * scale, 12 * scale);
|
|
|
|
// Chef hat
|
|
ctx.fillStyle = '#fff';
|
|
|
|
// Puffy top
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 10 * scale, y - 25 * scale, 12 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 10 * scale, y - 25 * scale, 12 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y - 30 * scale, 14 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Hat band
|
|
roundRect(ctx, cx - 20 * scale, y - 12 * scale, 40 * scale, 20 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Hat band line
|
|
ctx.fillStyle = '#ecf0f1';
|
|
ctx.fillRect(cx - 20 * scale, y + 3 * scale, 40 * scale, 5 * scale);
|
|
|
|
// Utensils in apron pocket
|
|
ctx.fillStyle = '#888';
|
|
ctx.fillRect(cx - 6 * scale, y + 110 * scale, 2 * scale, 12 * scale);
|
|
ctx.fillRect(cx + 2 * scale, y + 112 * scale, 2 * scale, 10 * scale);
|
|
|
|
// Spatula in hand
|
|
var rightArm = getArmTransform(transforms, 'rightArm');
|
|
ctx.save();
|
|
ctx.translate(cx + 26 * scale, y + 108 * scale);
|
|
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Handle
|
|
ctx.fillStyle = '#5d4037';
|
|
roundRect(ctx, -2 * scale, 0, 4 * scale, 18 * scale, 1 * scale);
|
|
ctx.fill();
|
|
|
|
// Spatula head
|
|
ctx.fillStyle = '#888';
|
|
roundRect(ctx, -6 * scale, -15 * scale, 12 * scale, 18 * scale, 2 * scale);
|
|
ctx.fill();
|
|
|
|
// Slots
|
|
ctx.fillStyle = '#666';
|
|
ctx.fillRect(cx - 4 * scale - cx - 26 * scale, -12 * scale, 8 * scale, 2 * scale);
|
|
ctx.fillRect(cx - 4 * scale - cx - 26 * scale, -8 * scale, 8 * scale, 2 * scale);
|
|
ctx.fillRect(cx - 4 * scale - cx - 26 * scale, -4 * scale, 8 * scale, 2 * scale);
|
|
|
|
ctx.restore();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// COSTUME: COWBOY GEAR
|
|
// ============================================================================
|
|
|
|
var cowboyGear = {
|
|
id: 'cowboy-gear',
|
|
name: 'Cowboy Gear',
|
|
category: 'costume',
|
|
avatarMode: 'FULL_BODY',
|
|
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
|
|
defaultAnimation: 'idle',
|
|
accessoryVisibility: {
|
|
eyewear: true,
|
|
headwear: true,
|
|
effects: true
|
|
},
|
|
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
|
|
recommendedFor: ['action', 'adventure'],
|
|
description: 'Wild west cowboy',
|
|
avatarPosition: { x: 0.5, y: 0.5 },
|
|
avatarScale: 1.0,
|
|
props: [
|
|
{ id: 'lasso', anchor: 'rightArm', offset: { x: 10, y: 0 } }
|
|
],
|
|
contextEffects: {},
|
|
|
|
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
var vestColor = '#8b4513';
|
|
|
|
// Shirt
|
|
ctx.fillStyle = '#c19a6b';
|
|
roundRect(ctx, cx - 20 * scale, y + 64 * scale, 40 * scale, 45 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Shirt details (collar points)
|
|
ctx.fillStyle = '#a08060';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 8 * scale, y + 62 * scale);
|
|
ctx.lineTo(cx - 15 * scale, y + 75 * scale);
|
|
ctx.lineTo(cx - 5 * scale, y + 70 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 8 * scale, y + 62 * scale);
|
|
ctx.lineTo(cx + 15 * scale, y + 75 * scale);
|
|
ctx.lineTo(cx + 5 * scale, y + 70 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Vest
|
|
ctx.fillStyle = vestColor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 20 * scale, y + 65 * scale);
|
|
ctx.lineTo(cx - 22 * scale, y + 108 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 108 * scale);
|
|
ctx.lineTo(cx - 8 * scale, y + 75 * scale);
|
|
ctx.lineTo(cx - 12 * scale, y + 65 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 20 * scale, y + 65 * scale);
|
|
ctx.lineTo(cx + 22 * scale, y + 108 * scale);
|
|
ctx.lineTo(cx + 8 * scale, y + 108 * scale);
|
|
ctx.lineTo(cx + 8 * scale, y + 75 * scale);
|
|
ctx.lineTo(cx + 12 * scale, y + 65 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Belt
|
|
ctx.fillStyle = '#3d2314';
|
|
ctx.fillRect(cx - 20 * scale, y + 105 * scale, 40 * scale, 8 * scale);
|
|
|
|
// Belt buckle (large oval)
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx, y + 109 * scale, 8 * scale, 5 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.fillStyle = '#b8860b';
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx, y + 109 * scale, 5 * scale, 3 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Jeans
|
|
ctx.fillStyle = '#4a6fa5';
|
|
roundRect(ctx, cx - 16 * scale, y + 112 * scale, 32 * scale, 42 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Jean seams
|
|
ctx.strokeStyle = '#3d5a80';
|
|
ctx.lineWidth = 1 * scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 10 * scale, y + 115 * scale);
|
|
ctx.lineTo(cx - 10 * scale, y + 152 * scale);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 10 * scale, y + 115 * scale);
|
|
ctx.lineTo(cx + 10 * scale, y + 152 * scale);
|
|
ctx.stroke();
|
|
|
|
// Cowboy boots
|
|
ctx.fillStyle = '#5d4037';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 150 * scale);
|
|
ctx.lineTo(cx - 20 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx - 4 * scale, y + 150 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 4 * scale, y + 150 * scale);
|
|
ctx.lineTo(cx + 4 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx + 20 * scale, y + 160 * scale);
|
|
ctx.lineTo(cx + 18 * scale, y + 150 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Boot heel
|
|
ctx.fillStyle = '#3d2314';
|
|
ctx.fillRect(cx - 18 * scale, y + 158 * scale, 4 * scale, 4 * scale);
|
|
ctx.fillRect(cx + 14 * scale, y + 158 * scale, 4 * scale, 4 * scale);
|
|
|
|
// Boot top decoration
|
|
ctx.fillStyle = '#8b7355';
|
|
ctx.fillRect(cx - 17 * scale, y + 150 * scale, 12 * scale, 3 * scale);
|
|
ctx.fillRect(cx + 5 * scale, y + 150 * scale, 12 * scale, 3 * scale);
|
|
|
|
// Spurs
|
|
ctx.fillStyle = '#c0c0c0';
|
|
ctx.beginPath();
|
|
ctx.arc(cx - 19 * scale, y + 158 * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(cx + 19 * scale, y + 158 * scale, 2 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
},
|
|
|
|
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
|
|
var cx = x + 32 * scale;
|
|
|
|
// Shirt sleeves
|
|
ctx.fillStyle = '#c19a6b';
|
|
roundRect(ctx, cx - 28 * scale, y + 66 * scale, 10 * scale, 40 * scale, 3 * scale);
|
|
ctx.fill();
|
|
roundRect(ctx, cx + 18 * scale, y + 66 * scale, 10 * scale, 40 * scale, 3 * scale);
|
|
ctx.fill();
|
|
|
|
// Bandana around neck
|
|
ctx.fillStyle = '#e74c3c';
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 15 * scale, y + 58 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 65 * scale, cx + 15 * scale, y + 58 * scale);
|
|
ctx.lineTo(cx + 12 * scale, y + 62 * scale);
|
|
ctx.quadraticCurveTo(cx, y + 68 * scale, cx - 12 * scale, y + 62 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Bandana knot
|
|
ctx.beginPath();
|
|
ctx.arc(cx, y + 68 * scale, 4 * scale, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Bandana tails
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 2 * scale, y + 72 * scale);
|
|
ctx.quadraticCurveTo(cx - 5 * scale, y + 85 * scale, cx - 8 * scale + Math.sin(time * 2) * 2 * scale, y + 92 * scale);
|
|
ctx.lineTo(cx - 5 * scale + Math.sin(time * 2) * 2 * scale, y + 90 * scale);
|
|
ctx.quadraticCurveTo(cx - 3 * scale, y + 82 * scale, cx, y + 72 * scale);
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + 2 * scale, y + 72 * scale);
|
|
ctx.quadraticCurveTo(cx + 5 * scale, y + 85 * scale, cx + 8 * scale + Math.sin(time * 2 + 0.5) * 2 * scale, y + 92 * scale);
|
|
ctx.lineTo(cx + 5 * scale + Math.sin(time * 2 + 0.5) * 2 * scale, y + 90 * scale);
|
|
ctx.quadraticCurveTo(cx + 3 * scale, y + 82 * scale, cx, y + 72 * scale);
|
|
ctx.fill();
|
|
|
|
// Cowboy hat
|
|
var hatColor = '#8b4513';
|
|
|
|
// Brim
|
|
ctx.fillStyle = hatColor;
|
|
ctx.beginPath();
|
|
ctx.ellipse(cx, y + 8 * scale, 32 * scale, 8 * scale, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Crown
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx - 18 * scale, y + 6 * scale);
|
|
ctx.quadraticCurveTo(cx - 20 * scale, y - 18 * scale, cx - 10 * scale, y - 22 * scale);
|
|
ctx.quadraticCurveTo(cx, y - 16 * scale, cx + 10 * scale, y - 22 * scale);
|
|
ctx.quadraticCurveTo(cx + 20 * scale, y - 18 * scale, cx + 18 * scale, y + 6 * scale);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// Hat band
|
|
ctx.fillStyle = darken(hatColor, 0.3);
|
|
ctx.fillRect(cx - 18 * scale, y + 1 * scale, 36 * scale, 5 * scale);
|
|
|
|
// Hat band buckle
|
|
ctx.fillStyle = '#ffd700';
|
|
ctx.fillRect(cx - 4 * scale, y, 8 * scale, 7 * scale);
|
|
|
|
// Lasso in hand
|
|
var rightArm = getArmTransform(transforms, 'rightArm');
|
|
ctx.save();
|
|
ctx.translate(cx + 26 * scale, y + 108 * scale);
|
|
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
|
|
|
|
// Coiled rope
|
|
ctx.strokeStyle = '#c19a6b';
|
|
ctx.lineWidth = 3 * scale;
|
|
ctx.beginPath();
|
|
for (var coil = 0; coil < 3; coil++) {
|
|
ctx.arc(0, -5 * scale + coil * 6 * scale, 8 * scale, 0, Math.PI * 1.8);
|
|
}
|
|
ctx.stroke();
|
|
|
|
// Rope end
|
|
ctx.beginPath();
|
|
ctx.moveTo(8 * scale, 10 * scale);
|
|
ctx.lineTo(15 * scale, 20 * scale);
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// CONTEXTS ARRAY
|
|
// ============================================================================
|
|
|
|
var CONTEXTS = [
|
|
knightArmor,
|
|
spaceSuit,
|
|
ninjaOutfit,
|
|
wizardRobes,
|
|
superheroCape,
|
|
athleteUniform,
|
|
pirateOutfit,
|
|
scientistLabcoat,
|
|
chefOutfit,
|
|
cowboyGear
|
|
];
|
|
|
|
// ============================================================================
|
|
// PUBLIC API
|
|
// ============================================================================
|
|
|
|
window.CostumeContexts = {
|
|
CONTEXTS: CONTEXTS,
|
|
|
|
getById: function(id) {
|
|
for (var i = 0; i < CONTEXTS.length; i++) {
|
|
if (CONTEXTS[i].id === id) {
|
|
return CONTEXTS[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
getAll: function() {
|
|
return CONTEXTS.slice();
|
|
},
|
|
|
|
getRecommendedFor: function(gameType) {
|
|
var recommended = [];
|
|
for (var i = 0; i < CONTEXTS.length; i++) {
|
|
if (CONTEXTS[i].recommendedFor && CONTEXTS[i].recommendedFor.indexOf(gameType) !== -1) {
|
|
recommended.push(CONTEXTS[i]);
|
|
}
|
|
}
|
|
return recommended;
|
|
},
|
|
|
|
isAccessoryVisible: function(costumeId, accessoryType) {
|
|
var costume = this.getById(costumeId);
|
|
if (!costume || !costume.accessoryVisibility) {
|
|
return true;
|
|
}
|
|
return costume.accessoryVisibility[accessoryType] !== false;
|
|
}
|
|
};
|
|
|
|
})();
|