Archive commit

This commit is contained in:
Erik McClure
2019-08-28 21:15:02 -07:00
commit 735845746e
1435 changed files with 140724 additions and 0 deletions
@@ -0,0 +1,389 @@
ng-template(#descriptionTooltip)
.text-left
p Description and tags help you organize and search for your ponies.
p Use # tags to be able to quickly find specific ponies like #oc #winter or #unicorn
p
| Use @ tags to sort your ponies. @top to put them at the start of the list, @end at the end,
| and @1 @2 @3 ... to order them manually.
page-loader(*ngIf="!playing")
.row(*ngIf="!playing && !loading")
.col-lg-6.text-center
h1.sr-only(focusTitle) Characters
h2.sr-only Character selection
.mx-auto(style="max-width: 400px;")
support-button.mb-3
install-button.mb-3
character-select(
[(error)]="error" [newButton]="true" [removeButton]="true" (change)="updateMuzzles()"
(preview)="previewPony = $event")
.character-preview-box.mx-auto.my-3((fixToTop)="fixed = $event" [fixToTopOffset]="-40")
character-preview(
#characterPreview [pony]="previewInfo" [state]="state" [noBackground]="true" [noShadow]="fixed"
[name]="previewName" [tag]="previewTag" [extra]="previewExtra" style="z-index: 100")
.form-group.text-center
button.btn.btn-lg.btn-default([disabled]="!canDuplicate" (click)="duplicate()" aria-label="Duplicate character")
| Duplicate
button.btn.btn-lg.btn-default.ml-2([disabled]="!canRevert" (click)="revert()" aria-label="Revert changes")
| Revert
button.btn.btn-lg.btn-default.ml-2([disabled]="!canSave" (click)="save()" aria-label="Save character")
| Save
play-box(label="Save & Play" [(error)]="error")
play-notice
.col-lg-6.character-tabs([setOutlineHidden]="!customOutlines" [class.character-tab-warning]="updateWarning")
h2.sr-only Character customization
tabset(type="pills" *ngIf="loaded" saveActiveTab="character-active-tab" label="Character customization")
tab(title="body")
.character-tab(*tabContent)
div(*ngIf="account?.supporter")
.row.form-group
label.col-sm-4.col-form-label.text-patreon
| Supporter options
.col-sm-8.text-muted.pt-2
custom-checkbox([(checked)]="pony.hideSupport") hide supporter tag
hr.my-2
.row.form-group
label.col-sm-4.col-form-label Advanced options
.col-sm-8.text-muted.pt-1
custom-checkbox([(checked)]="pony.respawnAtSpawn")
| always respawn at spawn point
custom-checkbox([(checked)]="info.customOutlines")
| allow custom outlines
custom-checkbox([(checked)]="info.freeOutlines" (checkedChange)="freeOutlinesChanged($event)")
| don't darken outlines
custom-checkbox(
[(checked)]="info.darkenLockedOutlines" [disabled]="!info.freeOutlines"
(checkedChange)="darkenLockedOutlinesChanged($event)")
| darken locked outlines
.row.form-group(*ngIf="debug")
label.col-sm-4.col-form-label Debug options
.col-sm-8
button.btn.btn-sm.btn-default((click)="addBlush()") Add blush
.row.form-group
label.col-sm-4.col-form-label Export/Import options
.col-sm-8
button.btn.btn-sm.btn-default((click)="exportPony()") Export pony
button.btn.btn-sm.btn-default.ml-1((click)="exportPonies()") Export all ponies
button.btn.btn-sm.btn-default.ml-1((click)="importFile.click()") Import ponies
input.d-none(#importFile type="file" (change)="importPonies(importFile.files[0])")
.row.form-group
label#social-site-label.col-sm-4.col-form-label Show social site
.col-sm-8
.dropdown.btn-group(dropdown)
button.btn.btn-default.dropdown-toggle(
dropdownToggle aria-labelledby="social-site-label" aria-describedby="social-site-help")
fa-icon.mr-1(
*ngIf="site?.icon" [icon]="icon(site.icon)" [fixedWidth]="true" [style.color]="site.color"
size="lg" [attr.aria-label]="site.icon")
strong {{site?.name}}
.dropdown-menu(*dropdownMenu)
a.dropdown-item(*ngFor="let s of sites" (click)="site = s")
fa-icon.mr-1(
*ngIf="s.icon" [icon]="icon(s.icon)" [fixedWidth]="true" [style.color]="s.color"
size="lg" [attr.aria-label]="s.icon")
strong {{s.name}}
.dropdown.btn-group.ml-1(dropdown *ngIf="tags.length > 1")
button.btn.btn-default.dropdown-toggle(dropdownToggle aria-label="Show tag")
span.badge(*ngIf="tag.id" [ngClass]="'badge-' + tag.className") {{tag.id}}
strong(*ngIf="!tag.id") {{tag.name}}
.dropdown-menu(*dropdownMenu)
a.dropdown-item(*ngFor="let t of tags" (click)="tag = t")
span.badge(*ngIf="t.id" [ngClass]="'badge-' + t.className") {{t.id}}
strong(*ngIf="!t.id") {{t.name}}
#social-site-help.text-muted.mt-1
| Shows social site next to your name to prove to others that it's your account.
.row.form-group
label.col-sm-4.col-form-label(for="description")
| Description
fa-icon.text-muted.ml-2([icon]="infoIcon" [tooltip]="descriptionTooltip" containerClass="tooltip-desc")
.col-sm-8
input.form-control#description(
[(ngModel)]="pony.desc" placeholder="example: with a hat #oc #winter" [maxlength]="maxDescLength")
.row.form-group
.col-sm-4.col-lock-box
label(for="frame-input") Animation
check-box.lock-box([icon]="playIcon" [(checked)]="playAnimation" label="Play animation")
.col-sm-8
.input-group
input.form-control#frame-input(
type="number" [(ngModel)]="state.animationFrame" [disabled]="playAnimation" min="0"
title="Animation frame" aria-label="Animation frame")
.input-group-append.dropdown(*ngIf="canSaveFiles" dropdown)
button.btn.btn-default.dropdown-toggle(dropdownToggle *ngIf="canExport" aria-label="Export / Import")
fa-icon([icon]="codeIcon")
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
a.dropdown-item((click)="export()") export all
a.dropdown-item((click)="import()") import data
a.dropdown-item((click)="testSize()") print compressed
a.dropdown-item((click)="testJSON()") print raw
.input-group-append.dropdown(dropdown)
button.btn.btn-default(
*ngIf="canSaveFiles" (click)="export(activeAnimation)" title="Save as PNG" aria-label="Save as PNG")
fa-icon([icon]="saveIcon")
button.btn.btn-default.dropdown-toggle(dropdownToggle style="min-width: 80px")
strong {{animations[activeAnimation]().name}}
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
a.dropdown-item(*ngFor="let a of animations; let i = index" (click)="setActiveAnimation(i)")
| {{a().name}}
hr
fill-outline(
label="Body color" [(fill)]="info.coatFill" [(outline)]="info.coatOutline"
[(outlineLocked)]="info.lockCoatOutline" [outlineHidden]="!customOutlines" (change)="changed()")
hr
set-selection(
label="Horn" [base]="coatFill" [set]="info.horn" [sets]="horns" [compact]="true" (change)="changed()"
[darken]="darken")
hr
.row.form-group
label.col-sm-4.col-form-label Magic color
.col-sm-8
color-picker([(color)]="info.magicColor" label="Magic color")
hr
set-selection(
label="Wings" [base]="coatFill" [set]="info.wings" [sets]="wings" [compact]="true" (change)="changed()"
[darken]="darken")
hr
set-selection(
label="Ears" [base]="coatFill" [set]="info.ears" [sets]="ears" [compact]="true" (change)="changed()"
[darken]="darken")
hr
set-selection(
label="Front hooves" [base]="coatFill" [set]="info.frontHooves" [sets]="frontHooves" [compact]="true"
[darken]="darken" (change)="changed()")
hr
set-selection(
label="Back hooves" [base]="hoovesFill" [set]="info.backHooves" [sets]="backHooves" [compact]="true"
[darken]="darken" (change)="changed()")
hr
butt-mark-editor([info]="info" [state]="buttMarkState")
tab(title="mane")
.character-tab(*tabContent)
set-selection(
label="Mane" [base]="maneFill" [set]="info.mane" [sets]="manes" [nonLockable]="true"
[darken]="darken" (change)="changed()")
hr
set-selection(
label="Back mane" [base]="maneFill" [set]="info.backMane" [sets]="backManes"
[darken]="darken" (change)="changed()")
tab(title="tail")
.character-tab(*tabContent)
set-selection(
label="Tail" [base]="maneFill" [set]="info.tail" [sets]="tails" [darken]="darken" (change)="changed()")
.form-group.text-center
button.btn.btn-sm.btn-default((click)="copyCoatColorToTail()" [disabled]="info.tail.lockFills[0]")
| Use body color
tab(title="face")
.character-tab(*tabContent)
.row.form-group
.col-sm-4.col-lock-box
label#eye-color Eye color
check-box.lock-box(
[(checked)]="info.lockEyeColor" [icon]="lockIcon" (checkedChange)="eyeColorLockChanged($event)"
label="Lock color for both eyes" title="Lock color for both eyes")
.col-sm-8
color-picker([(color)]="info.eyeColorRight" (colorChange)="changed()" labelledBy="eye-color")
.row.form-group(*ngIf="!info.lockEyeColor")
label#eye-color-left.col-sm-4.col-form-label Eye color (left)
.col-sm-8
color-picker([(color)]="info.eyeColorLeft" labelledBy="eye-color-left")
.row.form-group
.col-sm-4.col-lock-box
label#eye-whites-label Eye whites
check-box.lock-box(
[(checked)]="lockEyeWhites" [icon]="lockIcon" (checkedChange)="eyeWhiteLockChanged($event)"
label="Lock left eye white for both eyes" title="Lock left eye white for both eyes")
.col-sm-8
color-picker([(color)]="info.eyeWhites" (colorChange)="changed()" labelledBy="eye-whites-label")
.row.form-group(*ngIf="info.unlockEyeWhites")
label#eye-whites-label-left.col-sm-4.col-form-label Eye whites (left)
.col-sm-8
color-picker([(color)]="info.eyeWhitesLeft" labelledBy="eye-whites-label-left")
.row.form-group
.col-sm-4.col-lock-box
label#eyes {{info.lockEyes ? 'Eyes' : 'Right eye'}}
check-box.lock-box(
[(checked)]="info.lockEyes" [icon]="lockIcon" (checkedChange)="eyeOpennessChanged($event)"
label="Lock both eyes" title="Lock both eyes")
.col-sm-8
sprite-selection(
[(selected)]="info.eyeOpennessRight" [sprites]="eyesRight" [fill]="info.eyeColorRight"
[circle]="info.coatFill" [reverseExtra]="true" [skip]="1" [limit]="9" [darken]="darken"
(selectedChange)="eyeOpennessChanged(info.lockEyes)" aria-labelledby="eyes")
.row.form-group(*ngIf="!info.lockEyes")
.col-sm-4
label#left-eye Left eye
.col-sm-8
sprite-selection(
[(selected)]="info.eyeOpennessLeft" [sprites]="eyesLeft" [fill]="info.eyeColorLeft" [darken]="darken"
[circle]="info.coatFill" [reverseExtra]="true" [skip]="1" [limit]="5" aria-labelledby="left-eye")
.row.form-group
.col-sm-4.col-lock-box
label Eyeshadow
check-box.lock-box(
[(checked)]="info.eyeshadow" (checkedChange)="characterPreview.blink()"
label="Use eye shadow" title="Use eye shadow")
.col-sm-8
color-picker([(color)]="info.eyeshadowColor" [isDisabled]="!info.eyeshadow" label="Eyeshadow color")
.row.form-group
label#eyelashes.col-sm-4.col-form-label Eyelashes
.col-sm-8
sprite-selection(
[(selected)]="info.eyelashes" [sprites]="eyelashes" [fill]="info.eyeColorRight" [darken]="darken"
[circle]="info.coatFill" [reverseExtra]="true" aria-labelledby="eyelashes")
.row.form-group
.col-sm-4.col-lock-box
label#eyelashes-label Eyelash color
check-box.lock-box(
[(checked)]="lockEyelashColor" [icon]="lockIcon" (checkedChange)="eyelashLockChanged($event)"
label="Lock eyelash color for both eyes" title="Lock eyelash color for both eyes")
.col-sm-8
color-picker([(color)]="info.eyelashColor" (colorChange)="changed()" labelledBy="eyelashes-label")
.row.form-group(*ngIf="!lockEyelashColor")
label#eyelashes-label-left.col-sm-4.col-form-label Eyelash color (left)
.col-sm-8
color-picker([(color)]="info.eyelashColorLeft" labelledBy="eyelashes-label-left")
hr
set-selection(
label="Muzzle" base="info.coatFill" [set]="info.nose" [sets]="noses" [darken]="darken"
[compact]="true" (change)="changed(); updateMuzzles()")
hr
set-selection(
label="Markings" [base]="coatFill" [set]="info.head" [sets]="heads" [darken]="darken"
[onlyPatterns]="true" (change)="changed()")
hr
.row.form-group
label#fangs.col-sm-4.col-form-label Fangs
.col-sm-8
sprite-selection(
[(selected)]="info.fangs" [sprites]="fangs" [fill]="info.nose.fills" [darken]="darken"
[outline]="info.nose.outlines" [circle]="info.coatFill" aria-labelledby="fangs")
.row.form-group
label#expression.col-sm-4.col-form-label(for="#expression-selection") Expression
.col-sm-8
sprite-selection#expression-selection(
[(selected)]="info.muzzle" [sprites]="muzzles" [fill]="info.nose.fills" [darken]="darken"
[outline]="info.nose.outlines" [circle]="info.coatFill" [limit]="9" aria-labelledby="expression")
hr
set-selection(
label="Facial hair" [base]="maneFill" [set]="info.facialHair" [sets]="facialHair" [darken]="darken"
(change)="changed()")
tab(title="other")
.character-tab(*tabContent)
tabset.accessories-tabset(saveActiveTab="character-active-accessory-tab" label="Character accessories")
tab(title="head")
.accessory-tab(*tabContent)
set-selection(
label="Head accessories" [set]="info.headAccessory" [sets]="headAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
hr
set-selection(
label="Ear accessories" [set]="info.earAccessory" [sets]="earAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
hr
set-selection(
label="Face accessories" [set]="info.faceAccessory" [sets]="faceAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
tab(title="neck")
.accessory-tab(*tabContent)
set-selection(
label="Neck accessories" [set]="info.neckAccessory" [sets]="neckAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
tab(title="legs")
.accessory-tab(*tabContent)
.row.form-group
label.col-sm-4.col-form-label Use the same accessory
.col-sm-8.text-muted.pt-1
.mb-2
custom-checkbox([(checked)]="info.lockBackLegAccessory")
| for front and back legs
.mb-2
custom-checkbox([(checked)]="lockFrontLegAccessory")
| for both front legs
.mb-2
custom-checkbox([(checked)]="lockBackLegAccessory" [disabled]="info.lockBackLegAccessory")
| for both back legs
set-selection(
[label]="info.lockFrontLegAccessories ? 'Front leg accessory' : 'Front leg accessory (left)'"
[set]="info.frontLegAccessory" [sets]="frontLegAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
hr
div(*ngIf="info.unlockFrontLegAccessory")
set-selection(
label="Front leg accessory (right)"
[set]="info.frontLegAccessoryRight" [sets]="frontLegAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
hr
//- .form-group.d-flex.justify-content-center
custom-checkbox([(checked)]="info.lockBackLegAccessory") use the same accessory for back legs
div(*ngIf="!info.lockBackLegAccessory")
set-selection(
[label]="info.lockBackLegAccessories ? 'Back leg accessory' : 'Back leg accessory (left)'"
[set]="info.backLegAccessory" [sets]="backLegAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
hr
div(*ngIf="!info.lockBackLegAccessory && info.unlockBackLegAccessory")
set-selection(
label="Back leg accessory (right)"
[set]="info.backLegAccessoryRight" [sets]="backLegAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
tab(title="chest")
.accessory-tab(*tabContent)
set-selection(
label="Chest accessories" [set]="info.chestAccessory" [sets]="chestAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
set-selection(
*ngIf="hasSleeves(info.chestAccessory.type)" label="Sleeves" [set]="info.sleeveAccessory" [darken]="darken"
[sets]="sleeveAccessories" (change)="changed()")
tab(title="back")
.accessory-tab(*tabContent)
set-selection(
label="Back accessories" [set]="info.backAccessory" [sets]="backAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
tab(title="waist")
.accessory-tab(*tabContent)
set-selection(
label="Waist accessories" [set]="info.waistAccessory" [sets]="waistAccessories" [darken]="darken"
[nonLockable]="true" (change)="changed()")
tab(title="other")
.accessory-tab(*tabContent)
.form-group.d-flex.justify-content-center.pt-2
custom-checkbox([(checked)]="previewExtra") show preview
em.text-muted.ml-2
| (you have to enable these accessories in-game)
set-selection(
label="Extra accessories" [set]="info.extraAccessory" [sets]="extraAccessories"
[darken]="darken" (change)="changed()")
@@ -0,0 +1,62 @@
@import '../../../../styles/partials/variables';
.character-tabs {
min-height: 500px;
margin-bottom: 80px;
}
.character-tab {
background: $dark-bg;
margin-top: 10px;
padding: 10px 15px;
min-height: 900px;
border-radius: $border-radius-sm;
.character-tab-warning & {
box-shadow: 0 0 0 2px $warning;
}
}
.accessory-tab {
margin-top: 10px;
}
.character-preview-box {
position: relative;
width: 400px;
height: 400px;
background: $grass-color;
border-radius: $border-radius-lg;
@include media-breakpoint-down(sm) {
width: 280px;
height: 280px;
}
&.fixed-to-top {
character-preview {
position: fixed;
top: -45px;
z-index: 100;
pointer-events: none;
}
}
}
.col-lock-box {
display: flex;
align-items: flex-start;
justify-content: space-between;
> .lock-box {
@include media-breakpoint-down(xs) {
margin-right: 0;
margin-bottom: 5px;
}
}
> label {
margin: 0;
padding-top: 6px;
}
}
@@ -0,0 +1,501 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { clamp } from 'lodash';
import { PLAYER_NAME_MAX_LENGTH, PLAYER_DESC_MAX_LENGTH } from '../../../common/constants';
import {
PonyInfo, PonyObject, PonyState, SocialSiteInfo, ColorExtraSet, ColorExtra, CharacterTag, PonyEye, Eye, Muzzle,
Iris, ExpressionExtra
} from '../../../common/interfaces';
import { findById, toInt, cloneDeep, delay } from '../../../common/utils';
import {
SLEEVED_ACCESSORIES, frontHooves, mergedBackManes, mergedManes, mergedFacialHair, mergedEarAccessories,
mergedChestAccessories, mergedFaceAccessories, mergedBackAccessories, mergedExtraAccessories, mergedHeadAccessories
} from '../../../client/ponyUtils';
import { defaultPonyState, defaultDrawPonyOptions } from '../../../client/ponyHelpers';
import { toPalette, getBaseFill, syncLockedPonyInfo } from '../../../common/ponyInfo';
import * as sprites from '../../../generated/sprites';
import { boop, trot, stand, sitDownUp, lieDownUp, fly, flyBug } from '../../../client/ponyAnimations';
import { drawCanvas } from '../../../graphics/contextSpriteBatch';
import { Model, getPonyTag } from '../../services/model';
import { loadAndInitSpriteSheets, addTitles, createEyeSprite, addLabels } from '../../../client/spriteUtils';
import { GameService } from '../../services/gameService';
import { TRANSPARENT, BLACK, blushColor } from '../../../common/colors';
import { precompressPony, compressPonyString, decompressPony, decompressPonyString } from '../../../common/compressPony';
import { saveCanvas } from '../../../client/canvasUtils';
import { drawPony } from '../../../client/ponyDraw';
import { getProviderIcon } from '../../shared/sign-in-box/sign-in-box';
import { faPlay, faLock, faSave, faCode, faInfoCircle } from '../../../client/icons';
import { isFileSaverSupported, createExpression, readFileAsText } from '../../../client/clientUtils';
import { emptyTag, getAvailableTags } from '../../../common/tags';
import { ButtMarkEditorState } from '../../shared/butt-mark-editor/butt-mark-editor';
import { parseColorWithAlpha } from '../../../common/color';
const frontHoofTitles = ['', 'Fetlocks', 'Paws', 'Claws', ''];
const backHoofTitles = ['', 'Fetlocks', 'Paws', '', ''];
const horns = addLabels(sprites.horns, [
'None', 'Unicorn horn', 'Short unicorn horn', 'Curved unicorn horn', 'Tiny deer antlers',
'Short deer antlers', 'Medium deer antlers', 'Large deer antlers', 'Raindeer antlers', 'Goat horns',
'Ram horns', 'Buffalo horns', 'Moose horns', 'Bug antenna', 'Long unicorn horn',
]);
const wings = addLabels(sprites.wings[0]!, [
'None', 'Pegasus wings', 'Bat wings', 'Gryphon wings', 'Bug wings'
]);
const ears = addLabels(sprites.ears, [
'Regular ears', 'Fluffy ears', 'Long feathered ears', 'Bug ears', 'Short feathered ears', 'Deer ears',
]);
const noses = addTitles(sprites.noses[0], ['Pony muzzle', 'Gryphon beak', 'Deer nose']);
const flyAnimations = [{ ...stand, name: 'fly' }, fly, fly, fly, { ...flyBug, name: 'fly' }];
function eyeSprite(e: PonyEye | undefined) {
return createEyeSprite(e, 0, sprites.defaultPalette);
}
@Component({
selector: 'character',
templateUrl: 'character.pug',
styleUrls: ['character.scss'],
})
export class Character implements OnInit, OnDestroy {
readonly debug = DEVELOPMENT || BETA;
readonly playIcon = faPlay;
readonly lockIcon = faLock;
readonly saveIcon = faSave;
readonly codeIcon = faCode;
readonly infoIcon = faInfoCircle;
readonly maxNameLength = PLAYER_NAME_MAX_LENGTH;
readonly maxDescLength = PLAYER_DESC_MAX_LENGTH;
readonly horns = horns;
readonly manes = mergedManes;
readonly backManes = mergedBackManes;
readonly tails = sprites.tails[0];
readonly wings = wings;
readonly ears = ears;
readonly facialHair = mergedFacialHair;
readonly headAccessories = mergedHeadAccessories;
readonly earAccessories = mergedEarAccessories;
readonly faceAccessories = mergedFaceAccessories;
readonly neckAccessories = sprites.neckAccessories[1];
readonly frontLegAccessories = sprites.frontLegAccessories[1];
readonly backLegAccessories = sprites.backLegAccessories[1];
readonly backAccessories = mergedBackAccessories;
readonly chestAccessories = mergedChestAccessories;
readonly sleeveAccessories = sprites.frontLegSleeves[1];
readonly waistAccessories = sprites.waistAccessories[1];
readonly extraAccessories = mergedExtraAccessories;
readonly frontHooves = addTitles(frontHooves[1], frontHoofTitles);
readonly backHooves = addTitles(sprites.backLegHooves[1], backHoofTitles);
readonly animations = [
() => stand,
() => trot,
() => boop,
() => sitDownUp,
() => lieDownUp,
() => flyAnimations[this.previewInfo!.wings!.type || 0],
];
readonly eyelashes: ColorExtraSet = sprites.eyeLeft[1]!.map(eyeSprite);
readonly eyesLeft: ColorExtraSet = sprites.eyeLeft.map(e => e && e[0]).map(eyeSprite);
readonly eyesRight: ColorExtraSet = sprites.eyeRight.map(e => e && e[0]).map(eyeSprite);
readonly noses = noses;
readonly heads = sprites.head1[1];
readonly buttMarkState: ButtMarkEditorState = {
brushType: 'brush',
brush: 'orange',
};
muzzles: ColorExtraSet;
fangs: ColorExtraSet;
tags: CharacterTag[] = [
emptyTag,
];
state = defaultPonyState();
saved: PonyInfo[] = [];
activeAnimation = 0;
loaded = false;
playAnimation = true;
deleting = false;
fixed = false;
previewExtra = false;
previewPony: PonyObject | undefined = undefined;
sites: SocialSiteInfo[] = [];
error?: string;
canSaveFiles = isFileSaverSupported();
private savingLocked = false;
private interval?: any;
private syncTimeout?: any;
private animationTime = 0;
constructor(private gameService: GameService, private model: Model) {
this.createMuzzles();
this.updateMuzzles();
}
private getMuzzleType() {
return clamp(toInt(this.info && this.info.nose && this.info.nose.type), 0, sprites.noses[0].length);
}
createMuzzles() {
const type = this.getMuzzleType();
const happy = sprites.noses[0][type][0];
this.muzzles = sprites.noses
.slice()
.map(n => n[type][0])
.map(({ color, colors, mouth }) => ({ color, colors, extra: mouth, palettes: [sprites.defaultPalette] } as ColorExtra));
this.fangs = [undefined, { color: happy.color, colors: 3, extra: happy.fangs, palettes: [sprites.defaultPalette] }];
}
updateMuzzles() {
const type = this.getMuzzleType();
const happy = sprites.noses[0][type][0];
this.muzzles!.forEach((m, i) => {
if (m) {
const { color, colors, mouth } = sprites.noses[i][type][0];
m.color = color;
m.colors = colors;
m.extra = mouth;
m.timestamp = Date.now();
}
});
const fangs = this.fangs![1]!;
fangs.color = happy.color;
fangs.extra = happy.fangs;
fangs.timestamp = Date.now();
}
get account() {
return this.model.account;
}
get loading() {
return this.model.loading || this.model.updating;
}
get updateWarning() {
return this.gameService.updateWarning;
}
get playing() {
return this.gameService.playing;
}
get previewInfo() {
return this.previewPony ? this.previewPony.ponyInfo : this.pony.ponyInfo;
}
get previewName() {
return this.previewPony ? this.previewPony.name : this.pony.name;
}
get previewTag() {
return getPonyTag(this.previewPony || this.pony, this.account);
}
get customOutlines() {
return this.info.customOutlines;
}
get ponies() {
return this.model.ponies;
}
get pony() {
return this.model.pony;
}
set pony(value: PonyObject) {
this.model.selectPony(value);
}
get info() {
return this.pony.ponyInfo!;
}
get maneFill() {
return getBaseFill(this.info.mane);
}
get coatFill() {
return this.info.coatFill;
}
get hoovesFill() {
return getBaseFill(this.info.frontHooves);
}
get canExport() {
return DEVELOPMENT;
}
get site() {
return findById(this.sites, this.pony.site) || this.sites[0];
}
set site(value: SocialSiteInfo) {
this.pony.site = value.id;
}
get tag() {
return findById(this.tags, this.pony.tag) || this.tags[0];
}
set tag(value: CharacterTag) {
this.pony.tag = value.id;
}
get lockEyeWhites() {
return !this.info.unlockEyeWhites;
}
set lockEyeWhites(value) {
this.info.unlockEyeWhites = !value;
}
get darken() {
return !this.info.freeOutlines;
}
get lockFrontLegAccessory() {
return !this.info.unlockFrontLegAccessory;
}
set lockFrontLegAccessory(value) {
this.info.unlockFrontLegAccessory = !value;
}
get lockBackLegAccessory() {
return !this.info.unlockBackLegAccessory;
}
set lockBackLegAccessory(value) {
this.info.unlockBackLegAccessory = !value;
}
get lockEyelashColor() {
return !this.info.unlockEyelashColor;
}
set lockEyelashColor(value) {
this.info.unlockEyelashColor = !value;
}
icon(id: string) {
return getProviderIcon(id);
}
hasSleeves(type: number) {
return SLEEVED_ACCESSORIES.indexOf(type) !== -1;
}
ngOnInit() {
if (this.model.account) {
this.tags.push(...getAvailableTags(this.model.account));
}
this.sites = this.model.sites.filter(s => !!s.name);
this.updateMuzzles();
let last = Date.now();
return loadAndInitSpriteSheets().then(() => {
this.loaded = true;
this.interval = setInterval(() => {
const now = Date.now();
this.update((now - last) / 1000);
last = now;
}, 1000 / 24);
});
}
ngOnDestroy() {
clearInterval(this.interval);
}
changed() {
if (!this.syncTimeout) {
this.syncTimeout = requestAnimationFrame(() => {
this.syncTimeout = undefined;
syncLockedPonyInfo(this.info);
});
}
if (DEVELOPMENT || BETA) {
this.state.blushColor = blushColor(parseColorWithAlpha(this.coatFill || '', 1));
}
}
update(delta: number) {
this.animationTime += delta;
const animation = this.animations[this.activeAnimation]();
this.state.animation = animation;
if (this.playAnimation) {
this.state.animationFrame = Math.floor(this.animationTime * animation.fps) % animation.frames.length;
}
}
copyCoatColorToTail() {
if (this.info.tail && this.info.tail.fills) {
this.info.tail.fills[0] = this.info.coatFill;
this.changed();
}
}
eyeColorLockChanged(locked: boolean) {
if (locked) {
this.info.eyeColorLeft = this.info.eyeColorRight;
}
}
eyeWhiteLockChanged(locked: boolean) {
if (locked) {
this.info.eyeWhitesLeft = this.info.eyeWhites;
}
}
eyeOpennessChanged(locked: boolean) {
if (locked) {
this.info.eyeOpennessLeft = this.info.eyeOpennessRight;
}
}
eyelashLockChanged(locked: boolean) {
if (locked) {
this.info.eyelashColorLeft = this.info.eyelashColor;
}
}
select(pony: PonyObject | undefined) {
if (pony) {
this.deleting = false;
this.pony = pony;
}
}
setActiveAnimation(index: number) {
this.activeAnimation = index;
this.animationTime = 0;
}
freeOutlinesChanged(_free: boolean) {
this.changed();
}
darkenLockedOutlinesChanged(_darken: boolean) {
this.changed();
}
get canSave() {
return !this.model.pending && !!this.pony && !!this.pony.name && !this.savingLocked;
}
save() {
if (this.canSave) {
this.error = undefined;
this.deleting = false;
this.savingLocked = true;
this.model.savePony(this.pony)
.catch((e: Error) => this.error = e.message)
.then(() => delay(2000))
.then(() => this.savingLocked = false);
}
}
get canRevert() {
return !!findById(this.ponies, this.pony.id);
}
revert() {
if (this.canRevert) {
this.select(findById(this.ponies, this.pony.id));
}
}
get canDuplicate() {
return this.ponies.length < this.model.characterLimit;
}
duplicate() {
if (this.canDuplicate) {
this.deleting = false;
this.pony = cloneDeep(this.pony);
this.pony.name = '';
this.pony.id = '';
}
}
export(index?: number) {
const frameWidth = 80;
const frameHeight = 90;
const animations = index === undefined ? this.animations.map(a => a()) : [this.animations[index]()];
const frames = animations.reduce((sum, a) => sum + a.frames.length, 0);
const info = toPalette(this.info);
const options = defaultDrawPonyOptions();
const canvas = drawCanvas(frameWidth * frames, frameHeight, sprites.paletteSpriteSheet, TRANSPARENT, batch => {
let i = 0;
animations.forEach(a => {
for (let f = 0; f < a.frames.length; f++ , i++) {
const state: PonyState = {
...defaultPonyState(),
animation: a,
animationFrame: f,
blinkFrame: 1,
};
drawPony(batch, info, state, i * frameWidth + frameWidth / 2, frameHeight - 10, options);
}
});
});
const name = animations.length === 1 ? animations[0].name : 'all';
saveCanvas(canvas, `${this.pony.name}-${name}.png`);
}
import() {
if (DEVELOPMENT) {
const data = prompt('enter data');
if (data) {
this.importPony(data);
}
}
}
private importPony(data: string) {
if (DEVELOPMENT) {
this.pony.ponyInfo = decompressPonyString(data, true);
const t = decompressPonyString(data, false);
console.log(JSON.stringify(t, undefined, 2));
}
}
addBlush() {
if (DEVELOPMENT || BETA) {
this.state = {
...this.state,
expression: createExpression(Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush),
};
this.changed();
}
}
testSize() {
function stringifyValues(values: any[]): string {
return values.map(x => JSON.stringify(x)).join(typeof values[0] === 'object' ? ',\n\t' : ', ');
}
if (DEVELOPMENT) {
const compressed = compressPonyString(this.info);
const regularSize = JSON.stringify(this.info).length;
const ponyInfoNumber = decompressPony(compressed);
const precomp = precompressPony(ponyInfoNumber, BLACK, x => x) as any;
const details = Object.keys(precomp)
.filter(key => key !== 'version')
.map(key => ({ key, values: precomp[key] || [] as any[] }))
.map(({ key, values }) => `${key}: [\n\t${stringifyValues(values)}\n]`)
.join(',\n');
const serialized = compressPonyString(this.info);
console.log(serialized);
console.log(details);
console.log(`${serialized.length} / ${regularSize}`);
}
}
testJSON() {
if (DEVELOPMENT) {
console.log(JSON.stringify(this.info, undefined, 2));
}
}
exportPony() {
const data = ponyToExport(this.pony) + '\r\n';
saveAs(new Blob([data], { type: 'text/plain;charset=utf-8' }), `${this.pony.name}.txt`);
}
exportPonies() {
const data = this.ponies.map(ponyToExport).join('\r\n') + '\r\n';
saveAs(new Blob([data], { type: 'text/plain;charset=utf-8' }), 'ponies.txt');
}
async importPonies(file: File | undefined) {
if (file) {
const text = await readFileAsText(file);
const lines = text.split(/\r?\n/g);
let imported = 0;
for (const line of lines) {
try {
const [name, info, desc = ''] = line.split(/\t/g);
if (name && info) {
const pony: PonyObject = {
name,
id: '',
info,
desc,
ponyInfo: decompressPonyString(info, true),
};
await this.model.savePony(pony, true);
imported++;
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
}
alert(`Imported ${imported} ponies`);
}
}
}
function ponyToExport(pony: PonyObject) {
return `${pony.name}\t${pony.info}\t${pony.desc || ''}`.trim();
}