Skip to content

Commit

Permalink
add turn scenario rules, simple scenario rule management, update depe…
Browse files Browse the repository at this point in the history
…ndencies
  • Loading branch information
Lurkars committed Sep 2, 2024
1 parent f6dafa4 commit 0f75014
Show file tree
Hide file tree
Showing 23 changed files with 952 additions and 547 deletions.
15 changes: 15 additions & 0 deletions data/gh/scenarios/51.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@
"scenarioEffect": true
}
]
},
{
"round": "true",
"always": true,
"figures": [
{
"identifier": {
"type": "characterWithSummon",
"name": ".*"
},
"type": "damage",
"value": "2"
}
],
"alwaysApplyTurn": "after"
}
],
"rooms": [
Expand Down
438 changes: 219 additions & 219 deletions package-lock.json

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gloomhavensecretariat",
"version": "0.99.24",
"version": "0.99.25",
"license": "AGPL3",
"description": "Gloomhaven Secretariat is a Gloomhaven/Frosthaven Companion app.",
"homepage": "https://gloomhaven-secretariat.de",
Expand Down Expand Up @@ -75,29 +75,29 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/cdk": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"@angular/service-worker": "^18.2.0",
"@angular/animations": "^18.2.2",
"@angular/cdk": "^18.2.2",
"@angular/common": "^18.2.2",
"@angular/compiler": "^18.2.2",
"@angular/core": "^18.2.2",
"@angular/forms": "^18.2.2",
"@angular/platform-browser": "^18.2.2",
"@angular/platform-browser-dynamic": "^18.2.2",
"@angular/router": "^18.2.2",
"@angular/service-worker": "^18.2.2",
"autocompleter": "^9.3.2",
"leaflet": "^1.9.4",
"mermaid": "^10.9.1",
"ng-in-viewport": "^16.1.0",
"rxjs": "~7.8.1",
"tslib": "^2.6.3",
"tslib": "^2.7.0",
"uuid": "^10.0.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.1",
"@angular/cli": "^18.2.1",
"@angular/compiler-cli": "^18.2.0",
"@angular-devkit/build-angular": "^18.2.2",
"@angular/cli": "^18.2.2",
"@angular/compiler-cli": "^18.2.2",
"@types/d3": "^7.4.3",
"@types/dom-screen-wake-lock": "1.0.3",
"@types/dompurify": "^3.0.5",
Expand Down
4 changes: 3 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ import { FooterComponent } from './ui/footer/footer';
import { HintDialogComponent } from './ui/footer/hint-dialog/hint-dialog';
import { LevelComponent } from './ui/footer/level/level';
import { LevelDialogComponent } from './ui/footer/level/level-dialog';
import { ScenarioRulesDialogComponent } from './ui/footer/scenario-rules/dialog/scenario-rules-dialog';
import { ScenarioRuleComponent } from './ui/footer/scenario-rules/scenario-rule';
import { ScenarioRulesComponent } from './ui/footer/scenario-rules/scenario-rules';
import { ScenarioDialogComponent } from './ui/footer/scenario/dialog/scenario-dialog';
import { StatsListComponent } from './ui/footer/scenario/dialog/stats-list/stats-list';
Expand Down Expand Up @@ -169,7 +171,7 @@ import { TreasuresToolComponent } from './ui/tools/treasures/treasures-tool';
MainMenuComponent, CharacterMenuComponent, MonsterMenuComponent, SettingsMenuComponent, SettingMenuComponent, SettingMenuTitleComponent, DatamanagementMenuComponent, ScenarioMenuComponent, ScenarioChartDialogComponent, ScenarioChartPopupDialog, SectionMenuComponent, ServerMenuComponent, SettingsDebugMenuComponent, UndoDialogComponent, AboutMenuComponent, CampaignMenuComponent,
FooterComponent,
LootComponent, LootDeckComponent, LootRandomItemDialogComponent, LootDeckFullscreenComponent, LootDeckDialogComponent, LootDeckStandaloneComponent, LootApplyDialogComponent,
HintDialogComponent, ScenarioRulesComponent,
HintDialogComponent, ScenarioRulesComponent, ScenarioRuleComponent, ScenarioRulesDialogComponent,
AttackModifierComponent, AttackModifierEffectsComponent, AttackModifierDeckComponent, AttackModifierDeckDialogComponent, AttackModifierDrawComponent, AttackModifierDeckFullscreenComponent, AttackModifierStandaloneComponent,
LevelComponent, LevelDialogComponent,
ScenarioComponent, ScenarioDialogComponent, ScenarioSetupComponent, SectionDialogComponent, ScenarioSummaryComponent, StatsListComponent, ScenarioTreasuresDialogComponent, TreasureLabelComponent, EventEffectsDialog, EventRandomItemDialogComponent, EventRandomScenarioDialogComponent,
Expand Down
4 changes: 4 additions & 0 deletions src/app/game/businesslogic/GameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ export class GameManager {
case "monster":
return figures.filter((figure) => figure instanceof Monster && (!edition || figure.edition == edition) && figure.name.match(name) && (!identifier.marker || figure.entities.some((entity) => entity.marker == identifier.marker)) && (!identifier.tags || identifier.tags.length == 0 || figure.entities.some((entity) => identifier.tags && identifier.tags.every((tag) => entity.tags && entity.tags.indexOf(tag) != -1))));
case "character":
case "characterWithSummon":
return figures.filter((figure) => {
if (figure instanceof Character && !figure.absent && (!edition || figure.edition == edition) && figure.name.match(name) && (!identifier.tags || identifier.tags.length == 0 || identifier.tags && identifier.tags.every((tag) => figure.tags && figure.tags.indexOf(tag) != -1))) {
if (scenarioEffect) {
Expand All @@ -623,6 +624,9 @@ export class GameManager {
if (figure instanceof Monster || figure instanceof ObjectiveContainer) {
return figure.entities;
} else if (figure instanceof Character) {
if (identifier && identifier.type == "characterWithSummon") {
return [figure as Entity, ...figure.summons];
}
return figure as Entity;
} else {
return undefined;
Expand Down
58 changes: 49 additions & 9 deletions src/app/game/businesslogic/RoundManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ export class RoundManager {
if (settingsManager.settings.expireConditions) {
gameManager.entityManager.expireConditions(prevSummon);
}
if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(prevSummon, true);
}
if (settingsManager.settings.applyConditions && (!activeSummon || index > 0)) {
gameManager.entityManager.applyConditionsTurn(prevSummon, figure);
gameManager.entityManager.applyConditionsAfter(prevSummon, figure);
Expand All @@ -298,6 +301,9 @@ export class RoundManager {
if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsTurn(nextSummon, figure);
}
if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(nextSummon);
}
if (nextSummon.dead) {
this.turn(figure);
}
Expand All @@ -319,6 +325,9 @@ export class RoundManager {
if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsTurn(summon, figure);
}
if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(summon);
}
})
}
}
Expand All @@ -329,14 +338,23 @@ export class RoundManager {
}
})

if (settingsManager.settings.applyConditions) {
if (!(figure instanceof Character) || skipSummons) {
gameManager.entityManager.entitiesAll(figure).forEach((entity) => {
if (!(figure instanceof Character) || skipSummons) {
gameManager.entityManager.entitiesAll(figure).forEach((entity) => {
if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsTurn(entity, figure);
})
} else if (!skipSummons && !figure.summons.some((summon) => summon.active)) {
}

if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(entity);
}
})
} else if (!skipSummons && !figure.summons.some((summon) => summon.active)) {
if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsTurn(figure, figure);
}
if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(figure);
}
}

if (figure instanceof Character && (skipSummons || !figure.summons.some((summon) => summon.active))) {
Expand Down Expand Up @@ -397,6 +415,10 @@ export class RoundManager {
if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsAfter(summon, figure);
}

if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(summon, true);
}
}
})
}
Expand All @@ -414,15 +436,33 @@ export class RoundManager {
gameManager.entityManager.applyCondition(figure, figure, ConditionName.heal, true);
}

gameManager.entityManager.entitiesAll(figure).forEach((entity) => {
if (figure instanceof Character) {
if (settingsManager.settings.expireConditions) {
gameManager.entityManager.expireConditions(entity);
gameManager.entityManager.expireConditions(figure);
}

if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsAfter(entity, figure);
gameManager.entityManager.applyConditionsAfter(figure, figure);
}
})

if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(figure, true);
}
} else {
gameManager.entityManager.entitiesAll(figure).forEach((entity) => {
if (settingsManager.settings.expireConditions) {
gameManager.entityManager.expireConditions(entity);
}

if (settingsManager.settings.applyConditions) {
gameManager.entityManager.applyConditionsAfter(entity, figure);
}

if (settingsManager.settings.scenarioRules) {
gameManager.scenarioRulesManager.applyScenarioRulesTurn(entity, true);
}
})
}
}

this.game.elementBoard.forEach((element) => {
Expand Down
39 changes: 33 additions & 6 deletions src/app/game/businesslogic/ScenarioRulesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ export class ScenarioRulesManager {
addScenarioRulesAlways() {
const scenario = this.game.scenario;
if (scenario && scenario.rules) {
scenario.rules.filter((rule) => this.game.round > 0 && rule.always || rule.alwaysApply).forEach((rule) => {
scenario.rules.filter((rule) => this.game.round > 0 && rule.always || rule.alwaysApply || rule.alwaysApplyTurn).forEach((rule) => {
if (rule.alwaysApply && settingsManager.settings.scenarioRulesAutoapply) {
this.applyRule(rule, { "edition": scenario.edition, "scenario": scenario.index, "group": scenario.group, "index": scenario.rules.indexOf(rule), "section": false });
} else {
} else if (!this.game.disgardedScenarioRules.find((identifier) => identifier.edition == scenario.edition && identifier.scenario == scenario.index && identifier.group == scenario.group && identifier.index == scenario.rules.indexOf(rule) && !identifier.section)) {
this.addScenarioRule(scenario, rule, scenario.rules.indexOf(rule), false);
}
})
Expand All @@ -70,11 +70,11 @@ export class ScenarioRulesManager {
if (this.game.sections) {
this.game.sections.forEach((section) => {
if (section.rules) {
section.rules.filter((rule) => this.game.round > 0 && rule.always || rule.alwaysApply).forEach((rule) => {
section.rules.filter((rule) => this.game.round > 0 && rule.always || rule.alwaysApply || rule.alwaysApplyTurn).forEach((rule) => {
if (rule.alwaysApply && settingsManager.settings.scenarioRulesAutoapply) {
this.applyRule(rule, { "edition": section.edition, "scenario": section.index, "group": section.group, "index": section.rules.indexOf(rule), "section": true });
gameManager.game.scenarioRules.splice(gameManager.game.scenarioRules.length - 1, 1);
} else {
} else if (!this.game.disgardedScenarioRules.find((identifier) => identifier.edition == section.edition && identifier.scenario == section.index && identifier.group == section.group && identifier.index == section.rules.indexOf(rule) && identifier.section)) {
this.addScenarioRule(section, rule, section.rules.indexOf(rule), true);
}
})
Expand Down Expand Up @@ -112,6 +112,33 @@ export class ScenarioRulesManager {
})
}


applyScenarioRulesTurn(entity: Entity, after: boolean = false) {
const scenario = this.game.scenario;
let rules: { identifier: ScenarioRuleIdentifier, rule: ScenarioRule }[] = [];
if (scenario && scenario.rules) {
rules.push(...scenario.rules.map((rule, index) => {
const identifier = { "edition": scenario.edition, "scenario": scenario.index, "group": scenario.group, "index": index, "section": false };
return { identifier: identifier, rule: rule };
}).filter((ruleModel) => ruleModel.rule.alwaysApplyTurn && (!after && ruleModel.rule.alwaysApplyTurn == "turn" || after && ruleModel.rule.alwaysApplyTurn == "after") && this.game.appliedScenarioRules.find((applied) => applied.edition == ruleModel.identifier.edition && applied.scenario == ruleModel.identifier.scenario && applied.group == ruleModel.identifier.group && applied.index == ruleModel.identifier.index && applied.section == ruleModel.identifier.section)));
}

if (this.game.sections) {
this.game.sections.forEach((section) => {
if (section.rules) {
rules.push(...section.rules.map((rule, index) => {
const identifier = { "edition": section.edition, "scenario": section.index, "group": section.group, "index": index, "section": true };
return { identifier: identifier, rule: rule };
}).filter((ruleModel) => ruleModel.rule.alwaysApplyTurn && (!after && ruleModel.rule.alwaysApplyTurn == "turn" || after && ruleModel.rule.alwaysApplyTurn == "after") && this.game.appliedScenarioRules.find((applied) => applied.edition == ruleModel.identifier.edition && applied.scenario == ruleModel.identifier.scenario && applied.group == ruleModel.identifier.group && applied.index == ruleModel.identifier.index && applied.section == ruleModel.identifier.section)))
}
})
}

rules.forEach((ruleModel) => {
this.applyRule(ruleModel.rule, ruleModel.identifier, [entity]);
})
}

addScenarioErrata() {
const scenario = this.game.scenario;
if (scenario && scenario.errata) {
Expand Down Expand Up @@ -411,7 +438,7 @@ export class ScenarioRulesManager {
return this.entitiesByFigureRule(figureRule, rule).filter((entity) => (gameManager.entityManager.isAlive(entity) || entity instanceof MonsterEntity && entity.dormant) && (!(entity instanceof MonsterEntity) || (!(figureRule.identifier?.marker) || (entity instanceof MonsterEntity && figureRule.identifier && entity.marker == figureRule.identifier.marker && (!figureRule.identifier.tags || figureRule.identifier.tags.length == 0 || (entity instanceof MonsterEntity && figureRule.identifier.tags.forEach((tag) => entity.tags.indexOf(tag) != -1)))))))
}

applyRule(rule: ScenarioRule, identifier: ScenarioRuleIdentifier) {
applyRule(rule: ScenarioRule, identifier: ScenarioRuleIdentifier, entityFilter: Entity[] | undefined = undefined) {

const scenario = gameManager.scenarioRulesManager.getScenarioForRule(identifier).scenario;
const section = gameManager.scenarioRulesManager.getScenarioForRule(identifier).section;
Expand Down Expand Up @@ -487,7 +514,7 @@ export class ScenarioRulesManager {
let figures: Figure[] = gameManager.scenarioRulesManager.figuresByFigureRule(figureRule, rule);
let ruleEntities: Entity[] = gameManager.scenarioRulesManager.entitiesByFigureRule(figureRule, rule);
figures.forEach((figure) => {
let entities: Entity[] = gameManager.entityManager.entities(figure).filter((entity) => ruleEntities.indexOf(entity) != -1);
let entities: Entity[] = gameManager.entityManager.entitiesAll(figure).filter((entity) => ruleEntities.indexOf(entity) != -1 && (!entityFilter || entityFilter.indexOf(entity) != -1));
entities.forEach((entity) => {
switch (figureRule.type) {
case "gainCondition":
Expand Down
4 changes: 2 additions & 2 deletions src/app/game/model/data/Identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export class CountIdentifier extends Identifier {
}

export class AdditionalIdentifier extends Identifier {
type: "all" | "character" | "objective" | "monster" | undefined;
type: "all" | "character" | "characterWithSummon" | "objective" | "monster" | undefined;
marker: string | undefined;
tags: string[] | undefined;;

constructor(name: string, edition: string, type: "all" | "character" | "objective" | "monster" | undefined = undefined, marker: string | undefined = undefined, tags: string[] | undefined = undefined) {
constructor(name: string, edition: string, type: "all" | "character" | "characterWithSummon" | "objective" | "monster" | undefined = undefined, marker: string | undefined = undefined, tags: string[] | undefined = undefined) {
super(name, edition);
this.type = type;
this.marker = marker;
Expand Down
1 change: 1 addition & 0 deletions src/app/game/model/data/ScenarioRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class ScenarioRule {
start: boolean = false;
always: boolean = false;
alwaysApply: boolean = false;
alwaysApplyTurn: "turn" | "after" | false = false;
once: boolean = false;
requiredRooms: number[] = [];
requiredRules: ScenarioRuleIdentifier[] = [];
Expand Down
16 changes: 16 additions & 0 deletions src/app/ui/footer/scenario-rules/dialog/scenario-rules-dialog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="scenario-rules-dialog">
<h2 [ghs-label]="'scenario.rules.show.applied'" *ngIf="appliedScenarioRules.length"></h2>
<div class="applied" >
<ghs-scenario-rule *ngFor="let ruleModel of appliedScenarioRules" [rule]="ruleModel.rule"
[identifier]="ruleModel.identifier" (click)="disgardScenarioRule(ruleModel)"></ghs-scenario-rule>
</div>
<h2 [ghs-label]="'scenario.rules.show.discarded'"></h2>
<div class="discarded">
<ghs-scenario-rule *ngFor="let ruleModel of disgardedScenarioRules" [rule]="ruleModel.rule"
[identifier]="ruleModel.identifier"></ghs-scenario-rule>
</div>
<div class="menu">
<a *ngIf="disgardedScenarioRules.length" [ghs-label]="'scenario.rules.clearDiscarded'"
(click)="clearDiscardedScenarioRules()"></a>
</div>
</div>
31 changes: 31 additions & 0 deletions src/app/ui/footer/scenario-rules/dialog/scenario-rules-dialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
h2 {
font-size: calc(var(--ghs-unit) * 2.5 * var(--ghs-text-factor));
color: var(--ghs-color-darkgray);
margin: 0;
padding: 0;
}

.applied ghs-scenario-rule {

cursor: zoom-out;

&:hover {
opacity: 0.6;
}
}

.menu {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

a {
cursor: pointer;
font-size: calc(var(--ghs-unit) * 2 * var(--ghs-dialog-factor));

&:hover {
opacity: 0.6;
}
}
}
Loading

0 comments on commit 0f75014

Please sign in to comment.