...
 
Commits (37)
......@@ -3,11 +3,11 @@
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import {AnimationBase} from './animation-base';
import {IAnimationTarget} from './i-animation-target';
import { AnimationBase } from './animation-base';
import { IAnimationTarget } from './i-animation-target';
export class FadeOutAnimation extends AnimationBase {
private fadeRate: number;
private readonly fadeRate: number;
constructor(target: IAnimationTarget, fadeRate: number = 1) {
super(target);
......
......@@ -3,8 +3,12 @@
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import * as PIXI from 'pixi.js';
import Point = PIXI.Point;
export interface IAnimationTarget {
visible: boolean;
alpha: number;
tint?: number;
position: Point;
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { AnimationBase } from './animation-base';
import { IAnimationTarget } from './i-animation-target';
export class MoveAnimation extends AnimationBase {
private readonly moveRate: number;
private readonly xDelta: number;
private readonly yDelta: number;
private readonly maxDelta: number;
private totalDelta: number = 0;
constructor(target: IAnimationTarget, xDelta: number, yDelta: number, moveRate: number = 1, maxDelta: number = 200) {
super(target);
this.moveRate = moveRate;
this.xDelta = xDelta;
this.yDelta = yDelta;
this.maxDelta = maxDelta;
}
public update(delta: number): void {
if (!this.target || delta <= 0 || this.totalDelta > this.maxDelta) {
return;
}
const applyDelta: number = Math.min(delta, this.maxDelta - this.totalDelta);
this.totalDelta += applyDelta;
if (this.xDelta) {
this.target.position.x += this.xDelta * 0.1 * applyDelta;
}
if (this.yDelta) {
this.target.position.y += this.yDelta * 0.1 * applyDelta;
}
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import * as PIXI from 'pixi.js';
import { AnimationBase } from './animation-base';
import { IAnimationTarget } from './i-animation-target';
import Point = PIXI.Point;
export class ProjectileMoveAnimation extends AnimationBase {
private readonly xDelta: number;
private readonly yDelta: number;
private readonly maxDelta: number = 10;
private endPos: PIXI.Point;
private totalDelta: number = 0;
constructor(target: IAnimationTarget, startPos: Point, endPos: Point) {
super(target);
this.endPos = endPos;
target.position.set(startPos.x, startPos.y);
this.xDelta = (endPos.x - startPos.x) / this.maxDelta;
this.yDelta = (endPos.y - startPos.y) / this.maxDelta;
}
public update(delta: number): void {
if (!this.target || delta <= 0 || !this.target.visible) {
return;
}
if (this.totalDelta >= this.maxDelta) {
this.target.visible = false;
return;
}
const applyDelta: number = Math.min(delta, this.maxDelta - this.totalDelta);
this.totalDelta += applyDelta;
if (this.xDelta) {
this.target.position.x += this.xDelta * applyDelta;
}
if (this.yDelta) {
this.target.position.y += this.yDelta * applyDelta;
}
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import * as PIXI from 'pixi.js';
import { CellFlashInstruction } from '../../../libraries/rendering/cell-flash-instruction';
import { RenderInstruction } from '../../../libraries/rendering/render-instruction';
import { Point } from '../../../libraries/world/point';
import { FadeOutAnimation } from '../animation/fade-out-animation';
import { PixiHelpers } from './pixi-helpers';
import { PixiInstructionRenderer } from './pixi-instruction-renderer';
import { PixiRenderingDirector } from './pixi-rendering-director';
export class PixiCellFlashRenderer extends PixiInstructionRenderer {
public getContainer(director: PixiRenderingDirector, instr: RenderInstruction): PIXI.Container {
const container: PIXI.Container = new PIXI.Container();
director.addContainer(container, instr.renderingLayer);
return container;
}
public render(director: PixiRenderingDirector, instr: CellFlashInstruction, container: PIXI.Container): void {
const rect: PIXI.Graphics = new PIXI.Graphics();
container.addChild(rect);
container.position.set(instr.startPixel.x, instr.startPixel.y);
// Add a red flash to indicate something got hurt last turn
const cellRect: PIXI.Graphics = PixiHelpers.buildPixiRectangle(new Point(0, 0), instr.size, true, instr.bgColor);
container.addChild(cellRect);
director.animations.push(new FadeOutAnimation(cellRect, 5));
director.addAnimations(instr, rect, instr.bgColor);
}
}
......@@ -13,9 +13,7 @@ import { SpriteSheetReference } from '../../../libraries/rendering/sprite-sheet-
import { GameObject } from '../../../libraries/world/objects/game-object';
import { Point } from '../../../libraries/world/point';
import { FloorType } from '../../../services/generation-service/floor-type.enum';
import { Logger } from '../../../shared/utility/logger';
import { ColorPulseAnimation } from '../animation/color-pulse-animation';
import { FadeOutAnimation } from '../animation/fade-out-animation';
import { PixiHelpers } from './pixi-helpers';
import { PixiInstructionRenderer } from './pixi-instruction-renderer';
import { PixiRenderingDirector } from './pixi-rendering-director';
......@@ -44,6 +42,69 @@ export class PixiCellRenderer extends PixiInstructionRenderer {
return container;
}
private static getEffectiveTint(tint: string, shouldShade: boolean): string {
let output: string = tint ? tint : MaterialColor.white;
if (shouldShade) {
output = ColorHelper.getFadedColor(output);
}
return output;
}
private drawCursor(director: PixiRenderingDirector, instr: CellRenderInstruction, container: PIXI.Container): void {
// Draw a cursor border if the mouse is over it
this.cursor = PixiHelpers.buildSprite(
director,
new SpriteReference(SpriteSheetReference.small, 3, 2, instr.cursorColor),
instr.size,
instr.cursorColor
);
this.cursor.name = `${instr.id}_cursor`;
this.cursor.alpha = 0.85;
this.cursor.visible = this.isMouseOver;
container.addChild(this.cursor);
const cursorAnim: ColorPulseAnimation = new ColorPulseAnimation(
this.cursor,
ColorHelper.getRGBFromWeb(instr.cursorColor),
ColorHelper.darkenRGB(instr.cursorColor, 0.15)
);
cursorAnim.timePerFrame = 1;
director.animations.push(cursorAnim);
}
private static renderCorruption(instr: CellRenderInstruction, director: PixiRenderingDirector, container: PIXI.Container): void {
if (instr.cell.corruption > 0) {
let corruptX: number;
const corruptY: number = 8;
switch (instr.cell.corruption) {
case 1:
corruptX = 4;
break;
case 2:
corruptX = 5;
break;
default:
corruptX = 6;
}
const sr: SpriteReference = new SpriteReference(
SpriteSheetReference.small,
corruptX,
corruptY,
ColorHelper.getCorruptionColor(instr.cell.corruption, false),
0.5
);
const corruptSprite: PIXI.Sprite = PixiHelpers.buildSprite(director, sr, instr.size, '');
container.addChild(corruptSprite);
}
}
public render(director: PixiRenderingDirector, instr: CellRenderInstruction, container: PIXI.Container): void {
// Set up the tooltip now
if (instr.tooltip) {
......@@ -61,46 +122,49 @@ export class PixiCellRenderer extends PixiInstructionRenderer {
// Render floor
if (instr.cell.spriteReference) {
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(
director,
instr.cell.spriteReference,
instr.size,
this.getEffectiveTint(instr.cell.spriteReference.tint, shouldShade)
);
let tint: string = PixiCellRenderer.getEffectiveTint(instr.cell.spriteReference.tint, shouldShade);
if (instr.visualizer) {
tint = instr.visualizer.getTint(instr, tint);
}
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(director, instr.cell.spriteReference, instr.size, tint);
container.addChild(sprite);
}
// Render corruption, as needed
PixiCellRenderer.renderCorruption(instr, director, container);
// Render objects
for (const obj of instr.cell.objects.slice(0).sort((a: GameObject, b: GameObject): number => a.zIndex - b.zIndex)) {
let effectiveTint: string = obj.spriteReference.tint;
// Add corruption tint
if (obj.corruption > 0) {
effectiveTint = ColorHelper.getCorruptionColor(obj.corruption, false);
}
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(
director,
obj.spriteReference,
instr.size,
this.getEffectiveTint(obj.spriteReference.tint, shouldShade)
PixiCellRenderer.getEffectiveTint(effectiveTint, shouldShade)
);
container.addChild(sprite);
}
// Add a red flash to indicate something got hurt last turn if applicable
if (
instr.context.isVisible &&
instr.cell.actor &&
instr.cell.actor.recentlyHurt &&
instr.context.isInitialRender &&
!instr.isInToolTip
) {
const damageRect: PIXI.Graphics = PixiHelpers.buildPixiRectangle(new Point(0, 0), instr.size, true, MaterialColor.redDark4);
damageRect.name = `${instr.id}_damage_marker`;
container.addChild(damageRect);
director.animations.push(new FadeOutAnimation(damageRect, 5));
}
if (instr.cell.actor && instr.context.isVisible) {
const spriteRef: SpriteReference = instr.cell.actor.spriteReference;
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(director, spriteRef, instr.size, instr.cell.actor.spriteReference.tint);
let effectiveTint: string = instr.cell.actor.spriteReference.tint;
// Add corruption tint
if (instr.cell.actor.corruption > 0) {
effectiveTint = ColorHelper.getCorruptionColor(instr.cell.actor.corruption, false);
}
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(director, spriteRef, instr.size, effectiveTint);
container.addChild(sprite);
if (spriteRef.tint) {
......@@ -122,37 +186,6 @@ export class PixiCellRenderer extends PixiInstructionRenderer {
}
}
private drawCursor(director: PixiRenderingDirector, instr: CellRenderInstruction, container: PIXI.Container): void {
// Draw a cursor border if the mouse is over it
this.cursor = PixiHelpers.buildSprite(
director,
new SpriteReference(SpriteSheetReference.small, 3, 2, instr.cursorColor),
instr.size,
instr.cursorColor
);
this.cursor.name = `${instr.id}_cursor`;
this.cursor.alpha = 0.85;
this.cursor.visible = this.isMouseOver;
container.addChild(this.cursor);
const cursorAnim: ColorPulseAnimation = new ColorPulseAnimation(
this.cursor,
ColorHelper.getRGBFromWeb(instr.cursorColor),
ColorHelper.darkenRGB(instr.cursorColor, 0.15)
);
cursorAnim.timePerFrame = 1;
director.animations.push(cursorAnim);
}
private getEffectiveTint(tint: string, shouldShade: boolean): string {
let output: string = tint ? tint : MaterialColor.white;
if (shouldShade) {
output = ColorHelper.getFadedColor(output);
}
return output;
}
private createContainer(instr: CellRenderInstruction, director: PixiRenderingDirector): PIXI.Container {
const container: PIXI.Container = new PIXI.Container();
container.name = instr.id;
......@@ -181,8 +214,6 @@ export class PixiCellRenderer extends PixiInstructionRenderer {
this.isMouseOver = true;
this.isPressed = false;
Logger.debug(e.data.global, PixiCellRenderer.lastCursor);
if (instr.tooltip && (PixiCellRenderer.lastCursor.x !== e.data.global.x || PixiCellRenderer.lastCursor.y !== e.data.global.y)) {
// Don't just copy over the cursor; we'll want to be sure we use a different reference
PixiCellRenderer.lastCursor.x = e.data.global.x;
......
......@@ -45,8 +45,8 @@ export class PixiHelpers {
};
}
public static getTextStyle(color: string, size: number): TextStyle {
return new TextStyle(this.getTextOptions(color, size));
public static getTextStyle(color: string, size: number, wrapLength: number = 0): TextStyle {
return new TextStyle(this.getTextOptions(color, size, wrapLength));
}
public static addTooltipHandlers(container: PIXI.Container, instr: RenderInstruction, tooltipProvider: ITooltipProvider): void {
......
......@@ -7,10 +7,14 @@ import { environment } from '../../../../environments/environment';
import { RenderInstruction } from '../../../libraries/rendering/render-instruction';
import { IRendererProvider } from './i-renderer-provider';
import { PixiBarRenderer } from './pixi-bar-renderer';
import { PixiCellFlashRenderer } from './pixi-cell-flash-renderer';
import { PixiCellRenderer } from './pixi-cell-renderer';
import { PixiInstructionRenderer } from './pixi-instruction-renderer';
import { PixiLogRenderer } from './pixi-log-renderer';
import { PixiRectangleRenderer } from './pixi-rectangle-renderer';
import { PixiSpriteFlashRenderer } from './pixi-sprite-flash-renderer';
import { PixiSpriteProjectileRenderer } from './pixi-sprite-projectile-renderer';
import { PixiTextCalloutRenderer } from './pixi-text-callout-renderer';
import { PixiTextRenderer } from './pixi-text-renderer';
import { PixiTooltipRenderer } from './pixi-tooltip-renderer';
......@@ -27,6 +31,10 @@ export class PixiInstructionRendererFactory implements IRendererProvider {
this.handlers['LogRenderInstruction'] = (): PixiInstructionRenderer => new PixiLogRenderer();
this.handlers['ProgressBarRenderInstruction'] = (): PixiInstructionRenderer => new PixiBarRenderer();
this.handlers['TextRenderInstruction'] = (): PixiInstructionRenderer => new PixiTextRenderer();
this.handlers['CellFlashInstruction'] = (): PixiInstructionRenderer => new PixiCellFlashRenderer();
this.handlers['TextCalloutInstruction'] = (): PixiInstructionRenderer => new PixiTextCalloutRenderer();
this.handlers['SpriteProjectileInstruction'] = (): PixiInstructionRenderer => new PixiSpriteProjectileRenderer();
this.handlers['SpriteFlashInstruction'] = (): PixiInstructionRenderer => new PixiSpriteFlashRenderer();
}
}
......
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import * as PIXI from 'pixi.js';
import { RenderInstruction } from '../../../libraries/rendering/render-instruction';
import { SpriteFlashInstruction } from '../../../libraries/rendering/sprite-flash-instruction';
import { FadeOutAnimation } from '../animation/fade-out-animation';
import { PixiHelpers } from './pixi-helpers';
import { PixiInstructionRenderer } from './pixi-instruction-renderer';
import { PixiRenderingDirector } from './pixi-rendering-director';
export class PixiSpriteFlashRenderer extends PixiInstructionRenderer {
public getContainer(director: PixiRenderingDirector, instr: RenderInstruction): PIXI.Container {
const container: PIXI.Container = new PIXI.Container();
director.addContainer(container, instr.renderingLayer);
return container;
}
public render(director: PixiRenderingDirector, instr: SpriteFlashInstruction, container: PIXI.Container): void {
const rect: PIXI.Graphics = new PIXI.Graphics();
container.addChild(rect);
container.position.set(instr.startPixel.x, instr.startPixel.y);
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(director, instr.spriteRef, instr.size);
container.addChild(sprite);
director.animations.push(new FadeOutAnimation(sprite, instr.spriteFadeRate));
director.addAnimations(instr, rect, instr.bgColor);
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import * as PIXI from 'pixi.js';
import { SpriteProjectileInstruction } from '../../../libraries/rendering/sprite-projectile-instruction';
import { Logger } from '../../../shared/utility/logger';
import { ProjectileMoveAnimation } from '../animation/projectile-move-animation';
import { PixiHelpers } from './pixi-helpers';
import { PixiInstructionRenderer } from './pixi-instruction-renderer';
import { PixiRenderingDirector } from './pixi-rendering-director';
export class PixiSpriteProjectileRenderer extends PixiInstructionRenderer {
public getContainer(director: PixiRenderingDirector, instr: SpriteProjectileInstruction): PIXI.Container {
const container: PIXI.Container = new PIXI.Container();
director.addContainer(container, instr.renderingLayer);
return container;
}
public render(director: PixiRenderingDirector, instr: SpriteProjectileInstruction, container: PIXI.Container): void {
Logger.warn(`Rendering sprite projectile`, instr);
// Draw a cursor border if the mouse is over it
const sprite: PIXI.Sprite = PixiHelpers.buildSprite(director, instr.spriteReference, instr.size, instr.bgColor);
sprite.name = `${instr.id}_sprite_projectile`;
sprite.visible = true;
sprite.alpha = 1;
container.addChild(sprite);
const anim: ProjectileMoveAnimation = new ProjectileMoveAnimation(sprite, instr.startPixel, instr.targetPos);
director.animations.push(anim);
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import * as PIXI from 'pixi.js';
import { RenderInstruction } from '../../../libraries/rendering/render-instruction';
import { TextCalloutInstruction } from '../../../libraries/rendering/text-callout-instruction';
import { Point } from '../../../libraries/world/point';
import { FadeOutAnimation } from '../animation/fade-out-animation';
import { MoveAnimation } from '../animation/move-animation';
import { PixiHelpers } from './pixi-helpers';
import { PixiInstructionRenderer } from './pixi-instruction-renderer';
import { PixiRenderingDirector } from './pixi-rendering-director';
export class PixiTextCalloutRenderer extends PixiInstructionRenderer {
public getContainer(director: PixiRenderingDirector, instr: RenderInstruction): PIXI.Container {
const container: PIXI.Container = new PIXI.Container();
director.addContainer(container, instr.renderingLayer);
return container;
}
public render(director: PixiRenderingDirector, instr: TextCalloutInstruction, container: PIXI.Container): void {
const rect: PIXI.Graphics = new PIXI.Graphics();
container.addChild(rect);
container.position.set(instr.startPixel.x, instr.startPixel.y);
if (instr.effect.text) {
const fontSize: number = instr.textSize;
const text: PIXI.Text = new PIXI.Text(instr.effect.text, PixiHelpers.getTextStyle(instr.bgColor, fontSize, instr.wrapLength));
PixiHelpers.centerText(text, new Point(0, -text.height - 5), instr.size);
container.addChild(text);
director.animations.push(new MoveAnimation(text, 0, instr.rate * -2, 15));
director.animations.push(new FadeOutAnimation(text, instr.rate * 2));
}
director.addAnimations(instr, rect, instr.bgColor);
}
}
......@@ -5,6 +5,7 @@
import * as chromatism from 'chromatism';
import { ColourModes, ColourObject } from 'chromatism';
import { MaterialColor } from './material-color.enum';
import RGB = ColourModes.RGB;
export class ColorHelper {
......@@ -84,4 +85,30 @@ export class ColorHelper {
return c.hex;
}
public static getCorruptionColor(corruption: number, isActor: boolean): string {
if (isActor) {
switch (corruption) {
case 3:
return MaterialColor.purpleLight1;
case 2:
return MaterialColor.purpleLight2;
case 1:
return MaterialColor.purpleLight3;
default:
return MaterialColor.white;
}
} else {
switch (corruption) {
case 3:
return MaterialColor.purpleDark3;
case 2:
return MaterialColor.purpleDark2;
case 1:
return MaterialColor.purpleDark1;
default:
return MaterialColor.white;
}
}
}
}
......@@ -112,5 +112,33 @@ export enum MaterialColor {
blueGreyDark3 = '#37474f',
blueGreyDark4 = '#263238',
purple = '#9c27b0'
pinkLight5 = '#fce4ec',
pinkLight4 = '#f8bbd0',
pinkLight3 = '#f48fb1',
pinkLight2 = '#f06292',
pinkLight1 = '#ec407a',
pink = '#e91e63',
pinkDark1 = '#d81b60',
pinkDark2 = '#c2185b',
pinkDark3 = '#ad1457',
pinkDark4 = '#880e4f',
pinkAccent1 = '#ff80ab',
pinkAccent2 = '#ff4081',
pinkAccent3 = '#f50057',
pinkAccent4 = '#c51162',
purpleLight5 = '#f3e5f5',
purpleLight4 = '#e1bee7',
purpleLight3 = '#ce93d8',
purpleLight2 = '#ba68c8',
purpleLight1 = '#ab47bc',
purple = '#9c27b0',
purpleDark1 = '#8e24aa',
purpleDark2 = '#7b1fa2',
purpleDark3 = '#6a1b9a',
purpleDark4 = '#4a148c',
purpleAccent1 = '#ea80fc',
purpleAccent2 = '#e040fb',
purpleAccent3 = '#d500f9',
purpleAccent4 = '#aa00ff'
}
......@@ -3,8 +3,9 @@
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import {Cell} from '../world/cell';
import {TooltipInfo} from './tooltip-info';
import { environment } from '../../../environments/environment';
import { Cell } from '../world/cell';
import { TooltipInfo } from './tooltip-info';
export class CellColorizer {
public getCellTooltip(cell: Cell, isDebugMode: boolean, isVisible: boolean): TooltipInfo {
......@@ -22,13 +23,26 @@ export class CellColorizer {
info.body = `\r\nUnallocated Space`;
}
if (isDebugMode) {
info.title += ` ${cell.pos.toString()}`;
if (cell.actor) {
info.footer = `${cell.actor.id}`;
// Add corruption tags
if (cell.corruption > 0) {
if (cell.corruption >= 3) {
info.body += `\r\nExtremely Corrupted`;
} else if (cell.corruption >= 2) {
info.body += `\r\nHeavily Corrupted`;
} else {
info.body += `\r\nMildly Corrupted`;
}
if (environment.allowDebugMode) {
info.body += ` (${cell.corruption})`;
}
}
// Add Debug info
if (environment.allowDebugMode) {
info.title += ` ${cell.pos.toString()}`;
}
info.renderThumbnail = true;
return info;
......
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { IEffect } from '../../services/core-engine/IEffect';
import { Point } from '../world/point';
import { RenderInstruction } from './render-instruction';
import { RenderingLayer } from './rendering-layer.enum';
export class CellFlashInstruction extends RenderInstruction {
private readonly _effect: IEffect;
constructor(screenPos: Point, size: Point, effect: IEffect, color: string) {
super(screenPos, size, color);
this._effect = effect;
this.renderingLayer = RenderingLayer.level;
}
get effect(): IEffect {
return this._effect;
}
}
......@@ -3,6 +3,7 @@
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { VisualizerBase } from '../../services/visualizerBase';
import { MaterialColor } from '../material-color.enum';
import { Cell } from '../world/cell';
import { ICellInstructionGenerationContext } from './generation/i-cell-instruction-generation-context';
......@@ -13,6 +14,7 @@ export class CellRenderInstruction extends RenderInstruction {
public context: ICellInstructionGenerationContext;
public cell: Cell;
public cursorColor: MaterialColor;
public visualizer: VisualizerBase;
constructor(context: ICellInstructionGenerationContext, cell: Cell) {
super(context.screenPos, context.charSize);
......
......@@ -3,15 +3,14 @@
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import {MaterialColor} from '../material-color.enum';
import {Point} from '../world/point';
import {ButtonInstruction} from './button-instruction';
import {RenderingLayer} from './rendering-layer.enum';
import { MaterialColor } from '../material-color.enum';
import { Point } from '../world/point';
import { ButtonInstruction } from './button-instruction';
import { RenderingLayer } from './rendering-layer.enum';
export class CommandButtonInstruction extends ButtonInstruction {
public hotkeyNumber: string = '?';
public abilityCost: string = null;
public costColor: string = MaterialColor.orange;
public isActive: boolean = null;
constructor(screenPos: Point) {
......
......@@ -3,9 +3,11 @@
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import {Actor} from '../../world/actors/actor';
import {Point} from '../../world/point';
import {CellColorizer} from '../cell-colorizer';
import { IGameState } from '../../../services/core-engine/IGameState';
import { VisualizerBase } from '../../../services/visualizerBase';
import { Actor } from '../../world/actors/actor';
import { Point } from '../../world/point';
import { CellColorizer } from '../cell-colorizer';
export interface ICellInstructionGenerationContext {
colorizer: CellColorizer;
......@@ -17,4 +19,6 @@ export interface ICellInstructionGenerationContext {
cellPos: Point;
screenPos: Point;
charSize: Point;
state: IGameState;
visualizer: VisualizerBase;
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { IEffect } from '../../services/core-engine/IEffect';
import { MaterialColor } from '../material-color.enum';
import { Point } from '../world/point';
import { RenderInstruction } from './render-instruction';
import { RenderingLayer } from './rendering-layer.enum';
import { SpriteReference } from './sprite-reference';
export class SpriteFlashInstruction extends RenderInstruction {
private readonly _effect: IEffect;
private readonly _spriteRef: SpriteReference;
private readonly _spriteFadeRate: number;
constructor(screenPos: Point, size: Point, effect: IEffect, spriteRef: SpriteReference, spriteFadeRate: number) {
super(screenPos, size, MaterialColor.white);
this._effect = effect;
this._spriteRef = spriteRef;
this._spriteFadeRate = spriteFadeRate;
this.renderingLayer = RenderingLayer.level;
}
get effect(): IEffect {
return this._effect;
}
get spriteRef(): SpriteReference {
return this._spriteRef;
}
get spriteFadeRate(): number {
return this._spriteFadeRate;
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { IEffect } from '../../services/core-engine/IEffect';
import { Point } from '../world/point';
import { RenderInstruction } from './render-instruction';
import { RenderingLayer } from './rendering-layer.enum';
import { SpriteReference } from './sprite-reference';
export class SpriteProjectileInstruction extends RenderInstruction {
public readonly targetPos: Point;
public readonly spriteReference: SpriteReference;
private readonly _effect: IEffect;
constructor(screenPos: Point, size: Point, effect: IEffect, targetPos: Point, spriteReference: SpriteReference, color: string) {
super(screenPos, size, color);
this._effect = effect;
this.renderingLayer = RenderingLayer.level;
this.targetPos = targetPos;
this.spriteReference = spriteReference;
}
get effect(): IEffect {
return this._effect;
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { IEffect } from '../../services/core-engine/IEffect';
import { Point } from '../world/point';
import { RenderInstruction } from './render-instruction';
import { RenderingLayer } from './rendering-layer.enum';
export class TextCalloutInstruction extends RenderInstruction {
public readonly rate: number;
public readonly textSize: number;
public readonly wrapLength: number;
private readonly _effect: IEffect;
constructor(screenPos: Point, size: Point, effect: IEffect, color: string, textSize: number = 24, rate: number = 1) {
super(screenPos, size, color);
this._effect = effect;
this.renderingLayer = RenderingLayer.level;
this.rate = rate;
this.textSize = textSize;
this.wrapLength = size.x * 4;
}
get effect(): IEffect {
return this._effect;
}
}
......@@ -17,9 +17,9 @@ export class Actor {
public maxHp: number = 3;
public currentOps: number = 10;
public maxOps: number = 10;
public corruption: number;
public pos: Point = new Point(0, 0);
public recentlyHurt: boolean = false;
public id: string;
public isPlayer: boolean = false;
public typeId: string;
......
......@@ -44,20 +44,20 @@
{
"id": "ACTOR_ANTI_VIRUS",
"spriteSheet": "small",
"spriteX": 11,
"spriteX": 13,
"spriteY": 3
},
{
"id": "ACTOR_DAEMON",
"spriteSheet": "small",
"spriteX": 14,
"spriteY": 3
"spriteX": 11,
"spriteY": 6
},
{
"id": "ACTOR_DEFENDER",
"spriteSheet": "small",
"spriteX": 13,
"spriteY": 3
"spriteX": 11,
"spriteY": 5
},
{
"id": "ACTOR_INSPECTOR",
......@@ -74,8 +74,8 @@
{
"id": "ACTOR_GARBAGE_COLLECTOR",
"spriteSheet": "small",
"spriteX": 11,
"spriteY": 4
"spriteX": 9,
"spriteY": 6
},
{
"id": "ACTOR_BIT",
......@@ -101,6 +101,36 @@
"spriteX": 10,
"spriteY": 3
},
{
"id": "ACTOR_VIRUS",
"spriteSheet": "small",
"spriteX": 15,
"spriteY": 5
},
{
"id": "ACTOR_BUG",
"spriteSheet": "small",
"spriteX": 15,
"spriteY": 6
},
{
"id": "ACTOR_FEATURE",
"spriteSheet": "small",
"spriteX": 9,
"spriteY": 5
},
{
"id": "ACTOR_WORM",
"spriteSheet": "small",
"spriteX": 12,
"spriteY": 5
},
{
"id": "ACTOR_GLITCH",
"spriteSheet": "small",
"spriteX": 11,
"spriteY": 4
},
{
"id": "ACTOR_LOGIC_BOMB",
"spriteSheet": "small",
......
......@@ -4,6 +4,9 @@
*/
export enum GameCommand {
prevDebugView = 'PREV_DEBUG_VIEW',
nextDebugView = 'NEXT_DEBUG_VIEW',
toggleDebugView = 'TOGGLE_DEBUG',
internalCommand = 'INTERNAL_COMMAND',
showInventory = 'SHOW_INVENTORY',
cancel = 'CANCEL',
......
......@@ -17,6 +17,7 @@ export class Cell {
public terrainName: string;
public actor: Actor = null;
public pos: Point = new Point(0, 0);
public corruption: number;
constructor(cell: Cell = null) {
if (cell) {
......
......@@ -185,7 +185,7 @@ export class GameLevel {
this._height = this.maxY - this.minY;
}
public addCell(floorType: FloorType, pos: Point): void {
public addCell(floorType: FloorType, pos: Point): Cell {
// Ensure the row exists
if (!this.rows[pos.y]) {
this.rows[pos.y] = new CellRow();
......@@ -199,5 +199,7 @@ export class GameLevel {
// Add the new object to the data
this.rows[pos.y].cells[pos.x] = cellObj;
return cellObj;
}
}
......@@ -4,6 +4,7 @@
*/
import { IObjectData } from '../../../services/generation-service/i-object-data';
import { Logger } from '../../../shared/utility/logger';
import { SpriteReference } from '../../rendering/sprite-reference';
import { SpriteSheetReference } from '../../rendering/sprite-sheet-reference.enum';
import { GameLevel } from '../game-level';
......@@ -18,6 +19,8 @@ export class CommandPickup extends Pickup {
constructor(level: GameLevel, data: IObjectData) {
super(level, data);
Logger.warn(`Creating Command Pickup`, data);
this.command = data.id;
}
......
......@@ -23,6 +23,7 @@ export abstract class GameObject {
this.data = data;
this.level = level;
}
get name(): string {
......@@ -44,4 +45,8 @@ export abstract class GameObject {
get backgroundColor(): string {
return undefined;
}
get corruption(): number {
return this.data.corruption;
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { IObjectData } from '../../../services/generation-service/i-object-data';
import { Logger } from '../../../shared/utility/logger';
import { SpriteReference } from '../../rendering/sprite-reference';
import { SpriteSheetReference } from '../../rendering/sprite-sheet-reference.enum';
import { GameLevel } from '../game-level';
import { Pickup } from './pickup';
export class GenericPickup extends Pickup {
public command: string;
private readonly spriteRef: SpriteReference;
constructor(level: GameLevel, data: IObjectData) {
super(level, data);
Logger.warn(`Creating Generic Pickup`, data);
switch (data.id) {
case 'GET_HP':
this.spriteRef = new SpriteReference(SpriteSheetReference.small, 6, 5);
break;
case 'GET_OPS':
this.spriteRef = new SpriteReference(SpriteSheetReference.small, 3, 5);
break;
case 'GET_MAXHP':
this.spriteRef = new SpriteReference(SpriteSheetReference.small, 6, 3);
break;
case 'GET_MAXOPS':
this.spriteRef = new SpriteReference(SpriteSheetReference.small, 5, 3);
break;
default:
this.spriteRef = new SpriteReference(SpriteSheetReference.small, 6, 0);
}
}
get spriteReference(): SpriteReference {
return this.spriteRef;
}
}
......@@ -5,7 +5,6 @@
import { Alignment } from '../../../services/generation-service/alignment.enum';
import { IActorData } from '../../../services/generation-service/i-actor-data';
import { Logger } from '../../../shared/utility/logger';
import { MaterialColor } from '../../material-color.enum';
import { SpriteReference } from '../../rendering/sprite-reference';
import { SpriteSheetReference } from '../../rendering/sprite-sheet-reference.enum';
......@@ -18,18 +17,24 @@ export class OperatingSystemCore extends GameObject {
constructor(level: GameLevel, data: IActorData) {
super(level, data);
Logger.debug(`Adding OS Core. Team: ${data.team}`);
this.isCaptured = data.team === Alignment.Player;
}
get spriteReference(): SpriteReference {
return this.isCaptured ? new SpriteReference(SpriteSheetReference.small, 0, 4) : new SpriteReference(SpriteSheetReference.small, 1, 4);
if (this.data.team === Alignment.Player) {
return new SpriteReference(SpriteSheetReference.small, 0, 4);
} else if (this.data.team === Alignment.Virus || this.data.team === Alignment.Bug) {
return new SpriteReference(SpriteSheetReference.small, 2, 4);
} else {
return new SpriteReference(SpriteSheetReference.small, 1, 4);
}
}
get foregroundColor(): string {
if (this.isCaptured) {
if (this.data.team === Alignment.Player) {
return MaterialColor.greenAccent2;
} else if (this.data.team === Alignment.Virus || this.data.team === Alignment.Bug) {
return MaterialColor.purple;
} else {
return MaterialColor.red;
}
......
......@@ -16,7 +16,7 @@ export class ServiceObject extends GameObject {
constructor(level: GameLevel, data: IObjectData) {
super(level, data);
this.isActive = false;
this.isActive = data.state === 'ACTIVE';
}
get foregroundColor(): string {
......@@ -28,6 +28,8 @@ export class ServiceObject extends GameObject {
}
get spriteReference(): SpriteReference {
return this.isActive ? new SpriteReference(SpriteSheetReference.small, 4, 5) : new SpriteReference(SpriteSheetReference.small, 6, 5);
return this.corruption > 0
? new SpriteReference(SpriteSheetReference.small, 8, 0)
: new SpriteReference(SpriteSheetReference.small, 7, 0);
}
}
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { EffectType } from './effect-type.enum';
export interface IEffect {
effect: EffectType;
hexColor: string;
startPos: string;
endPos: string;
text: string;
data: string;
}
......@@ -4,9 +4,11 @@
*/
import { IClientMessage } from './i-client-message';
import { IEffect } from './IEffect';
import { IGameState } from './IGameState';
export interface IGameResponse {
effects: IEffect[];
state: IGameState;
messages: IClientMessage[];
}
......@@ -6,6 +6,7 @@
import { ILevel } from '../generation-service/i-level';
export interface IGameState {
uid: string;
level: ILevel;
/// <summary>
......
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
export enum EffectType {
Damage,
NoDamage,
Destroyed,
Missed,
StabilityRestore,
OpsChanged,
Projectile,
Explosion,
Teleport,
ActivationStart,
ActivationEnd,
CellMarked,
Captured,
HelpText,
PlayerTaunt,
SysTaunt,
VirusTaunt,
SysSecTaunt,
Spawn,
Cleanse
}
......@@ -142,7 +142,7 @@ export class GameLoopService implements IInitializable {
private checkForGameOver(): GameOverInfo {
const state: IGameState = this.worldService.gameState;
if (state.isGameOver) {
if (state && state.isGameOver) {
const info: GameOverInfo = new GameOverInfo();
info.turnsTaken = state.numMoves;
info.damageDealt = state.totalDamageDealt;
......
......@@ -5,6 +5,7 @@
import { EventEmitter, Injectable } from '@angular/core';
import { Observable } from 'rxjs/observable';
import { isNullOrUndefined } from 'util';
import { environment } from '../../../environments/environment';
import { ActorCommand } from '../../libraries/world/actors/commands/actor-command';
import { CommandSlot } from '../../libraries/world/actors/commands/command-slot';
......@@ -17,6 +18,7 @@ import { SignalRService } from '../signal-r.service';
import { MessageService } from '../ui-service/message.service';
import { ClientMessageType } from './client-message-type.enum';
import { CommandType } from './command-type.enum';
import { IEffect } from './IEffect';
import { IGameMoveParameters } from './IGameMoveParameters';
import { IGameResponse } from './IGameResponse';
import { IGameState } from './IGameState';
......@@ -26,16 +28,20 @@ import { INewGameParameters } from './INewGameParameters';
export class GameService {
private _isProcessingCommand: boolean = false;
public readonly effectsLoaded: EventEmitter<IEffect[]>;
public readonly gameStateChanged: EventEmitter<IGameState>;
public readonly isProcessingCommandChanged: EventEmitter<boolean>;
private useSignalR: boolean = true;
private useServerState: boolean = true;
constructor(private httpService: HttpService, private signalRService: SignalRService, private messageService: MessageService) {
this.gameStateChanged = new EventEmitter<IGameState>();
this.effectsLoaded = new EventEmitter<IEffect[]>();
this.isProcessingCommandChanged = new EventEmitter<boolean>();
this.useSignalR = environment.useSignalR;
this.useServerState = environment.useServerState;
}
private state: IGameState;
......@@ -137,25 +143,48 @@ export class GameService {
let observable: Observable<any>;
if (this.useSignalR) {
observable = this.signalRService.send('ProcessMove', parameters);
if (this.useServerState) {
if (this.useSignalR) {
observable = this.signalRService.send('ProcessCommand', parameters.state.uid, parameters.command);
} else {
observable = this.httpService.put(`api/game/${parameters.state.uid}`, parameters.command);
}
} else {
observable = this.httpService.put('api/game', parameters);
if (this.useSignalR) {
observable = this.signalRService.send('ProcessMove', parameters);
} else {
observable = this.httpService.put('api/game', parameters);
}
}
observable.subscribe(
(data: IGameResponse) => {
this.handleGameResponse(data);
// If the response was null, it could be because we are in SignalR / Server State Caching mode and the cached state was missing
// In this case, we'll just need to send the full state again
if (isNullOrUndefined(data) && this.useServerState && this.useSignalR) {
this.useServerState = false;
this.executeCommandAndHandleResponse(parameters);
} else {
this.handleGameResponse(data);
}
},
(err: any) => {
Logger.error(`Error executing move. PUT to api/game returned an error result`, err, parameters);
this.useSignalR = false; // Flip SignalR off in case it was the culprit
this.messageService.showMessage({
messageType: ClientMessageType.Assertion,
message: `Oops! There was a problem responding to your action. We'll get it fixed, but for now, try a different action.`
});
this.isProcessingCommand = false;
this.gameStateChanged.emit(this.state);
Logger.debug('Oh snapple', err);
if (err && err.status && +err.status === 404 && this.useServerState) {
this.useServerState = false;
Logger.warn('Server cached state for the current session was not found. Retrying with full state');
this.executeCommandAndHandleResponse(parameters);
} else {
Logger.error(`Error executing move. PUT to api/game returned an error result`, err, parameters);
this.useSignalR = false; // Flip SignalR off in case it was the culprit
this.messageService.showMessage({
messageType: ClientMessageType.Assertion,
message: `Oops! There was a problem responding to your action. We'll get it fixed, but for now, try a different action.`
});
this.isProcessingCommand = false;
this.gameStateChanged.emit(this.state);
}
}
);
}
......@@ -174,10 +203,15 @@ export class GameService {
}
}
this.effectsLoaded.emit(data.effects);
this.gameStateChanged.emit(data.state);
// Reset our communication preferences back to their server-defaults
this.useServerState = environment.useServerState;
} else {
Logger.warn(`Response did not contain any state`);
this.effectsLoaded.emit(null);
this.gameStateChanged.emit(null);
}
......
......@@ -19,14 +19,16 @@ import { MessageService } from '../ui-service/message.service';
import { CommandContextService } from './command-context.service';
import { GameService } from './game.service';
import { IContextComponent } from './i-context-component';
import { IEffect } from './IEffect';
import { IGameState } from './IGameState';
@Injectable()
export class WorldService implements IContextComponent {
private _player: PlayerActor;
private _level: GameLevel;
public gameState: IGameState;
public effects: IEffect[];
constructor(
private levelGenerator: LevelGenerationService,
......@@ -37,6 +39,7 @@ export class WorldService implements IContextComponent {
) {
this.contextService.registerComponent(this);
this.gameService.gameStateChanged.subscribe((state: IGameState) => this.onGameStateChanged(state));
this.gameService.effectsLoaded.subscribe((effects: IEffect[]) => (this.effects = effects));
}
public readonly levelLoaded: EventEmitter<GameLevel> = new EventEmitter<GameLevel>();
......@@ -83,7 +86,7 @@ export class WorldService implements IContextComponent {
this.bestiaryService.initialize();
// Use our level JSON to load the level
LevelGenerationService.addCells(level, levelData.cells, Point.fromPos2D(levelData.upperLeft));
LevelGenerationService.addCells(level, levelData, Point.fromPos2D(levelData.upperLeft));
this.levelGenerator.addObjects(level, levelData);
// Ensure our local representation of the player has the correct position
......
/*
* Copyright (c) 2018 Matt Eland
* Licensed under the Eclipse Public License. See LICENSE file in the project root for full license information.
*/
import { ColorHelper } from '../libraries/color-helper';