Initial commit

This commit is contained in:
Josh Goldberg
2018-05-21 12:40:10 +02:00
commit 550da5ebfc
13 changed files with 727 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
<!--
😍 Hi there! 😍
Thanks for filing an issue on ClassCyclr! ✨
Before going through with this, 🙏 please 🙏 search through open issues to make sure it hasn't already been filed.
If it hasn't, please also fill *either* the "Bug" or "Feature" templates and delete the rest: 👇
-->
<!-- Bug Template:
### Bug Summary
_(what's wrong?)_
### Steps to Reproduce
_(instructions on how someone else can play through the game to cause the bug)_
-->
<!-- Feature Template:
### Feature Request
_(what do you want?)_
_(be as detailed as possible!)_
<!--
Bug example:
### Bug Summary
The player can still walk after a battle cutscene has started.
### Steps to Reproduce
1. Start a game with Pokemon in the player's party
2. Trigger a battle with `FSP.battles.startBattle` (see docs/battles.md)
3. Immediately try to walk - you can, but shouldn't be able to.
-->
<!--
Feature example:
### Feature Request
Can we get a mod that lets the player push people out of the way automtically?
When you're fast-forwarding through the game, it's annoying having to try to move around NPCs.
I'd like it if we the player walking into them would push them out of the way without changing the player's velocity.
Bonus points if they get a little "!" on top of their head. That'd be cute.
-->
+23
View File
@@ -0,0 +1,23 @@
<!--
😍 Hi there! 😍
Thanks for submitting a pull request to ClassCyclr! ✨
Please fill out the following: 👇
-->
### Summary
<!-- _(what does your PR do?)_ -->
Fixes #_(issue number here)_
<!--
For example:
### Summary
Stops the player from being able to walk after a battle starts.
Fixes #123
-->
+23
View File
@@ -0,0 +1,23 @@
dist/
docs/generated/
test/
node_modules/
*.d.ts
*.js*
!./*.js
!*.json
*.html
npm-debug.log
debug.log
# Code coverage
coverage.json
coverage/
# Added by shenanigans-manager for maps testing
Maps.test.ts
# Local development typically uses npm install --link
# Package lock files aren't updated by linked installs
package-lock.json
yarn.lock
+4
View File
@@ -0,0 +1,4 @@
node_modules/
test/
*.test.*
npm-debug.log
+12
View File
@@ -0,0 +1,12 @@
language: node_js
node_js:
- "node"
script:
npm run setup && npm run verify:coverage
# Recommended workaround for https://github.com/travis-ci/travis-ci/issues/8836
sudo: required
addons:
chrome: stable
+7
View File
@@ -0,0 +1,7 @@
{
"editor.tabSize": 4,
"editor.trimAutoWhitespace": true,
"tslint.alwaysShowRuleFailuresAsWarnings": true,
"tslint.autoFixOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
+18
View File
@@ -0,0 +1,18 @@
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+65
View File
@@ -0,0 +1,65 @@
<!-- Top -->
# ClassCyclr
[![Greenkeeper badge](https://badges.greenkeeper.io/FullScreenShenanigans/ClassCyclr.svg)](https://greenkeeper.io/)
[![Build Status](https://travis-ci.org/FullScreenShenanigans/ClassCyclr.svg?branch=master)](https://travis-ci.org/FullScreenShenanigans/ClassCyclr)
[![NPM version](https://badge.fury.io/js/classcyclr.svg)](http://badge.fury.io/js/classcyclr)
Cycles through class names using TimeHandlr events.
<!-- /Top -->
Like [Lolex](https://github.com/sinonjs/lolex), but for repeating events and class cycling.
## Usage
### Constructor
```typescript
import { ClassCyclr } from "classcyclr";
import { TimeHandlr } from "timehandlr";
const timeHandler = new TimeHandlr();
const classCycler = new ClassCyclr({ timeHandler });
```
Documentation coming soon<sup>tm</sup>!
<!-- Development -->
## Development
After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo/):
```
git clone https://github.com/<your-name-here>/ClassCyclr
cd ClassCyclr
npm install
npm run setup
npm run verify
```
* `npm run setup` creates a few auto-generated setup files locally.
* `npm run verify` builds, lints, and runs tests.
### Building
```shell
npm run watch
```
Source files are written under `src/` in TypeScript and compile in-place to JavaScript files.
`npm run watch` will directly run the TypeScript compiler on source files in watch mode.
Use it in the background while developing to keep the compiled files up-to-date.
#### Running Tests
```shell
npm run test
```
Tests are written in [Mocha](https://github.com/mochajs/mocha) and [Chai](https://github.com/chaijs/chai).
Their files are written using alongside source files under `src/` and named `*.test.ts?`.
Whenever you add, remove, or rename a `*.test.t*` file under `src/`, `watch` will re-run `npm run test:setup` to regenerate the list of static test files in `test/index.html`.
You can open that file in a browser to debug through the tests.
<!-- Maps -->
<!-- /Maps -->
<!-- /Development -->
+85
View File
@@ -0,0 +1,85 @@
{
"author": {
"email": "joshuakgoldberg@outlook.com",
"name": "Josh Goldberg"
},
"browser": "./src/index.js",
"bugs": {
"url": "https://github.com/FullScreenShenanigans/ClassCyclr/issues"
},
"dependencies": {
"timehandlr": "^0.7.1"
},
"description": "Cycles through class names on using TimeHandlr events.",
"devDependencies": {
"@types/chai": "^4.1.2",
"@types/lodash": "^4.14.99",
"@types/lolex": "^2.1.2",
"@types/mocha": "^5.0.0",
"@types/sinon": "^4.3.1",
"@types/sinon-chai": "^2.7.29",
"chai": "^4.1.2",
"concurrently": "^3.5.1",
"glob": "^7.1.2",
"istanbul": "^0.4.5",
"lolex": "^2.3.2",
"mkdirp": "^0.5.1",
"mocha": "^5.0.5",
"mocha-headless-chrome": "^2.0.0",
"requirejs": "^2.3.5",
"run-for-every-file": "^1.1.0",
"shenanigans-manager": "^0.2.28",
"sinon": "^5.0.5",
"sinon-chai": "^3.0.0",
"tslint": "5.9.1",
"tsutils": "^2.26.1",
"typedoc": "^0.11.1",
"typescript": "^2.8.1",
"watch": "^1.0.2",
"webpack": "^4.5.0",
"webpack-cli": "^2.0.14"
},
"license": "MIT",
"name": "classcyclr",
"repository": {
"type": "git",
"url": "ssh://git@github.com:FullScreenShenanigans/ClassCyclr.git"
},
"scripts": {
"dist": "npm run dist:webpack",
"dist:webpack": "webpack",
"docs": "npm run docs:typedoc",
"docs:typedoc": "typedoc src/ --exclude **/*.d.ts --ignoreCompilerErrors --out docs/generated",
"init": "npm install && npm run setup && npm run verify",
"setup": "npm run setup:dirs && npm run setup:copy && npm run setup:package && npm run setup:readme",
"setup:copy": "npm run setup:copy:default",
"setup:copy:default": "run-for-every-file --dot --src \"node_modules/shenanigans-manager/setup/default/\" --file \"**/*\" --run \"mustache package.json {{src-file}} {{file}}\" --dest \".\" --only-files",
"setup:dirs": "shenanigans-manager ensure-dirs-exist",
"setup:package": "shenanigans-manager hydrate-package-json",
"setup:readme": "shenanigans-manager hydrate-readme",
"src": "npm run src:tsc && npm run src:tslint",
"src:tsc": "tsc -p .",
"src:tslint": "tslint -c tslint.json -p tsconfig.json -t stylish",
"test": "npm run test:setup && npm run test:run",
"test:coverage": "npm run test:coverage:generate-html && npm run test:coverage:instrument && npm run test:coverage:run && npm run test:coverage:report",
"test:coverage:generate-html": "shenanigans-manager generate-test-html --source instrumented",
"test:coverage:instrument": "istanbul instrument src -o instrumented",
"test:coverage:report": "istanbul report html",
"test:coverage:run": "mocha-headless-chrome --coverage coverage.json --file test/index.instrumented.html",
"test:run": "mocha-headless-chrome --file test/index.html",
"test:setup": "npm run test:setup:dir && npm run test:setup:copy && npm run test:setup:html && npm run test:setup:tsc",
"test:setup:copy": "npm run test:setup:copy:default",
"test:setup:copy:default": "run-for-every-file --dot --src \"node_modules/shenanigans-manager/setup/test/\" --file \"**/*\" --run \"mustache package.json {{src-file}} ./test/{{file}}\" --dest \".\" --only-files",
"test:setup:dir": "mkdirp test",
"test:setup:html": "shenanigans-manager generate-test-html",
"test:setup:tsc": "tsc -p test",
"verify": "npm run src && npm run test && npm run dist && npm run docs",
"verify:coverage": "npm run src && npm run test:setup && npm run test:coverage && npm run dist && npm run docs",
"watch": "concurrently \"tsc -p . -w\" --raw \"chokidar src/**/*.test.t* --command \"\"npm run test:setup:html\"\" --silent\" --raw"
},
"shenanigans": {
"name": "ClassCyclr"
},
"types": "./src/index.d.ts",
"version": "0.7.1"
}
+243
View File
@@ -0,0 +1,243 @@
import { INumericCalculator, ITimeHandlr, TimeEvent } from "timehandlr";
import { IClassCalculator, IClassChanger, IClassCyclr, IClassCyclrSettings, IThing, ITimeCycle, ITimeCycleSettings } from "./IClassCyclr";
/**
* Default classAdd Function.
*
* @param elemet The element whose class is being modified.
* @param className The String to be added to the thing's class.
*/
const classAddGeneric = (thing: IThing, className: string): void => {
thing.className += ` ${className}`;
};
/**
* Default classRemove Function.
*
* @param elemen The element whose class is being modified.
* @param className The String to be removed from the thing's class.
*/
const classRemoveGeneric = (thing: IThing, className: string): void => {
thing.className = thing.className.replace(className, "");
};
/**
* Cycles through class names using TimeHandlr events.
*/
export class ClassCyclr implements IClassCyclr {
/**
* Adds a class to a Thing.
*/
private readonly classAdd: IClassChanger;
/**
* Removes a class from a Thing.
*/
private readonly classRemove: IClassChanger;
/**
* Scheduling for dynamically repeating or synchronized events.
*/
private readonly timeHandler: ITimeHandlr;
/**
* Initializes a new instance of the ClassCyclr class.
*
* @param settings Settings to be used for initialization.
*/
public constructor(settings: IClassCyclrSettings) {
this.classAdd = settings.classAdd === undefined
? classAddGeneric
: settings.classAdd;
this.classRemove = settings.classRemove === undefined
? classRemoveGeneric
: settings.classRemove;
this.timeHandler = settings.timeHandler;
}
/**
* Adds a sprite cycle (settings) for a thing, to be referenced by the given
* name in the thing's cycles Object.
*
* @aram thing The object whose class is to be cycled.
* @param settings Container for repetition settings, particularly .length.
* @param name Name of the cycle, to be referenced in the thing's cycles.
* @param timing How long to wait between classes.
*/
public addClassCycle(thing: IThing, settings: ITimeCycleSettings, name: string, timing: number | INumericCalculator): ITimeCycle {
if (thing.cycles === undefined) {
thing.cycles = {};
}
if (name !== undefined) {
this.cancelClassCycle(thing, name);
}
// Immediately run the first class cycle, then return
settings = thing.cycles[name] = this.setClassCycle(thing, settings, timing);
this.cycleClass(thing, settings);
return settings;
}
/**
* Adds a synched sprite cycle (settings) for a thing, to be referenced by
* the given name in the thing's cycles Object, and in tune with all other
* cycles of the same period.
*
* @pram thing The object whose class is to be cycled.
* @param settings Container for repetition settings, particularly .length.
* @param name Name of the cycle, to be referenced in the thing's cycles.
* @param timing How long to wait between classes.
*/
public addClassCycleSynched(thing: IThing, settings: ITimeCycle, name: string, timing: number | INumericCalculator): ITimeCycle {
if (thing.cycles === undefined) {
thing.cycles = {};
}
if (typeof name !== "undefined") {
this.cancelClassCycle(thing, name);
}
// Immediately run the first class cycle, then return
settings = thing.cycles[name] = this.setClassCycle(thing, settings, timing, true);
this.cycleClass(thing, settings);
return settings;
}
/**
* Cancels the class cycle of a thing by finding the cycle under the thing's
* cycles and making it appear to be empty.
*
* @param thing The thing whose cycle is to be cancelled.
* @param name Name of the cycle to be cancelled.
*/
public cancelClassCycle(thing: IThing, name: string): void {
if (thing.cycles === undefined || !(name in thing.cycles)) {
return;
}
const cycle: ITimeCycle = thing.cycles[name];
if (cycle.event !== undefined) {
cycle.event.repeat = 0;
}
delete thing.cycles[name];
}
/**
* Cancels all class cycles of a thing under the thing's sycles.
*
* @param thing Thing whose cycles are to be cancelled.
*/
public cancelAllCycles(thing: IThing): void {
if (thing.cycles === undefined) {
return;
}
for (const name in thing.cycles) {
if (!{}.hasOwnProperty.call(thing.cycles, name)) {
continue;
}
const cycle: ITimeCycle = thing.cycles[name];
cycle.length = 1;
cycle[0] = false;
delete thing.cycles[name];
}
}
/**
* Initialization utility for sprite cycles of things. The settings are
* added t the right time (immediately if not synched, or on a delay if
* synched.
*
* @param ting The object whose class is to be cycled.
* @param settings Container for repetition settings, particularly .length.
* @param timing How often to do the cycle.
* @param synched Whether the animations should be synched to their period.
* @returns The cycle containing settings and the new event.
*/
private setClassCycle(thing: IThing, settings: ITimeCycle, timing: number | INumericCalculator, synched?: boolean): ITimeCycle {
const timingNumber = TimeEvent.runCalculator(timing);
// Start off before the beginning of the cycle
settings.location = settings.oldclass = -1;
// Let the object know to start the cycle when needed
if (synched) {
thing.onThingAdd = (): void => {
settings.event = this.timeHandler.addEventIntervalSynched(
this.cycleClass,
timingNumber,
Infinity,
thing,
settings);
};
} else {
thing.onThingAdd = (): void => {
settings.event = this.timeHandler.addEventInterval(
this.cycleClass,
timingNumber,
Infinity,
thing,
settings);
};
}
// If it should already start, do that
if (thing.placed) {
thing.onThingAdd(thing);
}
return settings;
}
/**
* Moves an object from its current class in the sprite cycle to the next.
* If the next object is === false, or the repeat function returns false,
* stop by rturning true.
*
* @param thig The object whose class is to be cycled.
* @param settings A container for repetition settings, particularly .length.
* @returns Whether the class cycle should stop (normally false).
*/
private readonly cycleClass = (thing: IThing, settings: ITimeCycle): boolean => {
// If anything has been invalidated, return true to stop
if (!thing || !settings || !settings.length || !thing.alive) {
return true;
}
// Get rid of the previous class from settings, if it's a String
if (settings.oldclass !== -1 && typeof settings[settings.oldclass as any] === "string") {
this.classRemove(thing, settings[settings.oldclass as any] as string);
}
// Move to the next location in settings, as a circular list
settings.location = (settings.location = (settings.location || 0) + 1) % settings.length;
// Current is the class, bool, or Function currently added and/or run
const current: boolean | string | IClassCalculator = settings[settings.location];
if (!current) {
return false;
}
const name = current.constructor === Function
? (current as IClassCalculator)(thing, settings)
: current;
settings.oldclass = settings.location;
// Strings are classes to be added directly
if (typeof name === "string") {
this.classAdd(thing, name);
return false;
}
// Truthy non-String names imply a stop is required
return !!name;
}
}
+154
View File
@@ -0,0 +1,154 @@
import { INumericCalculator, ITimeEvent, ITimeHandlr } from "timehandlr";
/**
* Settings to create a class cycling event, commonly as a String[].
*/
export interface ITimeCycleSettings {
/**
* How many class phases should be cycled through.
*/
length: number;
/**
* Each member of the Array-like cycle settings is a status checker,
* className, or Function to generate a className.
*/
[i: number]: boolean | string | IClassCalculator;
}
/**
* Information for a currently cycling time cycle.
*/
export interface ITimeCycle extends ITimeCycleSettings {
/**
* The container event using this cycle.
*/
event?: ITimeEvent;
/**
* Where in the classes this is currently.
*/
location?: number;
/**
* The previous class' index.
*/
oldclass?: number;
}
/**
* A container of cycle events, such as what a Thing will store.
*/
export interface ITimeCycles {
[i: string]: ITimeCycle;
}
/**
* Calculator for a class within a class cycle.
*
* @param args Any arguments.
* @returns Either a className or a value for whether this should stop.
*/
export type IClassCalculator = (thing: IThing, settings: ITimeCycle) => string | boolean;
/**
* General-purpose Function to add or remove a class on a Thing.
*
* @param thing A Thing whose class is to change.
* @param className The class to add or remove.
*/
export type IClassChanger = (thing: IThing, className: string) => void;
/**
* An object that may have classes added or removed, such as in a cycle.
*/
export interface IThing {
/**
* Whether this is capable of animating.
*/
alive?: boolean;
/**
* A summary of this Thing's current visual representation.
*/
className: string;
/**
* Known currently operating cycles, keyed by name.
*/
cycles?: ITimeCycles;
/**
* A callback for when this is added.
*/
onThingAdd?(thing: IThing): void;
/**
* Whether this is ready to have a visual display.
*/
placed?: boolean;
}
/**
* Settings to initialize a new IClassCyclr.
*/
export interface IClassCyclrSettings {
/**
* Adds a class to a Thing (by default, string concatenation).
*/
classAdd?: IClassChanger;
/**
* Removes a class from a Thing (by default, string removal).
*/
classRemove?: IClassChanger;
/**
* Scheduling for dynamically repeating or synchronized events.
*/
timeHandler: ITimeHandlr;
}
/**
* Cycles through class names using TimeHandlr events.
*/
export interface IClassCyclr {
/**
* Adds a sprite cycle (settings) for a thing, to be referenced by the given
* name in the thing's cycles Object.
*
* @aram thing The object whose class is to be cycled.
* @param settings Container for repetition settings, particularly .length.
* @param name Name of the cycle, to be referenced in the thing's cycles.
* @param timing How long to wait between classes.
*/
addClassCycle(thing: IThing, settings: ITimeCycleSettings, name: string, timing: number | INumericCalculator): ITimeCycle;
/**
* Adds a synched sprite cycle (settings) for a thing, to be referenced by
* the given name in the thing's cycles Object, and in tune with all other
* cycles of the same period.
*
* @pram thing The object whose class is to be cycled.
* @param settings Container for repetition settings, particularly .length.
* @param name Name of the cycle, to be referenced in the thing's cycles.
* @param timing How long to wait between classes.
*/
addClassCycleSynched(thing: IThing, settings: ITimeCycle, name: string, timing: number | INumericCalculator): ITimeCycle;
/**
* Cancels the class cycle of a thing by finding the cycle under the thing's
* cycles and making it appear to be empty.
*
* @parm thing The thing whose cycle is to be cancelled.
* @param name Name of the cycle to be cancelled.
*/
cancelClassCycle(thing: IThing, name: string): void;
/**
* Cancels all class cycles of a thing under the thing's sycles.
*
* @para thing Thing whose cycles are to be cancelled.
*/
cancelAllCycles(thing: IThing): void;
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"declaration": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"lib": ["dom", "es2015"],
"module": "amd",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"pretty": true,
"strictNullChecks": true,
"target": "es5"
},
"exclude": [
"dist",
"node_modules"
],
"include": [
"./src/**/*.ts",
"./src/**/*.tsx"
]
}
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "./node_modules/shenanigans-manager/setup/tslint.json",
"linterOptions": {
"exclude": [
"./node_modules/**/*"
]
},
"rules": {
"no-any": false,
"no-dynamic-delete": false,
"no-unsafe-any": false,
"prefer-function-over-method": false,
"strict-boolean-expressions": false,
"strict-type-predicates": false
}
}