diff --git a/angular.json b/angular.json
index d7425fa..9380e3b 100644
--- a/angular.json
+++ b/angular.json
@@ -3,7 +3,8 @@
"cli": {
"schematicCollections": [
"@angular-eslint/schematics"
- ]
+ ],
+ "analytics": false
},
"version": 1,
"newProjectRoot": "projects",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 7daa0e0..8289e18 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -107,6 +107,9 @@ import {
import {
RequieredInheritancesCreatorComponent
} from "./editor/character-editor/inventory-slot-editor/requiered-inheritances-editor/requiered-inheritances-creator/requiered-inheritances-creator.component";
+import {
+ CharacterInteractionEditorComponent
+} from "./editor/character-editor/character-interaction-editor/character-interaction-editor.component";
// AoT requires an exported function for factories
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
@@ -141,75 +144,76 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
InventorySlotCharacteristicEditorComponent,
CharacteristicSelectorComponent,
RequieredInheritancesEditorComponent,
- RequieredInheritancesCreatorComponent
- ],
- imports: [
- BrowserModule,
- FormsModule,
- HttpClientModule,
- CoreModule,
- SharedModule,
- TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useFactory: httpLoaderFactory,
- deps: [HttpClient]
- }
- }),
- BrowserAnimationsModule,
- MatIcon,
- MatToolbar,
- MatButton,
- MatFormField,
- MatInput,
- MatDrawerContainer,
- MatDrawer,
- MatIconButton,
- MatMenuTrigger,
- MatMenu,
- MatMenuItem,
- MatListItem,
- MatActionList,
- MatTabGroup,
- MatTab,
- MatTabLabel,
- MatLabel,
- MatFormField,
- ReactiveFormsModule,
- MatError,
- MatDialogTitle,
- MatDialogContent,
- MatDialogActions,
- MatMiniFabButton,
- MatTreeModule,
- MatTable,
- MatColumnDef,
- MatHeaderCell,
- MatHeaderCellDef,
- MatCellDef,
- MatCell,
- MatHeaderRow,
- MatRow,
- MatHeaderRowDef,
- MatRowDef,
- MatCheckbox,
- MatSelect,
- MatOption,
- MatHint,
- MatTooltip,
- MatCard,
- MatCardContent,
- MatCardHeader,
- MatAccordion,
- MatExpansionPanel,
- MatExpansionPanelTitle,
- MatCardTitle,
- MatExpansionPanelHeader,
- MatExpansionPanelDescription,
- MatAutocomplete,
- MatAutocompleteTrigger,
- MatNoDataRow
+ RequieredInheritancesCreatorComponent,
+ CharacterInteractionEditorComponent
],
+ imports: [
+ BrowserModule,
+ FormsModule,
+ HttpClientModule,
+ CoreModule,
+ SharedModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useFactory: httpLoaderFactory,
+ deps: [HttpClient]
+ }
+ }),
+ BrowserAnimationsModule,
+ MatIcon,
+ MatToolbar,
+ MatButton,
+ MatFormField,
+ MatInput,
+ MatDrawerContainer,
+ MatDrawer,
+ MatIconButton,
+ MatMenuTrigger,
+ MatMenu,
+ MatMenuItem,
+ MatListItem,
+ MatActionList,
+ MatTabGroup,
+ MatTab,
+ MatTabLabel,
+ MatLabel,
+ MatFormField,
+ ReactiveFormsModule,
+ MatError,
+ MatDialogTitle,
+ MatDialogContent,
+ MatDialogActions,
+ MatMiniFabButton,
+ MatTreeModule,
+ MatTable,
+ MatColumnDef,
+ MatHeaderCell,
+ MatHeaderCellDef,
+ MatCellDef,
+ MatCell,
+ MatHeaderRow,
+ MatRow,
+ MatHeaderRowDef,
+ MatRowDef,
+ MatCheckbox,
+ MatSelect,
+ MatOption,
+ MatHint,
+ MatTooltip,
+ MatCard,
+ MatCardContent,
+ MatCardHeader,
+ MatAccordion,
+ MatExpansionPanel,
+ MatExpansionPanelTitle,
+ MatCardTitle,
+ MatExpansionPanelHeader,
+ MatExpansionPanelDescription,
+ MatAutocomplete,
+ MatAutocompleteTrigger,
+ MatNoDataRow
+ ],
providers: [],
bootstrap: [AppComponent]
})
diff --git a/src/app/editor/character-editor/character-editor.component.html b/src/app/editor/character-editor/character-editor.component.html
index a8a7060..d05c0fe 100644
--- a/src/app/editor/character-editor/character-editor.component.html
+++ b/src/app/editor/character-editor/character-editor.component.html
@@ -66,3 +66,12 @@
+
+
+
+ Character Interactions
+
+
+
+
+
diff --git a/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.html b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.html
new file mode 100644
index 0000000..f0c1d8c
--- /dev/null
+++ b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.html
@@ -0,0 +1,109 @@
+
diff --git a/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.scss b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.scss
new file mode 100644
index 0000000..fb17cfc
--- /dev/null
+++ b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.scss
@@ -0,0 +1,55 @@
+table {
+ width: 100%;
+}
+
+tr.example-detail-row {
+ height: 0;
+}
+
+tr.example-element-row:not(.example-expanded-row):hover {
+ background: #4e5157;
+}
+
+tr.example-element-row:not(.example-expanded-row):active {
+ background: #545456;
+}
+
+.example-element-row td {
+ border-bottom-width: 0;
+}
+
+.example-element-detail {
+ overflow: hidden;
+ display: flex;
+}
+
+.example-element-diagram {
+ min-width: 80px;
+ border: 2px solid black;
+ padding: 8px;
+ font-weight: lighter;
+ margin: 8px 0;
+ height: 104px;
+}
+
+.example-element-symbol {
+ font-weight: bold;
+ font-size: 40px;
+ line-height: normal;
+}
+
+.example-element-description {
+ padding: 16px;
+}
+
+.example-element-description-attribution {
+ opacity: 0.5;
+}
+
+.mat-column-delete, .mat-column-edit, .mat-column-expand, .mat-column-sequence {
+ width: 32px;
+}
+
+.warning {
+ color: red;
+}
diff --git a/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.spec.ts b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.spec.ts
new file mode 100644
index 0000000..954b13a
--- /dev/null
+++ b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CharacterInteractionEditorComponent } from './character-interaction-editor.component';
+
+describe('CharacterInteractionEditorComponent', () => {
+ let component: CharacterInteractionEditorComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CharacterInteractionEditorComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(CharacterInteractionEditorComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.ts b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.ts
new file mode 100644
index 0000000..ba5a976
--- /dev/null
+++ b/src/app/editor/character-editor/character-interaction-editor/character-interaction-editor.component.ts
@@ -0,0 +1,78 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {Character} from "../../../project/game-model/characters/Character";
+import {GameModel} from "../../../project/game-model/GameModel";
+import {AbstractInteraction} from "../../../project/game-model/interactions/AbstractInteraction";
+import {MatColumnDef, MatTable, MatTableDataSource} from "@angular/material/table";
+import {InteractionSequences} from "../../../project/game-model/interactions/InteractionSequences";
+import {animate, state, style, transition, trigger} from "@angular/animations";
+import {Interaction} from "../../../project/game-model/interactions/Interaction";
+import {Condition} from "../../../project/game-model/interactions/condition/Condition";
+import {MatSnackBar} from "@angular/material/snack-bar";
+
+@Component({
+ selector: 'app-character-interaction-editor',
+ templateUrl: './character-interaction-editor.component.html',
+ styleUrl: './character-interaction-editor.component.scss',
+ animations: [
+ trigger('detailExpand', [
+ state('collapsed,void', style({height: '0px', minHeight: '0'})),
+ state('expanded', style({height: '*'})),
+ transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
+ ]),
+ ],
+})
+export class CharacterInteractionEditorComponent implements OnInit{
+
+ @Input() character: Character | undefined
+ @Input() gameModel: GameModel | undefined
+
+ displayedColumns: string[] = ['sequence', 'source', 'target', 'label', 'edit', 'delete']
+ columnsToDisplayWithExpand = [... this.displayedColumns, 'expand'];
+ expandedElement: AbstractInteraction | null = null;
+ editedElement: AbstractInteraction | null = null;
+
+ interactionDatasource: MatTableDataSource = new MatTableDataSource();
+
+ constructor(private snackbar: MatSnackBar) {
+ }
+
+
+ ngOnInit() {
+ this.interactionDatasource.data = this.gameModel!.getCharacterInteractionsByCharacter(this.character!);
+ }
+
+ isInteractionSequence(interaction: AbstractInteraction) {
+ return interaction instanceof InteractionSequences
+ }
+
+ addInteraction() {
+ const interaction = new Interaction(this.character!, undefined, "")
+ this.editedElement = interaction;
+
+ const interactions = this.interactionDatasource.data;
+ interactions.push(interaction);
+ this.interactionDatasource.data = interactions;
+ }
+
+ submitInteraction() {
+ if(this.editedElement == undefined) {
+ return;
+ }
+
+ if(this.editedElement!.validate(this.character!)) {
+ this.gameModel!.addCharacterInteraction(this.editedElement);
+ this.editedElement = null;
+ } else {
+ this.snackbar.open("Invalid Interaction", "", {duration: 2000});
+ }
+ }
+
+ deleteInteraction(interaction: AbstractInteraction) {
+ this.gameModel!.removeCharacterInteraction(interaction);
+ this.interactionDatasource.data = this.gameModel!.characterInteractions;
+ }
+
+ editInteraction(interaction: AbstractInteraction) {
+ this.editedElement = interaction;
+ }
+}
diff --git a/src/app/project/game-model/GameModel.ts b/src/app/project/game-model/GameModel.ts
index 2a34fc9..ea5d397 100644
--- a/src/app/project/game-model/GameModel.ts
+++ b/src/app/project/game-model/GameModel.ts
@@ -17,6 +17,7 @@ import {ConcreteItemGroup} from "./inventory/ConcreteItemGroup";
import {Item} from "./inventory/Item";
import {ItemgroupUtilities} from "./utils/ItemgroupUtilities";
import {ItemGroupCharacteristic} from "./inventory/ItemgroupCharacteristic";
+import {AbstractInteraction} from "./interactions/AbstractInteraction";
export class GameModel {
gameModelName: string
@@ -25,6 +26,7 @@ export class GameModel {
scriptAccounts: ScriptAccount[] = [];
characters: Character[] = []
itemgroups: ItemGroup[] = []
+ characterInteractions: AbstractInteraction[] = []
constructor(gameModelName: string) {
this.gameModelName = gameModelName;
@@ -240,4 +242,18 @@ export class GameModel {
}
return false;
}
+
+ getCharacterInteractionsByCharacter(character: Character) {
+ return this.characterInteractions.filter(interaction => interaction.targetCharacter === character || interaction.sourceCharacter === character);
+ }
+
+ addCharacterInteraction(characterInteraction: AbstractInteraction) {
+ if(this.characterInteractions.find(interaction => interaction.equals(characterInteraction)) == undefined) {
+ this.characterInteractions.push(characterInteraction);
+ }
+ }
+
+ removeCharacterInteraction(characterInteraction: AbstractInteraction) {
+ this.characterInteractions = this.characterInteractions.filter(interaction => interaction != characterInteraction);
+ }
}
diff --git a/src/app/project/game-model/interactions/AbstractInteraction.ts b/src/app/project/game-model/interactions/AbstractInteraction.ts
index 94ade42..0c7716f 100644
--- a/src/app/project/game-model/interactions/AbstractInteraction.ts
+++ b/src/app/project/game-model/interactions/AbstractInteraction.ts
@@ -3,16 +3,19 @@ import {Condition} from "./condition/Condition";
export abstract class AbstractInteraction {
sourceCharacter: Character
- targetCharacter: Character
+ targetCharacter: Character | undefined
conditions: Condition[] = []
interactionLabel: string
- constructor(sourceCharacter: Character, targetCharacter: Character, interactionLabel: string) {
+ constructor(sourceCharacter: Character, targetCharacter: Character | undefined, interactionLabel: string) {
this.sourceCharacter = sourceCharacter;
this.targetCharacter = targetCharacter;
this.interactionLabel = interactionLabel;
}
+
+ abstract equals(other: AbstractInteraction): boolean
+ abstract validate(requieredCharacter: Character): boolean
}
diff --git a/src/app/project/game-model/interactions/Interaction.ts b/src/app/project/game-model/interactions/Interaction.ts
index fab9192..d83cbd2 100644
--- a/src/app/project/game-model/interactions/Interaction.ts
+++ b/src/app/project/game-model/interactions/Interaction.ts
@@ -8,7 +8,31 @@ export class Interaction extends AbstractInteraction{
actions: Action[] = []
- constructor(sourceCharacter: Character, targetCharacter: Character, interactionLabel: string) {
+ constructor(sourceCharacter: Character, targetCharacter: Character | undefined, interactionLabel: string) {
super(sourceCharacter, targetCharacter, interactionLabel);
}
+
+ equals(other: AbstractInteraction): boolean {
+ if(!(other instanceof Interaction)) {
+ return false;
+ }
+
+ const equealCharacters = this.sourceCharacter === other.sourceCharacter && this.targetCharacter === other.targetCharacter;
+ const equalLabels = this.interactionLabel === other.interactionLabel;
+ const equalConditions = this.conditions.every(condition => other.conditions.includes(condition)) && other.conditions.every(condition => this.conditions.includes(condition));
+
+ const equalActions = this.actions.every(action => other.actions.includes(action) && other.actions.every(action => this.actions.includes(action)));
+
+ return equealCharacters && equalLabels && equalConditions && equalActions;
+ }
+
+ validate(requieredCharacter: Character): boolean {
+ const validCharacters = this.sourceCharacter == requieredCharacter || this.targetCharacter == requieredCharacter;
+ const validLabel = this.interactionLabel.length > 0;
+ //Todo: Check for contradicting conditions as well as double conditions and actions
+
+ return validCharacters && validLabel;
+ }
+
+
}
diff --git a/src/app/project/game-model/interactions/InteractionSequences.ts b/src/app/project/game-model/interactions/InteractionSequences.ts
index 121e1b4..3670d48 100644
--- a/src/app/project/game-model/interactions/InteractionSequences.ts
+++ b/src/app/project/game-model/interactions/InteractionSequences.ts
@@ -4,10 +4,68 @@ import {Character} from "../characters/Character";
export class InteractionSequences extends AbstractInteraction {
- interactions: Interaction[] = []
+ rootInteraction: InteractionSequenceNode
- constructor(sourceCharacter: Character, targetCharacter: Character, interactionLabel: string) {
- super(sourceCharacter, targetCharacter, interactionLabel);
+ constructor(interaction: Interaction, interactionLabel: string) {
+ super(interaction.sourceCharacter, interaction.targetCharacter, interactionLabel);
+ this.rootInteraction = new InteractionSequenceNode(interaction, []);
+ }
+
+ equals(other: AbstractInteraction): boolean {
+ if(!(other instanceof InteractionSequences)) {
+ return false;
+ }
+
+ const equealCharacters = this.sourceCharacter === other.sourceCharacter && this.targetCharacter === other.targetCharacter;
+ const equalLabels = this.interactionLabel === other.interactionLabel;
+ const equalConditions = this.conditions.every(condition => other.conditions.includes(condition)) && other.conditions.every(condition => this.conditions.includes(condition));
+
+ const equalSequenceTree = this.rootInteraction.equals(other.rootInteraction);
+
+ return equealCharacters && equalLabels && equalConditions && equalSequenceTree;
+ }
+
+ validate(requieredCharacter: Character): boolean {
+ const validCharacters = this.sourceCharacter == requieredCharacter || this.targetCharacter == requieredCharacter;
+ const validLabel = this.interactionLabel.length > 0;
+ const validSequenceTree = this.rootInteraction.validate(requieredCharacter)
+
+ return validCharacters && validLabel && validSequenceTree;
+ }
+
+
+}
+
+class InteractionSequenceNode {
+
+ root: Interaction
+ children: InteractionSequenceNode[] = [];
+
+ equals(other: InteractionSequenceNode) {
+ const equalsRoot = this.root.equals(other.root);
+ const equalsChildren = this.children.every(child => other.children.includes(child) && other.children.every(child => this.children.includes(child)));
+
+ return equalsRoot && equalsChildren;
+ }
+
+ validate(requiredCharacter: Character): boolean {
+ const validateRoot = this.root.validate(requiredCharacter);
+ let validateChildren = true;
+
+ for(let i=0; i