Skip to content

Commit

Permalink
feat(store): show party stat changes from selected items
Browse files Browse the repository at this point in the history
 - helping to determine if a piece of equipment is worth buying
  • Loading branch information
justindujardin committed Oct 27, 2022
1 parent 7f64ce0 commit a1bdf9f
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 22 deletions.
25 changes: 25 additions & 0 deletions src/app/models/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,28 @@ export const getGameInventory = createSelector(
.toList();
}
);

/** Get game Party with equipment objects resolved */
export const getGamePartyWithEquipment = createSelector(
getGameParty,
getEntityItemById,
(party: Immutable.List<Entity>, items: Immutable.Map<string, EntityItemTypes>) => {
return party
.map((entity) => {
if (!entity) {
return null;
}
const result: Partial<EntityWithEquipment> = {
armor: items.get(entity.armor) as ITemplateArmor,
helm: items.get(entity.helm) as ITemplateArmor,
shield: items.get(entity.shield) as ITemplateArmor,
accessory: items.get(entity.accessory) as ITemplateArmor,
boots: items.get(entity.boots) as ITemplateArmor,
weapon: items.get(entity.weapon) as ITemplateWeapon,
};
return Object.assign({}, entity, result) as EntityWithEquipment;
})
.filter((r) => r !== null)
.toList();
}
);
21 changes: 20 additions & 1 deletion src/app/routes/world/map/features/store-feature.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,32 @@ <h1>{{ name$ | async }}</h1>
mat-row
class="item-value"
*matRowDef="let row; columns: ['icon', 'value']"
(click)="toggleRowSelection(row)"
(click)="toggleRowSelection($event, row)"
[class.selected]="(selected$ | async).has(row)"
></tr>
</table>
<h1 *ngIf="!(inventory$ | async).length">No Items</h1>
</div>

<section class="differences">
<div *ngFor="let diff of differences$ | async; trackBy: trackByEid">
<rpg-sprite
[class.disabled]="
diff.diff == '' && !(selling$ | async) && (selected$ | async).size > 0
"
class="image"
[name]="diff.member.icon"
frame="7"
></rpg-sprite>
<strong
*ngIf="diff.diff != '' && !(selling$ | async)"
[class.upgrade]="diff.difference > 0"
[class.downgrade]="diff.difference < 0"
>{{ diff.diff }}</strong
>
</div>
</section>

<footer class="actions">
<button mat-raised-button (click)="close()" color="primary">Exit</button>
<div class="money">
Expand Down
28 changes: 28 additions & 0 deletions src/app/routes/world/map/features/store-feature.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@include pow-dialog();
color: white;
display: block;
user-select: none;
h1 {
margin: auto;
}
Expand All @@ -18,6 +19,33 @@
margin-bottom: 0;
padding-bottom: 15px;
}
.differences {
height: 80px;
display: flex;
flex-direction: row;
justify-content: space-around;
> div {
position: relative;
padding-top: 15px;
rpg-sprite {
display: block;
&.disabled {
opacity: 0.4;
}
}
> strong {
position: absolute;
right: -10px;
top: 0px;
&.upgrade {
color: $gameGold;
}
&.downgrade {
color: #ab3c3c;
}
}
}
}

