Compare commits

...

14 Commits

Author SHA1 Message Date
49e12d7046 Merge pull request 'inventory-slots' (#49) from inventory-slots into main
All checks were successful
E2E Testing / test (push) Successful in 1m35s
Reviewed-on: #49
2024-05-11 15:36:10 +02:00
Sebastian Böckelmann
8fc57d3396 Implement Combinable Inventoryslots
All checks were successful
E2E Testing / test (push) Successful in 1m32s
2024-05-11 15:34:25 +02:00
Sebastian Böckelmann
216d9aec07 Load and Parse Character InventorySlots
All checks were successful
E2E Testing / test (push) Successful in 1m33s
2024-05-11 15:10:57 +02:00
Sebastian Böckelmann
86097a898a Persist Character Inventory Slots
All checks were successful
E2E Testing / test (push) Successful in 1m33s
2024-05-11 14:52:40 +02:00
Sebastian Böckelmann
bc523029fc Specify Requiered Itemgroups for Itemslot
All checks were successful
E2E Testing / test (push) Successful in 1m35s
2024-05-11 11:38:02 +02:00
Sebastian Böckelmann
b70aee9004 Deduplicate of available Characteristics
All checks were successful
E2E Testing / test (push) Successful in 1m33s
2024-05-11 11:08:19 +02:00
Sebastian Böckelmann
1469f2e509 Delete SlotCharacteristics from InventorySlot
All checks were successful
E2E Testing / test (push) Successful in 1m37s
2024-05-11 11:05:00 +02:00
Sebastian Böckelmann
bd243fc080 Validate SlotCharacteristic before finish Editing
All checks were successful
E2E Testing / test (push) Successful in 1m35s
2024-05-11 11:02:37 +02:00
Sebastian Böckelmann
e53d718c17 Edit ItemSlotCharacteristics
All checks were successful
E2E Testing / test (push) Successful in 1m42s
2024-05-11 10:56:17 +02:00
Sebastian Böckelmann
99454753a6 Visualize ItemSlotCharacteristics and add new ones
All checks were successful
E2E Testing / test (push) Successful in 1m42s
2024-05-11 10:54:52 +02:00
Sebastian Böckelmann
4eb428277d Use Table Visualization for InventorySlots
All checks were successful
E2E Testing / test (push) Successful in 1m33s
2024-05-11 09:55:34 +02:00
Sebastian Böckelmann
19057fee93 Delete InventorySlots
All checks were successful
E2E Testing / test (push) Successful in 1m32s
2024-05-11 09:34:21 +02:00
Sebastian Böckelmann
0e954f1b48 Rename Inventoryslots
All checks were successful
E2E Testing / test (push) Successful in 1m36s
2024-05-11 09:32:57 +02:00
Sebastian Böckelmann
82dd597d6d Visualize InventorySlots and Create them
All checks were successful
E2E Testing / test (push) Successful in 1m39s
2024-05-11 09:28:29 +02:00
33 changed files with 864 additions and 22 deletions

View File

@ -215,11 +215,6 @@ export class AppComponent implements OnInit{
gameModel.gamesystems = gamesystems gameModel.gamesystems = gamesystems
gameModel.generateProductSystemContents() gameModel.generateProductSystemContents()
const characterTemplateSystems = gameModel.getTemplateSystems(TemplateType.CHARACTER).map(templateSystem => templateSystem as SimpleTemplateGamesystem)
const characterRelationTemplateSystems = gameModel.getTemplateSystems(TemplateType.CHARACTER_RELATION).map(templateSystem => templateSystem as SimpleTemplateGamesystem)
const characterParser = new CharacterParser(characterTemplateSystems, characterRelationTemplateSystems, gameModel.scriptAccounts);
gameModel.characters = characterParser.parseCharacters(storedGameModel.storedCharacters)
const itemgroupParser = new ItemgroupParser(); const itemgroupParser = new ItemgroupParser();
itemgroupParser.parseItemgroups(storedGameModel.storedItemgroups); itemgroupParser.parseItemgroups(storedGameModel.storedItemgroups);
gameModel.itemgroups = itemgroupParser.getParsedTopItemgroups(); gameModel.itemgroups = itemgroupParser.getParsedTopItemgroups();
@ -227,6 +222,13 @@ export class AppComponent implements OnInit{
const itemParser = new ItemParser(itemgroupParser.getParsedItemgroups()) const itemParser = new ItemParser(itemgroupParser.getParsedItemgroups())
itemParser.parseItems(storedGameModel.storedItems); itemParser.parseItems(storedGameModel.storedItems);
const characterTemplateSystems = gameModel.getTemplateSystems(TemplateType.CHARACTER).map(templateSystem => templateSystem as SimpleTemplateGamesystem)
const characterRelationTemplateSystems = gameModel.getTemplateSystems(TemplateType.CHARACTER_RELATION).map(templateSystem => templateSystem as SimpleTemplateGamesystem)
const characterParser = new CharacterParser(characterTemplateSystems, characterRelationTemplateSystems, gameModel.scriptAccounts, itemgroupParser.getCharacteristics(), itemgroupParser.getParsedItemgroups());
gameModel.characters = characterParser.parseCharacters(storedGameModel.storedCharacters)
this.gameModel = gameModel; this.gameModel = gameModel;
} }

View File

@ -43,7 +43,7 @@ import {
MatColumnDef, MatColumnDef,
MatHeaderCell, MatHeaderCell,
MatHeaderCellDef, MatHeaderCellDef,
MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, MatHeaderRow, MatHeaderRowDef, MatNoDataRow, MatRow, MatRowDef,
MatTable MatTable
} from "@angular/material/table"; } from "@angular/material/table";
import {MatCheckbox} from "@angular/material/checkbox"; import {MatCheckbox} from "@angular/material/checkbox";
@ -91,6 +91,22 @@ import {
import { import {
InheritedItemCharacteristicEditorComponent InheritedItemCharacteristicEditorComponent
} from "./editor/items/item-editor/inherited-item-characteristic-editor/inherited-item-characteristic-editor.component"; } from "./editor/items/item-editor/inherited-item-characteristic-editor/inherited-item-characteristic-editor.component";
import {
InventorySlotEditorComponent
} from "./editor/character-editor/inventory-slot-editor/inventory-slot-editor.component";
import {
InventorySlotCharacteristicEditorComponent
} from "./editor/character-editor/inventory-slot-editor/inventory-slot-characteristic-editor/inventory-slot-characteristic-editor.component";
import {MatAutocomplete, MatAutocompleteTrigger} from "@angular/material/autocomplete";
import {
CharacteristicSelectorComponent
} from "./editor/character-editor/inventory-slot-editor/inventory-slot-characteristic-editor/characteristic-selector/characteristic-selector.component";
import {
RequieredInheritancesEditorComponent
} from "./editor/character-editor/inventory-slot-editor/requiered-inheritances-editor/requiered-inheritances-editor.component";
import {
RequieredInheritancesCreatorComponent
} from "./editor/character-editor/inventory-slot-editor/requiered-inheritances-editor/requiered-inheritances-creator/requiered-inheritances-creator.component";
// AoT requires an exported function for factories // AoT requires an exported function for factories
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json'); const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
@ -120,7 +136,12 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
ItemGroupEditorComponent, ItemGroupEditorComponent,
ItemEditorComponent, ItemEditorComponent,
ItemgroupInheritorComponent, ItemgroupInheritorComponent,
InheritedItemCharacteristicEditorComponent InheritedItemCharacteristicEditorComponent,
InventorySlotEditorComponent,
InventorySlotCharacteristicEditorComponent,
CharacteristicSelectorComponent,
RequieredInheritancesEditorComponent,
RequieredInheritancesCreatorComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -184,7 +205,10 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
MatExpansionPanelTitle, MatExpansionPanelTitle,
MatCardTitle, MatCardTitle,
MatExpansionPanelHeader, MatExpansionPanelHeader,
MatExpansionPanelDescription MatExpansionPanelDescription,
MatAutocomplete,
MatAutocompleteTrigger,
MatNoDataRow
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -56,3 +56,13 @@
</mat-accordion> </mat-accordion>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card>
<mat-card-header>
<mat-card-title>Inventory-Slots</mat-card-title>
</mat-card-header>
<mat-card-content>
<app-inventory-slot-editor [character]="character" [itemgroups]="gameModel!.itemgroupsAsList"></app-inventory-slot-editor>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,12 @@
<mat-form-field class="example-full-width">
<mat-label>Item Characteristic</mat-label>
<input matInput
aria-label="State"
[matAutocomplete]="auto"
[formControl]="myControl">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="onOptionSelected()">
@for (option of filteredOptions | async; track option) {
<mat-option [value]="option.characteristicName">{{option.characteristicName}}</mat-option>
}
</mat-autocomplete>
</mat-form-field>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CharacteristicSelectorComponent } from './characteristic-selector.component';
describe('CharacteristicSelectorComponent', () => {
let component: CharacteristicSelectorComponent;
let fixture: ComponentFixture<CharacteristicSelectorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CharacteristicSelectorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CharacteristicSelectorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,46 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {AsyncPipe} from "@angular/common";
import {FormControl, FormsModule} from "@angular/forms";
import {MatAutocomplete, MatOption} from "@angular/material/autocomplete";
import {MatFormField, MatLabel} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {map, Observable, startWith} from "rxjs";
import {ItemGroupCharacteristic} from "../../../../../project/game-model/inventory/ItemgroupCharacteristic";
@Component({
selector: 'app-characteristic-selector',
templateUrl: './characteristic-selector.component.html',
styleUrl: './characteristic-selector.component.scss'
})
export class CharacteristicSelectorComponent implements OnInit{
myControl = new FormControl('');
@Input() options: ItemGroupCharacteristic[] = []
@Input() selectedInput: ItemGroupCharacteristic | undefined
filteredOptions: Observable<ItemGroupCharacteristic[]>;
@Output('onSelectOption') selectedOptionEmitter: EventEmitter<ItemGroupCharacteristic> = new EventEmitter<ItemGroupCharacteristic>();
constructor() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(state => (state ? this._filterStates(state) : this.options.slice())),
);
}
ngOnInit() {
if(this.selectedInput != undefined) {
this.myControl.setValue(this.selectedInput.characteristicName);
}
}
private _filterStates(value: any): ItemGroupCharacteristic[] {
const filterValue = value.toLowerCase();
return this.options.filter(state => state.characteristicName.toLowerCase().includes(filterValue));
}
onOptionSelected() {
const selectedOption = this.options.find(characteristic => characteristic.characteristicName.toLowerCase() === this.myControl.value!.toLowerCase());
this.selectedOptionEmitter.emit(selectedOption!)
}
}

