Skip to content

Commit

Permalink
Step editor improvements (#297)
Browse files Browse the repository at this point in the history
* added support for SHOW_MODAL_CHOICE

* made varconditions use the string editor

* fixed step editor from always refreshing events with children

* added support for more types for properties, improved some step previews

* added text color rendering support, SET_MSG_EXPRESSION

* added WM_Type

* added types to getPropString's branches
  • Loading branch information
elluminance authored Feb 23, 2024
1 parent 4b9f349 commit af1cd8f
Show file tree
Hide file tree
Showing 27 changed files with 436 additions and 62 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added
- Added proper support for the event steps `SHOW_MODAL_CHOICE` and `SET_MSG_EXPRESSION`.
- Added rendering of text colors in relevant events (such as `SHOW_MSG`).

### Changed
- `VarConditions` now use the string editor, as opposed to the JSON editor.
- Improved support for event/action step property previews - specifically for `Vec2`, `Vec3`, `Offset`, `Entity`, `EnemyType`, `NumberExpression`, `StringExpression`, `BooleanExpression`, `VarName`, `Effect`, and `Animation`.

### Fixed
- Events with children/branching steps (such as `IF`, `SHOW_CHOICE`, `SHOW_MODAL_CHOICE`, etc.) will no longer fully refresh upon any edits, causing focus to be reset.

## [1.3.1] 2024-02-09
### Changed
- Updated Angular and Electron versions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class EnemyTypeWidgetOverlayComponent extends AbstractWidget implements O
'OTHER'
];
readonly partyAttributes: AttributeValue = {
type: 'string',
type: 'String',
description: '',
withNull: true,
options: this.makeOptions(this.partyOptions)
Expand All @@ -32,7 +32,7 @@ export class EnemyTypeWidgetOverlayComponent extends AbstractWidget implements O
'NORTH_WEST'
];
readonly faceAttributes: AttributeValue = {
type: 'string',
type: 'String',
description: '',
withNull: true,
options: this.makeOptions(this.faceOptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ export class EventDetailComponent implements OnDestroy {
}

public loadEvent(event: AbstractEvent<any>) {
this.event = event;
this.loadSettings();
if(this.event !== event) {
this.event = event;
this.loadSettings();
}
}

private clearSubscriptions() {
Expand Down Expand Up @@ -94,6 +96,7 @@ export class EventDetailComponent implements OnDestroy {
instance.key = key;
instance.attribute = val;
const sub = instance.onChange.subscribe(() => this.update());

this.changeSubscriptions.push(sub);
return instance;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class EventHelperService {
if (valIf.elseStep) {
valIf.elseStep = valIf.elseStep.map((v: EventType) => this.getEventFromType(v, actionStep));
}
} else if (val.type === 'SHOW_CHOICE') {
} else if (val.type === 'SHOW_CHOICE' || val.type === 'SHOW_MODAL_CHOICE') {
const valChoice = val as any;
valChoice.options.forEach((option: any, index: number) => {
valChoice[index] = valChoice[index].map((v: EventType) => this.getEventFromType(v, actionStep));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,157 @@
import { SecurityContext } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { EntityAttributes } from '../../../../services/phaser/entities/cc-entity';
import { Label } from '../../../../models/events';
import { Point, Point3, PartialPoint3 } from '../../../../models/cross-code-map';

export type WMTypeNames = 'Action'
| 'Actor'
| 'Analyzable'
| 'AnimSheetRef'
| 'Animation'
| 'Area'
| 'Array'
| 'AttackInfo'
| 'BallChangerType'
| 'Boolean'
| 'BooleanExpression'
| 'BounceAction'
| 'CallEvent'
| 'Character'
| 'ChoiceOptions'
| 'CollabLabelFilter'
| 'Color'
| 'CombatConditions'
| 'CondAnims'
| 'CreditsTriggerSelect'
| 'DoorVariations'
| 'DropSelect'
| 'DynamicPlatformDests'
| 'Effect'
| 'EffectSelect'
| 'ElevatorDests'
| 'EnemyActionRef'
| 'EnemySearch'
| 'EnemyState'
| 'EnemyType'
| 'EnemyTypeList'
| 'Entity'
| 'EntityAnim'
| 'EntityAnimArray'
| 'Event'
| 'EventLoadCondition'
| 'EventSheetCall'
| 'Face'
| 'FlexibleTable'
| 'GUI'
| 'GUIState'
| 'GuiState'
| 'Image'
| 'Integer'
| 'Item'
| 'ItemsDropRate'
| 'Landmarks'
| 'LangLabel'
| 'LoreEntrySelect'
| 'LoreSelect'
| 'LorryAltTypes'
| 'MagnetAltDirs'
| 'Maps'
| 'Marker'
| 'ModalChoiceOptions'
| 'NPC'
| 'NPCStates'
| 'Number'
| 'NumberExpression'
| 'NumberVary'
| 'Object'
| 'Offset'
| 'OlPlatformStates'
| 'PersonExpression'
| 'PoiFilter'
| 'PropInteract'
| 'PropType'
| 'ProxyRef'
| 'Quest'
| 'QuestHub'
| 'QuestLabelSelect'
| 'QuestNameSelect'
| 'QuestResetSelect'
| 'QuestRewards'
| 'QuestTaskList'
| 'RandomDistribution'
| 'Reaction'
| 'ScalablePropConfig'
| 'Select'
| 'Shield'
| 'Shop'
| 'SoundT'
| 'String'
| 'StringExpression'
| 'TaskIndex'
| 'TileSheet'
| 'Timer'
| 'TrackerRef'
| 'TraderSelect'
| 'TrophySelect'
| 'VarCondition'
| 'VarName'
| 'Vec2'
| 'Vec2Expression'
| 'Vec3'
| 'Vec3Expression'
| 'WalkAnimConfig'
| 'XenoDialog';


export namespace WMTypes {
export type VarExpression<T> =
T |
{indirect: string} |
{varName: string} |
{actorAttrib: string}
;

export type Color = string;

export type Vec2 = Point;
export type Vec2Expression = VarExpression<Vec2>;
export type Vec3 = PartialPoint3 & {lvl?: number};
export type Vec3Expression = VarExpression<Vec3>;

export type NumberExpression = VarExpression<number>;
export type StringExpression = VarExpression<string>;
export type BooleanExpression = VarExpression<boolean>;

export type Offset = Point3;

export type VarName = string | {
actorAttrib?: string;
indirect?: string;
};

export interface Entity {
player?: boolean;
self?: boolean;
name?: string;
varName?: string;
party?: string;
}

export interface EnemyType {
type: string;
}

export interface Effect {
sheet: string;
name: string;
}

export interface Animation {
sheet: string;
name: string;
}
}

export interface EventType {
type: string;
Expand Down Expand Up @@ -53,18 +204,87 @@ export abstract class AbstractEvent<T extends EventType> {
}

protected getPropString(key: string, value?: any): string {
if (!value) {
if (value === undefined) {
value = this.data[key as keyof T];
}
const attr = this.getAttributes();
if (attr && attr[key]) {
const type = attr[key].type;
if (type === 'Color') {
value = this.getColorRectangle(value);
} else if (type === 'Vec2' && value) {
value = this.getVec2String(value.x, value.y);
} else if (type === 'Entity') {
value = '[' + value.name + ']';
switch (type as WMTypeNames) {
case 'Color':
value = this.getColorRectangle(value as WMTypes.Color);
break;
case 'Vec2':
case 'Vec2Expression': {
value = this.getVarExpressionValue(value as WMTypes.Vec2Expression, true);
if(typeof value !== 'string') {
const vec2 = value as WMTypes.Vec2;
value = this.getVec2String(vec2.x, vec2.y);
}
break;
}
case 'Vec3':
case 'Vec3Expression':
value = this.getVarExpressionValue(value as WMTypes.Vec3Expression, true);
if(typeof value !== 'string') {
const vec3 = value as WMTypes.Vec3;
value = this.getVec3String(vec3.x, vec3.y, vec3.z, vec3.lvl);
}
break;
case 'Offset':
if(value) {
const offset = value as WMTypes.Offset;
value = this.getVec3String(offset.x, offset.y, offset.z);
}
break;
case 'Entity': {
const entity = value as WMTypes.Entity;
if(entity.player){
value = 'player';
} else if(entity.self) {
value = 'self';
} else if(entity.name) {
value = '[' + entity.name + ']';
} else if (entity.varName) {
value = `[Var: ${entity.varName}]`;
} else if (entity.party) {
value = `[Party: ${entity.party}]`;
}
break;
}
case 'EnemyType': {
const enemyType = value as WMTypes.EnemyType;
value = '[' + enemyType.type + ']';
break;
}
case 'NumberExpression':
case 'StringExpression':
case 'BooleanExpression': {
const expression = value as WMTypes.NumberExpression
| WMTypes.StringExpression
| WMTypes.BooleanExpression;
value = this.getVarExpressionValue(expression);
break;
}
case 'VarName': {
const varName = value as WMTypes.VarName;
if(typeof varName === 'object') {
if(varName.indirect) {
value = `[indirect: ${value.indirect}]`;
} else if (varName.actorAttrib) {
value = `[actorAttrib: ${value.indirect}]`;
}
}
break;
}
case 'Effect':
case 'Animation':{
const obj = value as WMTypes.Effect | WMTypes.Animation;
if(obj) {
value = `${obj.sheet}/${obj.name}`;
}
break;
}
}
}

Expand All @@ -73,9 +293,30 @@ export abstract class AbstractEvent<T extends EventType> {
return `<span style="display: inline-block;"><span style="color: #858585">${key}</span>: ${value}</span>`;
}

protected getVarExpressionValue<T>(value: WMTypes.VarExpression<T>, supportsActorAttrib = false): T | string {
if(value && typeof value == 'object') {
if('indirect' in value) {
return `[indirect: ${value.indirect}]`;
} else if('varName' in value) {
return `[varName: ${value.varName}]`;
} else if('actorAttrib' in value && supportsActorAttrib) {
return `[actorAttrib: ${value.actorAttrib}]`;
}
}
return value as T;
}

protected getVec2String(x: number, y: number): string {
return `(${this.sanitize(x)}, ${this.sanitize(y)})`;
}

protected getVec3String(x: number, y: number, z?: number, level?: number): string {
if(level !== undefined) {
return `(${this.sanitize(x)}, ${this.sanitize(y)}, lvl ${this.sanitize(level)})`;
} else {
return `(${this.sanitize(x)}, ${this.sanitize(y)}, ${this.sanitize(z!)})`;
}
}

protected getTypeString(color: string): string {
color = this.sanitize(color);
Expand All @@ -93,6 +334,38 @@ export abstract class AbstractEvent<T extends EventType> {
protected getColorRectangle(color: string): string {
return `<span style="background-color: ${this.sanitize(color)};">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>`;
}

protected getProcessedText(langLabel: Label): string {
const textColors = [
null, // \c[0] White
'#ff6969', // \c[1] Red
'#65ff89', // \c[2] Green
'#ffe430', // \c[3] Yellow
'#808080', // \c[4] Gray
//'#ff8932', \c[5] Orange, only used for small font in vanilla
];
let text = langLabel?.en_US ?? '';

let inSpan = false;
text = text.replace(/\\c\[(\d+)\]|$/g, (substr, colorIndex) => {
const color = textColors[+colorIndex];
let replacement = '';
if(inSpan) {
replacement += '</span>';
inSpan = false;
}
if(color) {
replacement += `<span style="color: ${color}">`;
inSpan = true;
} else if (color !== null) {
//preserve the original color code untouched.
replacement += substr;
}
return replacement;
});

return text;
}

private sanitize(val: string | number) {
return this.domSanitizer.sanitize(SecurityContext.HTML, val) || '';
Expand Down
Loading

0 comments on commit af1cd8f

Please sign in to comment.