.inventory {
display: flex;
Expand Down
103 changes: 82 additions & 21 deletions src/app/routes/world/map/features/store-feature.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { BehaviorSubject, from, Observable } from 'rxjs';
import { combineLatest, first, map } from 'rxjs/operators';
import { AppState } from '../../../../app.model';
import { NotificationService } from '../../../../components/notification/notification.service';
import { IPartyMember } from '../../../../models/base-entity';
import {
EntityAddItemAction,
EntityRemoveItemAction,
} from '../../../../models/entity/entity.actions';
import { EntityWithEquipment } from '../../../../models/entity/entity.model';
import {
instantiateEntity,
ITemplateArmor,
Expand All @@ -38,6 +40,7 @@ import { Item } from '../../../../models/item';
import {
getGameInventory,
getGamePartyGold,
getGamePartyWithEquipment,
sliceGameState,
} from '../../../../models/selectors';
import { IScene } from '../../../../scene/scene.model';
Expand Down Expand Up @@ -91,6 +94,14 @@ export function itemInGroups(item: ITemplateBaseItem, groups: string[]): boolean
*/
export type StoreInventoryCategories = 'weapons' | 'armor' | 'magic' | 'misc';

type StoreComparableTypes = ITemplateWeapon | ITemplateArmor | ITemplateMagic;

interface IEquipmentDifference {
member: IPartyMember;
difference: number;
diff: string;
}

@Component({
selector: 'store-feature',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand Down Expand Up @@ -118,8 +129,6 @@ export abstract class StoreFeatureComponent extends TiledFeatureComponent {
super();
}

/** @internal */
private _level$: BehaviorSubject<number> = new BehaviorSubject<number>(1);
/** @internal */
private _weapons$: Observable<ITemplateWeapon[]> = from([WEAPONS_DATA]);
/** @internal */
Expand All @@ -136,23 +145,11 @@ export abstract class StoreFeatureComponent extends TiledFeatureComponent {
*/
name$: Observable<string> = this.feature$.pipe(map(getFeatureProperty('name')));

/**
* The item groups that this vendor sells
*/
groups$: Observable<string[]> = this.feature$.pipe(
map(getFeatureProperty('groups', []))
);

/**
* The amount of gold the party has to spend
*/
partyGold$: Observable<number> = this.store.select(getGamePartyGold);

/**
* The level of items available at this store
*/
level$: Observable<number> = this._level$;

/**
* The items that the party has available to sell to the merchant
*/
Expand Down Expand Up @@ -224,6 +221,59 @@ export abstract class StoreFeatureComponent extends TiledFeatureComponent {
_selected$ = new BehaviorSubject<Set<Item>>(new Set());
/** The selected item to purchase/sell. */
selected$: Observable<Set<Item>> = this._selected$;
/** The currently selected player entity with its equipment resolved to items rather than item ids */
partyWithEquipment$: Observable<Immutable.List<EntityWithEquipment>> =
this.store.select(getGamePartyWithEquipment);

differences$: Observable<IEquipmentDifference[]> = this.selected$.pipe(
combineLatest(
[this.partyWithEquipment$],
(
selected: Set<Item>,
party: Immutable.List<EntityWithEquipment>
): IEquipmentDifference[] => {
let results: IEquipmentDifference[] = [];
if (selected.size != 1) {
results = party.map((pm) => ({ member: pm, difference: 0, diff: '' })).toJS();
} else {
const compareItem = [...selected][0];
results = party
.map((pm: EntityWithEquipment) => {
// TODO: the item types here aren't quite right.
const weapon: ITemplateWeapon = compareItem as any;
const armor: ITemplateArmor = compareItem as any;

// Only compare items we can wield
const usedBy = compareItem.usedby || [];
if (usedBy.indexOf(pm.type) !== -1 || usedBy.length === 0) {
if (pm.weapon && weapon.attack !== undefined) {
return { member: pm, difference: weapon.attack - pm.weapon.attack };
} else if (pm[armor.type] && armor.defense !== undefined) {
return {
member: pm,
difference: armor.defense - pm[armor.type].defense,
};
}
}
return { member: pm, difference: 0, diff: '' };
})
.toJS();
}
return results.map((r) => {
if (!r.hasOwnProperty('diff')) {
if (r.difference > 0) {
r.diff = `+${r.difference}`;
} else if (r.difference < 0) {
r.diff = `-${r.difference}`;
} else {
r.diff = '0';
}
}
return r;
});
}
)
);

isBuying$: Observable<boolean> = this.selected$.pipe(
combineLatest([this.selling$], (selected: Set<Item>, selling: boolean) => {
Expand All @@ -249,14 +299,26 @@ export abstract class StoreFeatureComponent extends TiledFeatureComponent {
this._selected$.next(new Set());
this._selling$.next(false);
}

toggleRowSelection(row) {
if (this._selected$.value.has(row)) {
this._selected$.value.delete(row);
trackByEid(index, item) {
return item.member.id;
}
toggleRowSelection(event, row) {
// Shift for multiple selection
if (event.shiftKey) {
if (this._selected$.value.has(row)) {
this._selected$.value.delete(row);
} else {
this._selected$.value.add(row);
}
this._selected$.next(this._selected$.value);
} else {
this._selected$.value.add(row);
// Toggle selection state
if (this._selected$.value.has(row)) {
this._selected$.next(new Set([]));
} else {
this._selected$.next(new Set([row]));
}
}
this._selected$.next(this._selected$.value);
}

sellItems() {
Expand All @@ -274,7 +336,6 @@ export abstract class StoreFeatureComponent extends TiledFeatureComponent {
this.notify.show(`Sold ${items.length} items for ${totalCost} gold.`, null, 1500);
}
buyItems() {
const items = this._selected$.value;
this.store
.select(sliceGameState)
.pipe(
Expand Down

0 comments on commit a1bdf9f

Please sign in to comment.