View File

@ -0,0 +1,45 @@
<table mat-table [dataSource]="datasource" class="mat-elevation-z8">
<ng-container matColumnDef="increasing">
<th mat-header-cell *matHeaderCellDef>Increasing Characteristic</th>
<td mat-cell *matCellDef="let slotCharacteristic">
<span *ngIf="editedCharacteristic !== slotCharacteristic">{{slotCharacteristic.increasingCharacteristic.characteristicName}}</span>
<app-characteristic-selector *ngIf="editedCharacteristic === slotCharacteristic"
[options]="availableCharacteristics"
[selectedInput]="slotCharacteristic.increasingCharacteristic"
(onSelectOption)="onSelectIncreasingCharacteristic($event, slotCharacteristic)">
</app-characteristic-selector>
</td>
</ng-container>
<ng-container matColumnDef="decreasing">
<th mat-header-cell *matHeaderCellDef>Decreasing Characteristic</th>
<td mat-cell *matCellDef="let slotCharacteristic">
<span *ngIf="editedCharacteristic !== slotCharacteristic">{{slotCharacteristic.increasingCharacteristic.characteristicName}}</span>
<app-characteristic-selector *ngIf="editedCharacteristic === slotCharacteristic"
[options]="availableCharacteristics"
[selectedInput]="slotCharacteristic.decreasingCharacteristic"
(onSelectOption)="onSelectDecreasingCharacteristic($event, slotCharacteristic)">
</app-characteristic-selector>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let slotCharacteristic">
<button mat-icon-button (click)="finishEditing()" *ngIf="editedCharacteristic === slotCharacteristic"><mat-icon>done</mat-icon></button>
<button mat-icon-button *ngIf="editedCharacteristic !== slotCharacteristic" [disabled]="editedCharacteristic != null"
(click)="editSlotCharacteristic(slotCharacteristic)"><mat-icon>edit</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef>
<button mat-icon-button (click)="addInventorySlotCharacteristic()"><mat-icon>add</mat-icon></button>
</th>
<td mat-cell *matCellDef="let slotCharacteristic">
<button mat-icon-button color="warn" (click)="deleteSlotCharacteristic(slotCharacteristic)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

View File

@ -0,0 +1,9 @@
.table {
width: 100%;
}
.mat-column-edit, .mat-column-delete {
width: 32px;
align-content: center;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InventorySlotCharacteristicEditorComponent } from './inventory-slot-characteristic-editor.component';
describe('InventorySlotCharacteristicEditorComponent', () => {
let component: InventorySlotCharacteristicEditorComponent;
let fixture: ComponentFixture<InventorySlotCharacteristicEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [InventorySlotCharacteristicEditorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(InventorySlotCharacteristicEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,72 @@
import {Component, Input, OnInit} from '@angular/core';
import {InventorySlot} from "../../../../project/game-model/inventory/intentory-slots/InventorySlot";
import {ItemGroupCharacteristic} from "../../../../project/game-model/inventory/ItemgroupCharacteristic";
import {ItemGroup} from "../../../../project/game-model/inventory/ItemGroup";
import {MatTable, MatTableDataSource} from "@angular/material/table";
import {
InventoryCharacteristic
} from "../../../../project/game-model/inventory/intentory-slots/InventoryCharacteristic";
import {MatSnackBar} from "@angular/material/snack-bar";
@Component({
selector: 'app-inventory-slot-characteristic-editor',
templateUrl: './inventory-slot-characteristic-editor.component.html',
styleUrl: './inventory-slot-characteristic-editor.component.scss'
})
export class InventorySlotCharacteristicEditorComponent implements OnInit{
@Input() inventorySlot: InventorySlot | undefined
@Input() itemgroups: ItemGroup[] = []
availableCharacteristics: ItemGroupCharacteristic[] = []
datasource: MatTableDataSource<InventoryCharacteristic> = new MatTableDataSource<InventoryCharacteristic>();
displayedColumns: string[] = ['increasing', 'decreasing', 'edit', 'delete']
editedCharacteristic: InventoryCharacteristic | null = null;
constructor(private snackbar: MatSnackBar) {
}
ngOnInit() {
this.itemgroups.forEach(itemgroup => {
itemgroup.itemGroupCharacteristics.forEach(characteristic => {
if(!this.availableCharacteristics.includes(characteristic)) {
this.availableCharacteristics.push(characteristic)
}
})
})
this.datasource.data = this.inventorySlot!.slotCharacteristics;
}
addInventorySlotCharacteristic() {
const slotCharacteristic = new InventoryCharacteristic(null, null);
this.inventorySlot!.addSlotCharacteristic(slotCharacteristic);
this.datasource.data = this.inventorySlot!.slotCharacteristics;
this.editedCharacteristic = slotCharacteristic;
}
finishEditing() {
if(this.editedCharacteristic != null && this.editedCharacteristic.isValid()) {
this.editedCharacteristic = null;
} else if(this.editedCharacteristic != null) {
this.snackbar.open("Please select a valid increasing and decreasing Characterstic", "", {duration: 2000});
}
}
onSelectIncreasingCharacteristic(selectedCharacteristic: ItemGroupCharacteristic, slotCharacteristic: InventoryCharacteristic) {
slotCharacteristic.increasingCharacteristic = selectedCharacteristic;
}
onSelectDecreasingCharacteristic(selectedCharacteristic: ItemGroupCharacteristic, slotCharacteristic: InventoryCharacteristic) {
slotCharacteristic.decreasingCharacteristic = selectedCharacteristic;
}
editSlotCharacteristic(slotCharacteristic: InventoryCharacteristic) {
this.editedCharacteristic = slotCharacteristic;
}
deleteSlotCharacteristic(slotCharacteristic: InventoryCharacteristic) {
this.inventorySlot!.slotCharacteristics = this.inventorySlot!.slotCharacteristics.filter(characteristic => characteristic !== slotCharacteristic);
this.datasource.data = this.inventorySlot!.slotCharacteristics;
}
}

View File

@ -0,0 +1,76 @@
<table mat-table [dataSource]="dataSource" multiTemplateDataRows class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Slot Name</th>
<td mat-cell *matCellDef="let slot">
<span *ngIf="slot !== editedSlot">
{{slot.slotName}}
</span>
<mat-form-field appearance="fill" class="long-form" *ngIf="slot === editedSlot">
<mat-label>Slot Name</mat-label>
<input matInput [(ngModel)]="slot.slotName">
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="combinable">
<th mat-header-cell *matHeaderCellDef>Combinable</th>
<td mat-cell *matCellDef="let slot">
<span *ngIf="slot != editedSlot">
<mat-icon *ngIf="slot.combinable">done</mat-icon>
<mat-icon *ngIf="!slot.combinable">close</mat-icon>
</span>
<span *ngIf="slot == editedSlot">
<mat-checkbox [(ngModel)]="slot.combinable"></mat-checkbox>
</span>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let slot">
<button mat-icon-button (click)="editInventorySlot(slot)" *ngIf="editedSlot !== slot" [disabled]="editedSlot != null"><mat-icon>edit</mat-icon></button>
<button mat-icon-button (click)="finishEditing()" *ngIf="editedSlot === slot"><mat-icon>done</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let slot">
<button mat-icon-button (click)="deleteInventorySlot(slot)" color="warn"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="expand">
<th mat-header-cell *matHeaderCellDef aria-label="row actions">
<button mat-icon-button (click)="addInventorySlot()"><mat-icon>add</mat-icon></button>
</th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button aria-label="expand row" (click)="(expandedSlot = expandedSlot === element ? null : element); $event.stopPropagation()">
@if (expandedSlot === element) {
<mat-icon>keyboard_arrow_up</mat-icon>
} @else {
<mat-icon>keyboard_arrow_down</mat-icon>
}
</button>
</td>
</ng-container>
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
<div class="example-element-detail"
[@detailExpand]="element == expandedSlot ? 'expanded' : 'collapsed'">
<app-inventory-slot-characteristic-editor [inventorySlot]="element" [itemgroups]="itemgroups"></app-inventory-slot-characteristic-editor>
<app-requiered-inheritances-editor [inventorySlot]="element" [itemgroups]="itemgroups"></app-requiered-inheritances-editor>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
class="example-element-row"
[class.example-expanded-row]="expandedSlot === element"
(click)="expandedSlot = expandedSlot === element ? null : element">
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>

View File

@ -0,0 +1,58 @@
.panel-actions {
float: right;
}
.add-btn {
width: 100%;
}
table {
width: 100%;
}
tr.example-detail-row {
height: 0;
}
tr.example-element-row:not(.example-expanded-row):hover {
background: #545456;
}
tr.example-element-row:not(.example-expanded-row):active {
background: #4e5157;
}
.example-element-row td {
border-bottom-width: 0;
}
.example-element-detail {
overflow: hidden;
}
.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-edit, .mat-column-delete, .mat-column-expand {
width: 32px;
align-content: center;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InventorySlotEditorComponent } from './inventory-slot-editor.component';
describe('InventorySlotEditorComponent', () => {
let component: InventorySlotEditorComponent;
let fixture: ComponentFixture<InventorySlotEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [InventorySlotEditorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(InventorySlotEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,57 @@
import {Component, Input, OnInit} from '@angular/core';
import {Character} from "../../../project/game-model/characters/Character";
import {ItemGroup} from "../../../project/game-model/inventory/ItemGroup";
import {InventorySlot} from "../../../project/game-model/inventory/intentory-slots/InventorySlot";
import {MatDialog} from "@angular/material/dialog";
import {animate, state, style, transition, trigger} from "@angular/animations";
import {MatTableDataSource} from "@angular/material/table";
@Component({
selector: 'app-inventory-slot-editor',
templateUrl: './inventory-slot-editor.component.html',
styleUrl: './inventory-slot-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 InventorySlotEditorComponent implements OnInit{
@Input() character: Character | undefined
@Input() itemgroups: ItemGroup[] = []
displayedColumns: string[] = ['name','combinable', 'edit', 'delete']
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
dataSource: MatTableDataSource<InventorySlot> = new MatTableDataSource<InventorySlot>();
expandedSlot: InventorySlot | null = null
editedSlot: InventorySlot | null = null
constructor(private dialog: MatDialog) {
}
ngOnInit() {
this.dataSource.data = this.character!.inventorySlots;
}
addInventorySlot() {
this.editedSlot = new InventorySlot("New Inventory Slot");
this.character!.addInventorySlot(this.editedSlot);
this.dataSource.data = this.character!.inventorySlots;
}
editInventorySlot(inventorySlot: InventorySlot) {
this.editedSlot = inventorySlot
}
deleteInventorySlot(inventorySlot: InventorySlot) {
this.character!.removeInventorySlot(inventorySlot);
this.dataSource.data = this.character!.inventorySlots;
}
finishEditing() {
this.editedSlot = null;
}
}

View File

@ -0,0 +1,13 @@
<h1 matDialogTitle>Add Itemgroup Requierement</h1>
<mat-dialog-content>
<mat-form-field appearance="outline" class="long-form">
<mat-label>Select Itemgroup as Requierement</mat-label>
<mat-select [formControl]="selectorCtrl">
<mat-option *ngFor="let option of itemgroups" [value]="option">{{option.componentName}}</mat-option>
</mat-select>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-raised-button (click)="cancel()">Cancel</button>
<button mat-raised-button color="primary" (click)="submit()" [disabled]="selectorCtrl.invalid">Submit</button>
</mat-dialog-actions>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RequieredInheritancesCreatorComponent } from './requiered-inheritances-creator.component';
describe('RequieredInheritancesCreatorComponent', () => {
let component: RequieredInheritancesCreatorComponent;
let fixture: ComponentFixture<RequieredInheritancesCreatorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RequieredInheritancesCreatorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(RequieredInheritancesCreatorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import {Component, Inject} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {ItemGroup} from "../../../../../project/game-model/inventory/ItemGroup";
import {FormControl, Validators} from "@angular/forms";
@Component({
selector: 'app-requiered-inheritances-creator',
templateUrl: './requiered-inheritances-creator.component.html',
styleUrl: './requiered-inheritances-creator.component.scss'
})
export class RequieredInheritancesCreatorComponent {
selectorCtrl = new FormControl('', Validators.required)
constructor(@Inject(MAT_DIALOG_DATA) public itemgroups: ItemGroup[],
private dialogRef: MatDialogRef<RequieredInheritancesCreatorComponent>) {
}
cancel() {
this.dialogRef.close();
}
submit() {
if(this.selectorCtrl.value != undefined) {
this.dialogRef.close(this.selectorCtrl.value);
}
}
}

View File

@ -0,0 +1,25 @@
<table mat-table [dataSource]="datasource" class="mat-elevation-z8">
<ng-container matColumnDef="itemgroupName">
<th mat-header-cell *matHeaderCellDef>Itemgroup Name</th>
<td mat-cell *matCellDef="let requieredInheritence">{{requieredInheritence.componentName}}</td>
</ng-container>
<ng-container matColumnDef="itemgroupDescription">
<th mat-header-cell *matHeaderCellDef>Itemgroup Description</th>
<td mat-cell *matCellDef="let requieredInheritence">{{requieredInheritence.componentDescription}}</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef>
<button mat-icon-button (click)="addInheritance()"><mat-icon>add</mat-icon></button>
</th>
<td mat-cell *matCellDef="let requieredInheritence">
<button mat-icon-button color="warn" (click)="deleteRequieredInheritence(requieredInheritence)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No reqiered Inheritence: Items of all Itemgroups can be added</td>
</tr>
</table>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RequieredInheritancesEditorComponent } from './requiered-inheritances-editor.component';
describe('RequieredInheritancesEditorComponent', () => {
let component: RequieredInheritancesEditorComponent;
let fixture: ComponentFixture<RequieredInheritancesEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RequieredInheritancesEditorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(RequieredInheritancesEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,49 @@
import {Component, Input, OnInit} from '@angular/core';
import {InventorySlot} from "../../../../project/game-model/inventory/intentory-slots/InventorySlot";
import {ItemGroup} from "../../../../project/game-model/inventory/ItemGroup";
import {MatTableDataSource} from "@angular/material/table";
import {MatDialog} from "@angular/material/dialog";
import {
RequieredInheritancesCreatorComponent
} from "./requiered-inheritances-creator/requiered-inheritances-creator.component";
@Component({
selector: 'app-requiered-inheritances-editor',
templateUrl: './requiered-inheritances-editor.component.html',
styleUrl: './requiered-inheritances-editor.component.scss'
})
export class RequieredInheritancesEditorComponent implements OnInit{
@Input() inventorySlot: InventorySlot | undefined
@Input() itemgroups: ItemGroup[] = []
datasource: MatTableDataSource<ItemGroup> = new MatTableDataSource<ItemGroup>();
displayedColumns: string[] = ['itemgroupName', 'itemgroupDescription', 'delete']
constructor(private dialog: MatDialog) {
}
ngOnInit() {
this.datasource.data = this.inventorySlot!.requiredInheritances;
}
addInheritance() {
const dialogRef = this.dialog.open(RequieredInheritancesCreatorComponent, {
data: this.itemgroups,
width: "400px"
})
dialogRef.afterClosed().subscribe(res => {
if(res != undefined) {
console.log("Added")
this.inventorySlot!.addRequieredInheritence(res);
this.datasource.data = this.inventorySlot!.requiredInheritances;
}
})
}
deleteRequieredInheritence(requieredInheritence: ItemGroup) {
this.inventorySlot!.removeRequieredInheritence(requieredInheritence)
this.datasource.data = this.inventorySlot!.requiredInheritances;
}
}

View File

@ -7,19 +7,41 @@ import {SimpleTemplateGamesystem} from "../templates/simpleGamesystem/SimpleTemp
import {ProductTemplateSystem} from "../templates/productGamesystem/ProductTemplateSystem"; import {ProductTemplateSystem} from "../templates/productGamesystem/ProductTemplateSystem";
import {ProductGamesystem} from "../gamesystems/ProductGamesystem"; import {ProductGamesystem} from "../gamesystems/ProductGamesystem";
import {CharacterRelation} from "./CharacterRelation"; import {CharacterRelation} from "./CharacterRelation";
import {InventorySlot} from "../inventory/intentory-slots/InventorySlot";
import {Tupel} from "../../../shared/Tupel";
export class Character extends ModelComponent implements TemplateElement { export class Character extends ModelComponent implements TemplateElement {
characterSpecificTemplateSystems: Gamesystem<any, any>[] = [] characterSpecificTemplateSystems: Gamesystem<any, any>[] = []
characterRelations: CharacterRelation[] = [] characterRelations: CharacterRelation[] = []
characterRelationGamesystems: Gamesystem<any, any>[] = [] characterRelationGamesystems: Gamesystem<any, any>[] = []
inventorySlots: InventorySlot[] = []
combinableInventorySlots: Tupel<InventorySlot, InventorySlot>[] = []
constructor(componentName: string, componentDescription: string) { constructor(componentName: string, componentDescription: string) {
super(componentName, componentDescription, ModelComponentType.CHARACTER); super(componentName, componentDescription, ModelComponentType.CHARACTER);
} }
addInventorySlot(inventorySlot: InventorySlot) {
if(this.inventorySlots.find(slot => slot.slotName === inventorySlot.slotName) == undefined) {
this.inventorySlots.push(inventorySlot);
}
}
removeInventorySlot(removedInventorySlot: InventorySlot) {
this.inventorySlots = this.inventorySlots.filter(slot => removedInventorySlot.slotName !== slot.slotName);
}
addCombinedInventorySlot(combinedSlot: Tupel<InventorySlot, InventorySlot>) {
if(this.combinableInventorySlots.find(tupel => tupel.value00 === combinedSlot.value00 && tupel.value01 === combinedSlot.value01) != undefined) {
this.combinableInventorySlots.push(combinedSlot);
}
}
removeCombinedInventorySlot(combinedSlot: Tupel<InventorySlot, InventorySlot>) {
this.combinableInventorySlots = this.combinableInventorySlots.filter(slots => slots.value00 != combinedSlot.value00 && slots.value01 != combinedSlot.value01);
}
addCharacterSpecificSimpleTemplatesystem(gamesystem: Gamesystem<any, any>, recursiveCall: boolean = false) { addCharacterSpecificSimpleTemplatesystem(gamesystem: Gamesystem<any, any>, recursiveCall: boolean = false) {
if(!this.isTemplateSystemCharacterSpecific(gamesystem.componentName)) { if(!this.isTemplateSystemCharacterSpecific(gamesystem.componentName)) {
if(gamesystem instanceof SimpleTemplateGamesystem) { if(gamesystem instanceof SimpleTemplateGamesystem) {
@ -65,9 +87,6 @@ export class Character extends ModelComponent implements TemplateElement {
this.addAsymetricCharacterRelationGamesystem(gamesystem.parentGamesystem, true) this.addAsymetricCharacterRelationGamesystem(gamesystem.parentGamesystem, true)
} }
} else {
console.log("Was already added")
console.log(this)
} }
} }

View File

@ -0,0 +1,16 @@
import {ItemGroupCharacteristic} from "../ItemgroupCharacteristic";
export class InventoryCharacteristic {
increasingCharacteristic: ItemGroupCharacteristic | null
decreasingCharacteristic: ItemGroupCharacteristic | null
constructor(increasingCharacteristic: ItemGroupCharacteristic | null, decreasingCharacteristic: ItemGroupCharacteristic | null) {
this.increasingCharacteristic = increasingCharacteristic;
this.decreasingCharacteristic = decreasingCharacteristic;
}
isValid() {
return this.increasingCharacteristic != null && this.decreasingCharacteristic != null;
}
}

View File

@ -0,0 +1,34 @@
import {ItemGroupCharacteristic} from "../ItemgroupCharacteristic";
import {InventoryCharacteristic} from "./InventoryCharacteristic";
import {ItemGroup} from "../ItemGroup";
export class InventorySlot {
slotName: string
slotCharacteristics: InventoryCharacteristic[] = []
requiredInheritances: ItemGroup[] = [] //if empty: non reqierements
combinable: boolean = false;
constructor(slotName: string) {
this.slotName = slotName;
}
addSlotCharacteristic(slotCharacteristic: InventoryCharacteristic) {
const found = this.slotCharacteristics.find(characteristiic => characteristiic.increasingCharacteristic === slotCharacteristic.increasingCharacteristic
&& characteristiic.decreasingCharacteristic === slotCharacteristic.decreasingCharacteristic)
if(found == undefined) {
this.slotCharacteristics.push(slotCharacteristic);
}
}
addRequieredInheritence(itemgroup: ItemGroup) {
if(this.requiredInheritances.find(group => group.componentName === itemgroup.componentName) == undefined) {
this.requiredInheritances.push(itemgroup);
}
}
removeRequieredInheritence(itemgroup: ItemGroup) {
this.requiredInheritances = this.requiredInheritances.filter(group => group.componentName !== itemgroup.componentName)
}
}

View File

@ -7,8 +7,9 @@ import {ScriptAccount} from "../../game-model/scriptAccounts/ScriptAccount";
import {SimpleTemplateState} from "../../game-model/templates/simpleGamesystem/SimpleTemplateState"; import {SimpleTemplateState} from "../../game-model/templates/simpleGamesystem/SimpleTemplateState";
import {SimpleTemplateTransition} from "../../game-model/templates/simpleGamesystem/SimpleTemplateTransition"; import {SimpleTemplateTransition} from "../../game-model/templates/simpleGamesystem/SimpleTemplateTransition";
import {CharacterRelation} from "../../game-model/characters/CharacterRelation"; import {CharacterRelation} from "../../game-model/characters/CharacterRelation";
import {load} from "@angular-devkit/build-angular/src/utils/server-rendering/esm-in-memory-loader/loader-hooks"; import {InventorySlotParser} from "./InventorySlotParser";
import {Gamesystem} from "../../game-model/gamesystems/Gamesystem"; import {ItemGroupCharacteristic} from "../../game-model/inventory/ItemgroupCharacteristic";
import {ItemGroup} from "../../game-model/inventory/ItemGroup";
export class CharacterParser { export class CharacterParser {
@ -17,12 +18,14 @@ export class CharacterParser {
characterRelationSpecificGamesystems: SimpleTemplateGamesystem[] characterRelationSpecificGamesystems: SimpleTemplateGamesystem[]
scriptAccountConditionParser: ScriptAccountConditionParser scriptAccountConditionParser: ScriptAccountConditionParser
scriptAccountActionParser: ScriptAccountActionParser scriptAccountActionParser: ScriptAccountActionParser
inventorySlotParser: InventorySlotParser
constructor(characterSpecificGamesystems: SimpleTemplateGamesystem[], characterRelationSpecificGamesystems: SimpleTemplateGamesystem[], scriptAccounts: ScriptAccount[]) { constructor(characterSpecificGamesystems: SimpleTemplateGamesystem[], characterRelationSpecificGamesystems: SimpleTemplateGamesystem[], scriptAccounts: ScriptAccount[], characteristics: ItemGroupCharacteristic[], itemgroups: ItemGroup[]) {
this.characterSpecificGamesystems = characterSpecificGamesystems; this.characterSpecificGamesystems = characterSpecificGamesystems;
this.characterRelationSpecificGamesystems = characterRelationSpecificGamesystems; this.characterRelationSpecificGamesystems = characterRelationSpecificGamesystems;
this.scriptAccountActionParser = new ScriptAccountActionParser(scriptAccounts); this.scriptAccountActionParser = new ScriptAccountActionParser(scriptAccounts);
this.scriptAccountConditionParser = new ScriptAccountConditionParser(scriptAccounts) this.scriptAccountConditionParser = new ScriptAccountConditionParser(scriptAccounts)
this.inventorySlotParser = new InventorySlotParser(characteristics, itemgroups);
} }
@ -46,6 +49,8 @@ export class CharacterParser {
const characterRelationGamesystems = this.parseCharacterSpecificGamesystems(character, characterData.characterRelationGamesystems, this.characterRelationSpecificGamesystems) const characterRelationGamesystems = this.parseCharacterSpecificGamesystems(character, characterData.characterRelationGamesystems, this.characterRelationSpecificGamesystems)
characterRelationGamesystems.forEach(gamesystem => character.addAsymetricCharacterRelationGamesystem(gamesystem)) characterRelationGamesystems.forEach(gamesystem => character.addAsymetricCharacterRelationGamesystem(gamesystem))
character.inventorySlots = this.inventorySlotParser.parseInventorySlots(characterData.inventorySlots);
return character; return character;
} }

View File

@ -0,0 +1,66 @@
import {ItemGroupCharacteristic} from "../../game-model/inventory/ItemgroupCharacteristic";
import {ItemGroup} from "../../game-model/inventory/ItemGroup";
import {InventorySlot} from "../../game-model/inventory/intentory-slots/InventorySlot";
import {InventorySlotSerializer} from "../../serializer/InventorySlotSerializer";
import {InventoryCharacteristic} from "../../game-model/inventory/intentory-slots/InventoryCharacteristic";
export class InventorySlotParser {
private parsedCharacteristics: ItemGroupCharacteristic[]
private parsedItemgroups: ItemGroup[]
constructor(parsedCharacteristics: ItemGroupCharacteristic[], parsedItemgroups: ItemGroup[]) {
this.parsedCharacteristics = parsedCharacteristics;
this.parsedItemgroups = parsedItemgroups;
}
parseInventorySlots(inventorySlotData: any): InventorySlot[] {
const result: InventorySlot[] = []
for(let i=0; i<inventorySlotData.length; i++) {
result.push(this.parseSingleInventorySlot(inventorySlotData[i]))
}
return result;
}
private parseSingleInventorySlot(inventorySlotData: any): InventorySlot {
const slotName = inventorySlotData.slotName;
const slotCharacteristics = this.parseSlotCharacteristics(inventorySlotData.slotCharacteristics)
const inheritedGroups = this.parseRequieredInheritedItemgroups(inventorySlotData.requiredInheritances);
const inventorySlot = new InventorySlot(slotName);
inventorySlot.slotCharacteristics = slotCharacteristics;
inventorySlot.requiredInheritances = inheritedGroups;
inventorySlot.combinable = inventorySlotData.combinable;
return inventorySlot;
}
private parseSlotCharacteristics(slotCharacteristicsData: any): InventoryCharacteristic[] {
const characteristics: InventoryCharacteristic[] = []
for(let i=0; i<slotCharacteristicsData.length; i++) {
const increasingCharacteristic = this.findReferencedCharacteristic(slotCharacteristicsData[i].increasingCharacteristic);
const decreasingCharacteristic = this.findReferencedCharacteristic(slotCharacteristicsData[i].decreasingCharacteristic);
characteristics.push(new InventoryCharacteristic(increasingCharacteristic!, decreasingCharacteristic!))
}
return characteristics;
}
private parseRequieredInheritedItemgroups(inheritanceData: any): ItemGroup[] {
const result: ItemGroup[] = []
for(let i=0; i<inheritanceData.length; i++) {
const inheritedGroup = this.findReferencedItemgroup(inheritanceData[i]);
if(inheritedGroup != undefined) {
result.push(inheritedGroup);
}
}
return result;
}
private findReferencedCharacteristic(characteristicName: string) {
return this.parsedCharacteristics.find(characteristic => characteristic.characteristicName === characteristicName);
}
private findReferencedItemgroup(groupName: string) {
return this.parsedItemgroups.find(group => group.componentName === groupName);
}
}

View File

@ -9,6 +9,7 @@ export class ItemgroupParser {
private parsedItemgroups: ItemGroup[] = [] private parsedItemgroups: ItemGroup[] = []
private topParsedItemgroups: ItemGroup[] = [] private topParsedItemgroups: ItemGroup[] = []
private parsedCharacteristics: ItemGroupCharacteristic[] = []
parseItemgroups(storeComponents: StoreComponent[]) { parseItemgroups(storeComponents: StoreComponent[]) {
const topologicalSort = this.topologicalSort(storeComponents); const topologicalSort = this.topologicalSort(storeComponents);
@ -81,6 +82,7 @@ export class ItemgroupParser {
const characteristic = new ItemGroupCharacteristic(name, description, itemgroup); const characteristic = new ItemGroupCharacteristic(name, description, itemgroup);
characteristic.itemgroup.addItemgroupCharacteristic(characteristic); characteristic.itemgroup.addItemgroupCharacteristic(characteristic);
this.parsedCharacteristics.push(characteristic)
return characteristic; return characteristic;
} }
@ -91,4 +93,8 @@ export class ItemgroupParser {
getParsedItemgroups() { getParsedItemgroups() {
return this.parsedItemgroups; return this.parsedItemgroups;
} }
getCharacteristics() {
return this.parsedCharacteristics;
}
} }

View File

@ -4,6 +4,8 @@ import {SerializeConstants} from "./SerializeConstants";
import {ModelComponentType} from "../game-model/ModelComponentType"; import {ModelComponentType} from "../game-model/ModelComponentType";
import {SimpleTemplateGamesystem} from "../game-model/templates/simpleGamesystem/SimpleTemplateGamesystem"; import {SimpleTemplateGamesystem} from "../game-model/templates/simpleGamesystem/SimpleTemplateGamesystem";
import {Gamesystem} from "../game-model/gamesystems/Gamesystem"; import {Gamesystem} from "../game-model/gamesystems/Gamesystem";
import {ItemGroup} from "../game-model/inventory/ItemGroup";
import {InventorySlotSerializer} from "./InventorySlotSerializer";
export class CharacterSerializer { export class CharacterSerializer {
@ -19,7 +21,6 @@ export class CharacterSerializer {
const fileName = character.componentName const fileName = character.componentName
const templateGamesystemBackup = character.characterSpecificTemplateSystems.concat(); const templateGamesystemBackup = character.characterSpecificTemplateSystems.concat();
character.characterSpecificTemplateSystems = character.characterSpecificTemplateSystems.filter(system => system instanceof SimpleTemplateGamesystem) character.characterSpecificTemplateSystems = character.characterSpecificTemplateSystems.filter(system => system instanceof SimpleTemplateGamesystem)
console.log("Templatesystem: ", character.characterSpecificTemplateSystems)
const jsonString = JSON.stringify(character, (key, value) => { const jsonString = JSON.stringify(character, (key, value) => {
if(value instanceof Gamesystem) { if(value instanceof Gamesystem) {
return { return {
@ -29,14 +30,15 @@ export class CharacterSerializer {
} }
} }
if(key == 'inventorySlots') {
return InventorySlotSerializer.serializeInventorySlots(value)
}
if(key === 'scriptAccount') { if(key === 'scriptAccount') {
return value.componentName return value.componentName
} }
if(key === 'conditionMap' || key === 'actionMap' || key === 'initialMap') { if(key === 'conditionMap' || key === 'actionMap' || key === 'initialMap') {
if(key === 'initialMap') {
console.log(value)
}
if(value.get(character) == undefined) { if(value.get(character) == undefined) {
return [] return []
} }
@ -54,6 +56,8 @@ export class CharacterSerializer {
} }
}, SerializeConstants.JSON_INDENT) }, SerializeConstants.JSON_INDENT)
console.log(jsonString)
character.characterSpecificTemplateSystems = templateGamesystemBackup character.characterSpecificTemplateSystems = templateGamesystemBackup
return new StoreComponent(jsonString, fileName, ModelComponentType.CHARACTER); return new StoreComponent(jsonString, fileName, ModelComponentType.CHARACTER);
} }

View File

@ -0,0 +1,24 @@
import {InventorySlot} from "../game-model/inventory/intentory-slots/InventorySlot";
export class InventorySlotSerializer {
private static ignoredKeys: string[] = ['']
public static serializeInventorySlots(inventorySlots: InventorySlot[]) {
return inventorySlots.map(inventorySlot => {
return {
slotName: inventorySlot.slotName,
slotCharacteristics: inventorySlot.slotCharacteristics.map(slotCharacteristic => {
return {
increasingCharacteristic: slotCharacteristic.increasingCharacteristic!.characteristicName,
decreasingCharacteristic: slotCharacteristic.decreasingCharacteristic!.characteristicName
}
}),
requiredInheritances: inventorySlot.requiredInheritances.map(itemgroup => {
return itemgroup.componentName
}),
combinable: inventorySlot.combinable
}
})
}
}

View File

@ -66,5 +66,7 @@
"generateIsolatedStates": true, "generateIsolatedStates": true,
"symmetric": true "symmetric": true
} }
] ],
"inventorySlots": [],
"combinableInventorySlots": []
} }

View File

@ -66,5 +66,27 @@
"generateIsolatedStates": true, "generateIsolatedStates": true,
"symmetric": true "symmetric": true
} }
] ],
"inventorySlots": [
{
"slotName": "Linke Hand",
"slotCharacteristics": [
{
"increasingCharacteristic": "Test0",
"decreasingCharacteristic": "Test0"
}
],
"requiredInheritances": [
"Clothing"
],
"combinable": true
},
{
"slotName": "Rechte Hand",
"slotCharacteristics": [],
"requiredInheritances": [],
"combinable": true
}
],
"combinableInventorySlots": []
} }