Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step editor improvements #297

Merged
merged 11 commits into from
Feb 23, 2024
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 {
Vegita2 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading