Skip to content

Commit

Permalink
Merge pull request #335 from Ariemeth/finish_caf
Browse files Browse the repository at this point in the history
starting to finish caf rules for gilgamesh troupe
  • Loading branch information
Ariemeth authored Jan 9, 2024
2 parents fe6288b + 0a759be commit 94511d9
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 30 deletions.
4 changes: 2 additions & 2 deletions lib/models/combatGroups/combat_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ class CombatGroup extends ChangeNotifier {
validationErrors.addAll(sve.validations);

// make sure there is at least a CGL in the CG
final highestRank = _highestCommandLevel();
final highestRank = highestCommandLevel();
if (highestRank < CommandLevel.cgl) {
_tryEnsureCommander(tryFix: tryFix);
validationErrors.add(Validation(
Expand Down Expand Up @@ -274,7 +274,7 @@ class CombatGroup extends ChangeNotifier {
}
}

CommandLevel _highestCommandLevel() {
CommandLevel highestCommandLevel() {
final leaders = getLeaders(null);
if (leaders.isEmpty) {
return CommandLevel.none;
Expand Down
9 changes: 9 additions & 0 deletions lib/models/combatGroups/group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ class Group extends ChangeNotifier {
});

_units.add(unit);
final rs = combatGroup?.roster?.rulesetNotifer.value;
if (rs != null) {
final onAddedRules = rs
.allFactionRules(unit: unit)
.where((rule) => rule.onUnitAdded != null);
for (var rule in onAddedRules) {
rule.onUnitAdded!(combatGroup!.roster!, unit);
}
}
return validations;
}

Expand Down
21 changes: 20 additions & 1 deletion lib/models/roster/roster.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,26 @@ class UnitRoster extends ChangeNotifier {
String _activeCG = '';
String get rulesVersion => _currentRulesVersion;
bool _isEliteForce = false;
Unit? selectedForceLeader;
Unit? _selectedForceLeader;

Unit? get selectedForceLeader => _selectedForceLeader;

set selectedForceLeader(Unit? newLeader) {
if (newLeader == _selectedForceLeader) {
return;
}
final onChangedRules = rulesetNotifer.value
.allEnabledRules(null)
.where((rule) => rule.onForceLeaderChanged != null);

_selectedForceLeader = newLeader;

if (_selectedForceLeader != null) {
onChangedRules.forEach((rule) {
rule.onForceLeaderChanged!(this, newLeader);
});
}
}

UnitRoster(Data data) {
factionNotifier = ValueNotifier<Faction>(Faction.blackTalons(data));
Expand Down
20 changes: 20 additions & 0 deletions lib/models/rules/faction_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class FactionRule extends ChangeNotifier {
this.onModRemoved,
this.modifyWeapon,
this.modifyTraits,
this.onForceLeaderChanged,
this.onUnitAdded,
this.onUnitRemoved,
this.onLeadershipChanged,
this.onEnabled,
this.onDisabled,
}) {
Expand Down Expand Up @@ -130,6 +134,18 @@ class FactionRule extends ChangeNotifier {
/// Modify a [Unit]'s [Trait]s.
final Function(List<Trait> traits, UnitCore uc)? modifyTraits;

/// Called when the force leader is changed.
final void Function(UnitRoster roster, Unit? newleader)? onForceLeaderChanged;

/// Called when a unit is added to a group.
final void Function(UnitRoster roster, Unit unit)? onUnitAdded;

/// Called when a unit is removed from a group.
final void Function(UnitRoster roster, Unit unit)? onUnitRemoved;

/// Called when a unit's leadership level is changed
final void Function(UnitRoster roster, Unit unit)? onLeadershipChanged;

/// Called with the [FactionRule] is enabled.
final Function()? onEnabled;

Expand Down Expand Up @@ -310,6 +326,10 @@ class FactionRule extends ChangeNotifier {
onEnabled: onEnabled != null ? onEnabled : original.onEnabled,
onDisabled: onDisabled != null ? onDisabled : original.onDisabled,
cgCheck: cgCheck != null ? cgCheck : original.cgCheck,
onForceLeaderChanged: original.onForceLeaderChanged,
onUnitAdded: original.onUnitAdded,
onUnitRemoved: original.onUnitRemoved,
onLeadershipChanged: original.onLeadershipChanged,
);
}
Map<String, dynamic> toJson() {
Expand Down
31 changes: 30 additions & 1 deletion lib/models/rules/rule_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,36 @@ Validation? _checkModelRules(Unit unit, Group group) {
return Validation(false, issue: 'cannot be part of a secondary group');
}

// current units in the group need to only be other parts
RegExp gilgameshTypeExp = RegExp(
r'^Gilgamesh (?<location>Forward|Rear).*Type (?<type>.)$',
caseSensitive: false);

if (gilgameshTypeExp.hasMatch(unit.core.name)) {
final unitTypeMatch = gilgameshTypeExp.firstMatch(unit.core.name);

if (!unitsInGroup.any((u) {
if (!gilgameshTypeExp.hasMatch(u.core.name)) {
return true;
}

final uMatch = gilgameshTypeExp.firstMatch(u.core.name);

if (uMatch == null || unitTypeMatch == null) {
return true;
}

// A Gilgamesh front or back in already in the group and one is
// trying to be added. Check to ensure the new part is the same
// type (A or B)
final uMatchType = uMatch.namedGroup('type');
final unitType = unitTypeMatch.namedGroup('type');
return uMatchType == unitType;
})) {
return Validation(false,
issue: 'Cannot mix Gilgamesh Type A and B parts');
}
}

if (unitsInGroup.every((u) =>
u.core.frame != frame &&
(u.core.frame == _gilgameshFront ||
Expand Down
94 changes: 93 additions & 1 deletion lib/models/rules/utopia/caf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:gearforce/models/rules/north/north.dart' as north;
import 'package:gearforce/models/rules/options/combat_group_options.dart';
import 'package:gearforce/models/rules/options/special_unit_filter.dart';
import 'package:gearforce/models/rules/utopia/utopia.dart';
import 'package:gearforce/models/unit/command.dart';
import 'package:gearforce/models/validation/validations.dart';

const String _baseRuleId = 'rule::utopia::caf';
Expand Down Expand Up @@ -310,9 +311,100 @@ final FactionRule ruleGilgameshTroupe = FactionRule(
return Validation(
false,
issue: 'Only a Gilgamesh or units with no actions can be added; See' +
' Gilgamesh Troupe',
' Gilgamesh Troupe rules',
);
},
onForceLeaderChanged: (roster, newleader) {
// Check if any cg is a Gilgamesh Troupe
if (!roster
.getCGs()
.any((cg) => cg.isOptionEnabled(_ruleGilgameshTroupeId))) {
return;
}

final leadersCG = newleader?.combatGroup;
if (leadersCG == null) {
return;
}
if (newleader!.core.frame.contains('Gilgamesh') &&
leadersCG.isOptionEnabled(_ruleGilgameshTroupeId)) {
return;
}

// check if there is a gilgamesh in the gilgamesh troupe cg
final gilgaCG = roster
.getCGs()
.firstWhere((cg) => cg.isOptionEnabled(_ruleGilgameshTroupeId));
if (!gilgaCG.units.any((u) => u.core.frame.contains('Gilgamesh'))) {
// No Gilgamesh in the force, ignore this rule
return;
}

// Check if the gilgamesh is an available force leader
if (roster.availableForceLeaders().any((u) =>
u.core.frame.contains('Gilgamesh') &&
u.combatGroup != null &&
u.combatGroup!.isOptionEnabled(_ruleGilgameshTroupeId))) {
// The gilgamesh is available as a force leader so select it
roster.selectedForceLeader = roster.availableForceLeaders().firstWhere(
(u) =>
u.core.frame.contains('Gilgamesh') &&
u.combatGroup != null &&
u.combatGroup!.isOptionEnabled(_ruleGilgameshTroupeId));
return;
}

// A Gilgamesh exists in a Gilgamesh Troupe CG, is not the force leader and
// does not have a high enough rank to be a force leader, so increase the
// command level to make it the force leader.
final nextCGRank =
CommandLevel.NextGreater(roster.selectedForceLeader!.commandLevel);
final gilgas =
gilgaCG.units.where((u) => u.core.frame.contains('Gilgamesh'));

var gilga = gilgas.first;
gilgas.forEach((g) {
if (g.commandLevel > gilga.commandLevel) {
gilga = g;
}
});
gilga.commandLevel = nextCGRank;
},
onLeadershipChanged: (roster, unit) {
// make sure this unit is a Gilgamesh component.
if (!unit.core.frame.contains('Gilgamesh')) {
return;
}

// check if this unit is already the force leader
if (roster.selectedForceLeader == unit) {
return;
}

// make sure this gilgamesh is in a Gilgamesh Troupe
final unitsCG = unit.combatGroup;
if (unitsCG == null) {
return;
}
if (!unitsCG.isOptionEnabled(_ruleGilgameshTroupeId)) {
return;
}

// Check if the gilgamesh is an available force leader
if (roster.availableForceLeaders().any((u) => u == unit)) {
// The gilgamesh is available as a force leader so select it
roster.selectedForceLeader = unit;
return;
}

// A Gilgamesh exists in a Gilgamesh Troupe CG, is not the force leader and
// does not have a high enough rank to be a force leader, so increase the
// command level to make it the force leader.
final nextCGRank =
CommandLevel.NextGreater(roster.selectedForceLeader!.commandLevel);

unit.commandLevel = nextCGRank;
},
description: 'The Divine Brother: This combat group must use a Gilgamesh.' +
' The Gilgamesh must be the force leader.\n' +
'The Brother’s Friends: The Gilgamesh may spend 1 CP to issue a special' +
Expand Down
19 changes: 19 additions & 0 deletions lib/models/unit/command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ enum CommandLevel {
}
return second;
}

static CommandLevel NextGreater(CommandLevel cl) {
switch (cl) {
case CommandLevel.none:
return CommandLevel.cgl;
case CommandLevel.bc:
return CommandLevel.cgl;
case CommandLevel.po:
return CommandLevel.cgl;
case CommandLevel.secic:
return CommandLevel.cgl;
case CommandLevel.cgl:
return CommandLevel.xo;
case CommandLevel.xo:
return CommandLevel.co;
default:
return CommandLevel.tfc;
}
}
}

extension EnumOperators on CommandLevel {
Expand Down
57 changes: 36 additions & 21 deletions lib/models/unit/unit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ class Unit extends ChangeNotifier {
if (_commandLevel == cl) {
return;
}

_commandLevel = cl;

// Note: Setting the command level on other units directly using prviate
// fields to prevent the notifier from running.
switch (cl) {
Expand All @@ -224,39 +227,51 @@ class Unit extends ChangeNotifier {
break;
case CommandLevel.cgl:
// Only 1 cgl, tfc, co, or xo can exist within a single Combat Group
combatGroup?.getUnitWithCommand(CommandLevel.cgl)?._commandLevel =
CommandLevel.none;
combatGroup?.getUnitWithCommand(CommandLevel.tfc)?._commandLevel =
CommandLevel.none;
combatGroup?.getUnitWithCommand(CommandLevel.co)?._commandLevel =
CommandLevel.none;
combatGroup?.getUnitWithCommand(CommandLevel.xo)?._commandLevel =
CommandLevel.none;
final units = combatGroup?.units.where(
(unit) => unit != this && unit.commandLevel >= CommandLevel.cgl);
units?.forEach((unit) {
unit.commandLevel = CommandLevel.none;
});

break;
case CommandLevel.secic:
// only 1 second in command per combat group
combatGroup?.getUnitWithCommand(CommandLevel.secic)?._commandLevel =
CommandLevel.none;
final units = combatGroup?.units.where(
(unit) => unit != this && unit.commandLevel == CommandLevel.secic);
units?.forEach((unit) {
unit.commandLevel = CommandLevel.none;
});
break;
case CommandLevel.xo:
case CommandLevel.co:
case CommandLevel.tfc:
// only 1 xo, co, tfc per task force
combatGroup?.roster?.getFirstUnitWithCommand(cl)?._commandLevel =
CommandLevel.cgl;
final topUnits = roster
?.getAllUnits()
.where((unit) => unit != this && unit.commandLevel == cl);
topUnits?.forEach((unit) {
unit.commandLevel = CommandLevel.none;
});

// only 1 of xo, co, tfc, or cgl per combat group
combatGroup?.getUnitWithCommand(CommandLevel.cgl)?._commandLevel =
CommandLevel.none;
combatGroup?.getUnitWithCommand(CommandLevel.tfc)?._commandLevel =
CommandLevel.none;
combatGroup?.getUnitWithCommand(CommandLevel.co)?._commandLevel =
CommandLevel.none;
combatGroup?.getUnitWithCommand(CommandLevel.xo)?._commandLevel =
CommandLevel.none;
final units = combatGroup?.units.where(
(unit) => unit != this && unit.commandLevel >= CommandLevel.cgl);
units?.forEach((unit) {
unit.commandLevel = CommandLevel.none;
});

break;
}

_commandLevel = cl;
final rs = roster?.rulesetNotifer.value;
if (rs != null) {
final onLeadershipRules = rs
.allFactionRules(unit: this)
.where((rule) => rule.onLeadershipChanged != null);
for (var rule in onLeadershipRules) {
rule.onLeadershipChanged!(roster!, this);
}
}

notifyListeners();
}
Expand Down
2 changes: 1 addition & 1 deletion lib/screens/roster/roster.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import 'package:url_launcher/url_launcher_string.dart';
const double _leftPanelWidth = 670.0;
const double _titleHeight = 40.0;
const double _menuTitleHeight = 50.0;
const String _version = '0.100.0';
const String _version = '0.100.1';
const String _bugEmailAddress = '[email protected]';
const String _dp9URL = 'https://www.dp9.com/';
const String _sourceCodeURL = 'https://github.com/Ariemeth/gearforce-flutter';
Expand Down
12 changes: 10 additions & 2 deletions lib/screens/roster/select_force_leader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ class _SelectForceLeaderState extends State<SelectForceLeader> {
@override
Widget build(BuildContext context) {
final roster = context.watch<UnitRoster>();
final availableUnits = roster.availableForceLeaders();
var selectedLeader = roster.selectedForceLeader;

if (selectedLeader != null && !availableUnits.contains(selectedLeader)) {
print(
'Selected leader is not in the available units list, leader: $selectedLeader, available: $availableUnits');
selectedLeader = null;
}

return DropdownButton<Unit?>(
value: roster.selectedForceLeader,
value: selectedLeader,
hint: Text('Select Force Leader'),
icon: const Icon(Icons.arrow_downward),
iconSize: 16,
Expand All @@ -33,7 +41,7 @@ class _SelectForceLeaderState extends State<SelectForceLeader> {
roster.selectedForceLeader = newValue;
});
},
items: _availableForceLeaders(roster.availableForceLeaders()),
items: _availableForceLeaders(availableUnits),
);
}

Expand Down
Loading

0 comments on commit 94511d9

Please sign in to comment.