@civ-clone/base-unit-lifecycle 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.gitattributes +4 -0
  2. package/.github/workflows/build-on-push.yml +39 -0
  3. package/.prettierrc +4 -0
  4. package/LICENSE +21 -0
  5. package/README.md +61 -2
  6. package/lib/activate.d.ts +15 -0
  7. package/lib/activate.js +27 -0
  8. package/lib/activate.js.map +1 -0
  9. package/lib/activate.ts +47 -0
  10. package/lib/canStow.d.ts +8 -0
  11. package/lib/canStow.js +17 -0
  12. package/lib/canStow.js.map +1 -0
  13. package/lib/canStow.ts +25 -0
  14. package/lib/created.d.ts +9 -0
  15. package/lib/created.js +19 -0
  16. package/lib/created.js.map +1 -0
  17. package/lib/created.ts +24 -0
  18. package/lib/defeated.d.ts +25 -0
  19. package/lib/defeated.js +33 -0
  20. package/lib/defeated.js.map +1 -0
  21. package/lib/defeated.ts +55 -0
  22. package/lib/destroyed.d.ts +37 -0
  23. package/lib/destroyed.js +74 -0
  24. package/lib/destroyed.js.map +1 -0
  25. package/lib/destroyed.ts +106 -0
  26. package/lib/detonatedRules.d.ts +56 -0
  27. package/lib/detonatedRules.js +95 -0
  28. package/lib/detonatedRules.js.map +1 -0
  29. package/lib/detonatedRules.ts +196 -0
  30. package/lib/diplomatRules.d.ts +27 -0
  31. package/lib/diplomatRules.js +41 -0
  32. package/lib/diplomatRules.js.map +1 -0
  33. package/lib/diplomatRules.ts +83 -0
  34. package/lib/halted.d.ts +6 -0
  35. package/lib/halted.js +10 -0
  36. package/lib/halted.js.map +1 -0
  37. package/lib/halted.ts +11 -0
  38. package/lib/impassable.d.ts +8 -0
  39. package/lib/impassable.js +15 -0
  40. package/lib/impassable.js.map +1 -0
  41. package/lib/impassable.ts +19 -0
  42. package/lib/lifecycleRules.d.ts +118 -0
  43. package/lib/lifecycleRules.js +126 -0
  44. package/lib/lifecycleRules.js.map +1 -0
  45. package/lib/lifecycleRules.ts +378 -0
  46. package/lib/lostAtSea.d.ts +12 -0
  47. package/lib/lostAtSea.js +26 -0
  48. package/lib/lostAtSea.js.map +1 -0
  49. package/lib/lostAtSea.ts +37 -0
  50. package/lib/movedRules.d.ts +44 -0
  51. package/lib/movedRules.js +126 -0
  52. package/lib/movedRules.js.map +1 -0
  53. package/lib/movedRules.ts +300 -0
  54. package/lib/playerAction.d.ts +22 -0
  55. package/lib/playerAction.js +28 -0
  56. package/lib/playerAction.js.map +1 -0
  57. package/lib/playerAction.ts +64 -0
  58. package/lib/stowed.d.ts +2 -0
  59. package/lib/stowed.js +8 -0
  60. package/lib/stowed.js.map +1 -0
  61. package/lib/stowed.ts +6 -0
  62. package/lib/treatyPartner.d.ts +26 -0
  63. package/lib/treatyPartner.js +38 -0
  64. package/lib/treatyPartner.js.map +1 -0
  65. package/lib/treatyPartner.ts +78 -0
  66. package/lib/unsupported.d.ts +6 -0
  67. package/lib/unsupported.js +9 -0
  68. package/lib/unsupported.js.map +1 -0
  69. package/lib/unsupported.ts +10 -0
  70. package/lib/validateMove.d.ts +11 -0
  71. package/lib/validateMove.js +26 -0
  72. package/lib/validateMove.js.map +1 -0
  73. package/lib/validateMove.ts +36 -0
  74. package/package.json +61 -4
  75. package/tests/fixtures/civ1-unit-rules.json +89 -0
  76. package/tests/lifecycle.test.ts +657 -0
  77. package/tests/movedRules.test.ts +256 -0
  78. package/tests/rules.test.ts +434 -0
  79. package/tsconfig.json +16 -0
@@ -0,0 +1,300 @@
1
+ import {
2
+ CityRegistry,
3
+ instance as cityRegistryInstance,
4
+ } from '@civ-clone/core-city/CityRegistry';
5
+ import {
6
+ Engine,
7
+ instance as engineInstance,
8
+ } from '@civ-clone/core-engine/Engine';
9
+ import {
10
+ InteractionRegistry,
11
+ instance as interactionRegistryInstance,
12
+ } from '@civ-clone/core-diplomacy/InteractionRegistry';
13
+ import {
14
+ RuleRegistry,
15
+ instance as ruleRegistryInstance,
16
+ } from '@civ-clone/core-rule/RuleRegistry';
17
+ import {
18
+ TransportRegistry,
19
+ instance as transportRegistryInstance,
20
+ } from '@civ-clone/core-unit-transport/TransportRegistry';
21
+ import {
22
+ UnitRegistry,
23
+ instance as unitRegistryInstance,
24
+ } from '@civ-clone/core-unit/UnitRegistry';
25
+ import { Peace, Vendetta } from '@civ-clone/library-diplomacy/Declarations';
26
+ import Action from '@civ-clone/core-unit/Action';
27
+ import Air from '@civ-clone/base-unit-type-air/Air';
28
+ import AttackEndsTurn from '@civ-clone/base-unit-trait-attack-ends-turn/AttackEndsTurn';
29
+ import CoastBound from '@civ-clone/base-unit-trait-coast-bound/CoastBound';
30
+ import Criterion from '@civ-clone/core-rule/Criterion';
31
+ import Effect from '@civ-clone/core-rule/Effect';
32
+ import High from '@civ-clone/core-rule/Priorities/High';
33
+ import { ITransport } from '@civ-clone/core-unit-transport/Transport';
34
+ import LostAtSea from '@civ-clone/core-unit-transport/Rules/LostAtSea';
35
+ import Moved from '@civ-clone/core-unit/Rules/Moved';
36
+ import NavalTransport from '@civ-clone/base-unit-type-naval-transport/NavalTransport';
37
+ import Never from '@civ-clone/core-diplomacy/Expiries/Never';
38
+ import Player from '@civ-clone/core-player/Player';
39
+ import Sleeping from '@civ-clone/base-unit-action-sleep/Rules/Sleeping';
40
+ import Stowed from '@civ-clone/base-unit-action-embark/Busy/Stowed';
41
+ import Tile from '@civ-clone/core-world/Tile';
42
+ import Unit from '@civ-clone/core-unit/Unit';
43
+ import { hasVendetta } from '@civ-clone/library-diplomacy/lib/holds';
44
+ import { instance as rngInstance } from '@civ-clone/core-random';
45
+
46
+ type ActionType = abstract new (...args: any[]) => Action;
47
+
48
+ export interface MovedDependencies {
49
+ cityRegistry?: CityRegistry;
50
+ engine?: Engine;
51
+ interactionRegistry?: InteractionRegistry;
52
+ randomNumberGenerator?: () => number;
53
+ ruleRegistry?: RuleRegistry;
54
+ transportRegistry?: TransportRegistry;
55
+ unitRegistry?: UnitRegistry;
56
+ }
57
+
58
+ export interface MovedOptions {
59
+ // The ruleset's own action classes: a plain move, leaving a transport, and the attacks.
60
+ Move: ActionType;
61
+ Disembark: ActionType;
62
+ attacks: ActionType[];
63
+ // Actions with an enemy that end a peace treaty without a grudge (walking onto, or pillaging, the partner's land).
64
+ noVendetta: ActionType[];
65
+ // The chance a `CoastBound` unit that ends its move out of sight of land is lost.
66
+ coastBoundLoss: number;
67
+ }
68
+
69
+ /**
70
+ * A ruleset's `Moved` rules, in order, with ids `<prefix>:unit/moved/<name>` as civ1-unit's are: telling the client,
71
+ * vision, ending a unit's moves, transports carrying and collecting their cargo, leaving a transport, a `CoastBound`
72
+ * unit lost at sea, an `AttackEndsTurn` unit's turn ending, and a broken peace treaty.
73
+ */
74
+ export const movedRules = (
75
+ prefix: string,
76
+ {
77
+ cityRegistry = cityRegistryInstance,
78
+ engine = engineInstance,
79
+ interactionRegistry = interactionRegistryInstance,
80
+ randomNumberGenerator = rngInstance,
81
+ ruleRegistry = ruleRegistryInstance,
82
+ transportRegistry = transportRegistryInstance,
83
+ unitRegistry = unitRegistryInstance,
84
+ }: MovedDependencies,
85
+ { Move, Disembark, attacks, noVendetta, coastBoundLoss }: MovedOptions
86
+ ): Moved[] => {
87
+ const id = (name: string): string => `${prefix}:unit/moved/${name}`,
88
+ // Stows each of `units` aboard `transport`, as `Embark` and `LandAircraft` do, until it is full.
89
+ collect = (transport: NavalTransport, units: Unit[]): void =>
90
+ units.forEach((unit: Unit): void => {
91
+ if (!transport.hasCapacity() || !transport.canStow(unit)) {
92
+ return;
93
+ }
94
+
95
+ transport.stow(unit, unit.tile());
96
+
97
+ unit.setBusy(new Stowed());
98
+ }),
99
+ // The transport's own units on the tile it has just moved onto, that aren't already aboard something.
100
+ unitsToCollect = (transport: Unit, tile: Tile = transport.tile()): Unit[] =>
101
+ unitRegistry
102
+ .getByTile(tile)
103
+ .filter(
104
+ (unit: Unit): boolean =>
105
+ unit !== transport &&
106
+ unit.player() === transport.player() &&
107
+ !transportRegistry.hasUnit(unit)
108
+ ),
109
+ // A transport's own move that took it onto a new tile (not one it was carried along on, and not an attack).
110
+ transportMoved = [
111
+ new Criterion((unit: Unit): boolean => unit instanceof NavalTransport),
112
+ new Criterion(
113
+ (unit: Unit, action: Action): boolean => action instanceof Move
114
+ ),
115
+ new Criterion(
116
+ (unit: Unit, action: Action): boolean => action.to() === unit.tile()
117
+ ),
118
+ ],
119
+ isOwnCity = (unit: Unit): boolean =>
120
+ cityRegistry.getByTile(unit.tile())?.player() === unit.player();
121
+
122
+ return [
123
+ new Moved(
124
+ id('emit'),
125
+ new Effect((unit: Unit, action: Action): void => {
126
+ engine.emit('unit:moved', unit, action);
127
+ })
128
+ ),
129
+ new Moved(
130
+ id('apply-visibility'),
131
+ new Effect((unit: Unit): void => unit.applyVisibility())
132
+ ),
133
+ new Moved(
134
+ id('end-moves-when-exhausted'),
135
+ new Criterion((unit: Unit): boolean => unit.moves().value() < 0.3),
136
+ new Effect((unit: Unit): void => {
137
+ unit.moves().set(0);
138
+ unit.setActive(false);
139
+ })
140
+ ),
141
+ new Moved(
142
+ // A transport leaving one of its player's cities takes only the units sentried aboard (`Stowed` or `Sleeping`);
143
+ // at sea, all of its cargo goes with it, and so does an aircraft aboard, awake or not.
144
+ id('move-cargo'),
145
+ new Criterion((unit: Unit): boolean => unit instanceof NavalTransport),
146
+ new Criterion(
147
+ (unit: Unit, action: Action): boolean => action instanceof Move
148
+ ),
149
+ new Criterion((unit: Unit): boolean =>
150
+ (unit as NavalTransport).hasCargo()
151
+ ),
152
+ new Effect((unit: Unit, action: Action): void => {
153
+ const transport = unit as NavalTransport,
154
+ leftPort =
155
+ cityRegistry.getByTile(action.from())?.player() === unit.player();
156
+
157
+ transport.cargo().forEach((cargo: Unit): void => {
158
+ if (
159
+ leftPort &&
160
+ !(cargo instanceof Air) &&
161
+ !(cargo.busy() instanceof Stowed) &&
162
+ !(cargo.busy() instanceof Sleeping)
163
+ ) {
164
+ transport.unload(cargo);
165
+
166
+ return;
167
+ }
168
+
169
+ cargo.action(action.forUnit(cargo));
170
+ });
171
+ })
172
+ ),
173
+ // These two come after `move-cargo`, so a unit collected here isn't also sent along the move just made.
174
+ new Moved(
175
+ // A carrier that moves onto its player's aircraft outside a city picks them up.
176
+ id('collect-aircraft'),
177
+ ...transportMoved,
178
+ new Criterion((unit: Unit): boolean => !isOwnCity(unit)),
179
+ new Effect((unit: Unit): void =>
180
+ collect(
181
+ unit as NavalTransport,
182
+ unitsToCollect(unit).filter(
183
+ (tileUnit: Unit): boolean => tileUnit instanceof Air
184
+ )
185
+ )
186
+ )
187
+ ),
188
+ new Moved(
189
+ // A transport that moves into one of its player's cities picks up the units sleeping there.
190
+ id('collect-sleeping'),
191
+ ...transportMoved,
192
+ new Criterion((unit: Unit): boolean => isOwnCity(unit)),
193
+ new Effect((unit: Unit): void =>
194
+ collect(
195
+ unit as NavalTransport,
196
+ unitsToCollect(unit).filter(
197
+ (tileUnit: Unit): boolean => tileUnit.busy() instanceof Sleeping
198
+ )
199
+ )
200
+ )
201
+ ),
202
+ new Moved(
203
+ id('disembark'),
204
+ new Criterion(
205
+ (unit: Unit, action: Action): boolean => action instanceof Disembark
206
+ ),
207
+ new Effect((unit: Unit): void => {
208
+ const manifest = transportRegistry.getByUnit(unit);
209
+
210
+ manifest.transport().unload(unit);
211
+
212
+ transportRegistry.unregister(manifest);
213
+ })
214
+ ),
215
+ new Moved(
216
+ // A unit that leaves its transport's tile with a plain move (an aircraft taking off, a unit walking off a ship in
217
+ // a city) is no longer aboard. A `Disembark` is a move too, which `disembark` unloads.
218
+ id('take-off'),
219
+ new Criterion(
220
+ (unit: Unit, action: Action): boolean => action instanceof Move
221
+ ),
222
+ new Criterion(
223
+ (unit: Unit, action: Action): boolean => !(action instanceof Disembark)
224
+ ),
225
+ new Criterion((unit: Unit): boolean => transportRegistry.hasUnit(unit)),
226
+ new Criterion(
227
+ (unit: Unit): boolean =>
228
+ transportRegistry.getByUnit(unit).transport().tile() !== unit.tile()
229
+ ),
230
+ new Effect((unit: Unit): void => {
231
+ transportRegistry.getByUnit(unit).transport().unload(unit);
232
+
233
+ if (unit.busy() instanceof Stowed) {
234
+ unit.setBusy();
235
+ }
236
+ })
237
+ ),
238
+ new Moved(
239
+ id('trireme-lost-at-sea'),
240
+ new Criterion((unit: Unit): boolean => unit.hasTrait(CoastBound)),
241
+ new Criterion((unit: Unit): boolean => unit.moves().value() === 0),
242
+ new Criterion((unit: Unit): boolean => !unit.tile().isCoast()),
243
+ new Criterion((): boolean => randomNumberGenerator() <= coastBoundLoss),
244
+ new Effect((unit: Unit): void => {
245
+ ruleRegistry.process(LostAtSea, unit as unknown as ITransport);
246
+ })
247
+ ),
248
+ new Moved(
249
+ // A unit that `AttackEndsTurn` can't attack again until next turn.
250
+ id('bomber/end-turn-after-attack'),
251
+ new High(),
252
+ new Criterion((unit: Unit): boolean => unit.hasTrait(AttackEndsTurn)),
253
+ new Criterion((unit: Unit, action: Action): boolean =>
254
+ attacks.some(
255
+ (AttackType: ActionType): boolean => action instanceof AttackType
256
+ )
257
+ ),
258
+ new Effect((unit: Unit): void => {
259
+ unit.moves().set(0);
260
+ unit.setActive(false);
261
+ })
262
+ ),
263
+ new Moved(
264
+ id('break-peace-treaty'),
265
+ // Any action with an enemy: a sneak attack or capture, or a covert action at peace.
266
+ new Criterion(
267
+ (unit: Unit, action: Action) =>
268
+ typeof (action as unknown as { enemy?: unknown }).enemy === 'function'
269
+ ),
270
+ new Effect((unit: Unit, action: Action) => {
271
+ const enemy = (action as unknown as { enemy(): Player }).enemy(),
272
+ peaceTreaties = interactionRegistry
273
+ .getByPlayers(unit.player(), enemy)
274
+ .filter(
275
+ (interaction): interaction is Peace =>
276
+ interaction instanceof Peace && interaction.active()
277
+ );
278
+
279
+ peaceTreaties.forEach((peaceTreaty) =>
280
+ peaceTreaty.expire(unit.player())
281
+ );
282
+
283
+ // The victim of an attack in peacetime holds a grudge; walking onto or pillaging its land only ends the treaty.
284
+ if (
285
+ peaceTreaties.length > 0 &&
286
+ !noVendetta.some(
287
+ (ActionType: ActionType): boolean => action instanceof ActionType
288
+ ) &&
289
+ !hasVendetta(enemy, unit.player(), interactionRegistry)
290
+ ) {
291
+ interactionRegistry.register(
292
+ new Vendetta(enemy, unit.player(), new Never(), ruleRegistry)
293
+ );
294
+ }
295
+ })
296
+ ),
297
+ ];
298
+ };
299
+
300
+ export default movedRules;
@@ -0,0 +1,22 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ import UnitRegistry from '@civ-clone/core-unit/UnitRegistry';
5
+ export type UnitPlayerActionType<T extends PlayerAction> = new (
6
+ player: Player,
7
+ unit: Unit
8
+ ) => T;
9
+ export declare const hasActiveUnits: (
10
+ unitRegistry: UnitRegistry
11
+ ) => (player: Player) => boolean;
12
+ export declare const activeUnits: <T extends PlayerAction<any>>(
13
+ unitRegistry: UnitRegistry,
14
+ PlayerActionType: UnitPlayerActionType<T>
15
+ ) => (player: Player) => T[];
16
+ export declare const hasInactiveUnits: (
17
+ unitRegistry: UnitRegistry
18
+ ) => (player: Player) => boolean;
19
+ export declare const inactiveUnits: <T extends PlayerAction<any>>(
20
+ unitRegistry: UnitRegistry,
21
+ PlayerActionType: UnitPlayerActionType<T>
22
+ ) => (player: Player) => T[];
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inactiveUnits = exports.hasInactiveUnits = exports.activeUnits = exports.hasActiveUnits = void 0;
4
+ // The player has an active unit with moves left.
5
+ const hasActiveUnits = (unitRegistry) => (player) => unitRegistry
6
+ .getByPlayer(player)
7
+ .some((unit) => unit.active() && unit.moves().value());
8
+ exports.hasActiveUnits = hasActiveUnits;
9
+ // The player's active units with moves left, as `PlayerActionType`s.
10
+ const activeUnits = (unitRegistry, PlayerActionType) => (player) => unitRegistry
11
+ .getByPlayer(player)
12
+ .filter((unit) => unit.active() && unit.moves().value() > 0)
13
+ .sort((a, b) => (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0))
14
+ .map((unit) => new PlayerActionType(player, unit));
15
+ exports.activeUnits = activeUnits;
16
+ // The player has a unit that is inactive or has no moves left.
17
+ const hasInactiveUnits = (unitRegistry) => (player) => unitRegistry
18
+ .getByPlayer(player)
19
+ .some((unit) => !unit.active() || !unit.moves().value());
20
+ exports.hasInactiveUnits = hasInactiveUnits;
21
+ // The player's units that are inactive or have no moves left, as `PlayerActionType`s.
22
+ const inactiveUnits = (unitRegistry, PlayerActionType) => (player) => unitRegistry
23
+ .getByPlayer(player)
24
+ .filter((unit) => !unit.active() || unit.moves().value() === 0)
25
+ .sort((a, b) => (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0))
26
+ .map((unit) => new PlayerActionType(player, unit));
27
+ exports.inactiveUnits = inactiveUnits;
28
+ //# sourceMappingURL=playerAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playerAction.js","sourceRoot":"","sources":["playerAction.ts"],"names":[],"mappings":";;;AAeA,iDAAiD;AAC1C,MAAM,cAAc,GACzB,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,MAAc,EAAW,EAAE,CAC1B,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;AALhD,QAAA,cAAc,kBAKkC;AAE7D,qEAAqE;AAC9D,MAAM,WAAW,GACtB,CACE,YAA0B,EAC1B,gBAAyC,EACzC,EAAE,CACJ,CAAC,MAAc,EAAO,EAAE,CACtB,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,MAAM,CACL,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CACnE;KACA,IAAI,CACH,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAC3B,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAChD;KACA,GAAG,CAAC,CAAC,IAAU,EAAK,EAAE,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAfrD,QAAA,WAAW,eAe0C;AAElE,+DAA+D;AACxD,MAAM,gBAAgB,GAC3B,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,MAAc,EAAW,EAAE,CAC1B,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;AALlD,QAAA,gBAAgB,oBAKkC;AAE/D,sFAAsF;AAC/E,MAAM,aAAa,GACxB,CACE,YAA0B,EAC1B,gBAAyC,EACzC,EAAE,CACJ,CAAC,MAAc,EAAO,EAAE,CACtB,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,MAAM,CAAC,CAAC,IAAU,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;KACpE,IAAI,CACH,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAC3B,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAChD;KACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAb5C,QAAA,aAAa,iBAa+B"}
@@ -0,0 +1,64 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ import UnitRegistry from '@civ-clone/core-unit/UnitRegistry';
5
+
6
+ // What a ruleset's player `Action` rules ask and offer: the player's units that can take orders this turn, and those
7
+ // that can't. Each list puts waiting units last and otherwise keeps the registry's order. `PlayerActionType` is the
8
+ // player action each unit is offered as (an active unit, an inactive one).
9
+
10
+ // The constructor of a player action for one unit.
11
+ export type UnitPlayerActionType<T extends PlayerAction> = new (
12
+ player: Player,
13
+ unit: Unit
14
+ ) => T;
15
+
16
+ // The player has an active unit with moves left.
17
+ export const hasActiveUnits =
18
+ (unitRegistry: UnitRegistry) =>
19
+ (player: Player): boolean =>
20
+ unitRegistry
21
+ .getByPlayer(player)
22
+ .some((unit) => unit.active() && unit.moves().value());
23
+
24
+ // The player's active units with moves left, as `PlayerActionType`s.
25
+ export const activeUnits =
26
+ <T extends PlayerAction>(
27
+ unitRegistry: UnitRegistry,
28
+ PlayerActionType: UnitPlayerActionType<T>
29
+ ) =>
30
+ (player: Player): T[] =>
31
+ unitRegistry
32
+ .getByPlayer(player)
33
+ .filter(
34
+ (unit: Unit): boolean => unit.active() && unit.moves().value() > 0
35
+ )
36
+ .sort(
37
+ (a: Unit, b: Unit): number =>
38
+ (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0)
39
+ )
40
+ .map((unit: Unit): T => new PlayerActionType(player, unit));
41
+
42
+ // The player has a unit that is inactive or has no moves left.
43
+ export const hasInactiveUnits =
44
+ (unitRegistry: UnitRegistry) =>
45
+ (player: Player): boolean =>
46
+ unitRegistry
47
+ .getByPlayer(player)
48
+ .some((unit) => !unit.active() || !unit.moves().value());
49
+
50
+ // The player's units that are inactive or have no moves left, as `PlayerActionType`s.
51
+ export const inactiveUnits =
52
+ <T extends PlayerAction>(
53
+ unitRegistry: UnitRegistry,
54
+ PlayerActionType: UnitPlayerActionType<T>
55
+ ) =>
56
+ (player: Player): T[] =>
57
+ unitRegistry
58
+ .getByPlayer(player)
59
+ .filter((unit: Unit) => !unit.active() || unit.moves().value() === 0)
60
+ .sort(
61
+ (a: Unit, b: Unit): number =>
62
+ (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0)
63
+ )
64
+ .map((unit) => new PlayerActionType(player, unit));
@@ -0,0 +1,2 @@
1
+ import Unit from '@civ-clone/core-unit/Unit';
2
+ export declare const sleepAboard: (unit: Unit) => void;
package/lib/stowed.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sleepAboard = void 0;
4
+ const Sleep_1 = require("@civ-clone/base-unit-action-sleep/Sleep");
5
+ // A unit that has just boarded a transport sleeps aboard it. Returns what `unit.action` returns.
6
+ const sleepAboard = (unit) => unit.action(new Sleep_1.default(unit.tile(), unit.tile(), unit));
7
+ exports.sleepAboard = sleepAboard;
8
+ //# sourceMappingURL=stowed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stowed.js","sourceRoot":"","sources":["stowed.ts"],"names":[],"mappings":";;;AAAA,mEAA4D;AAG5D,iGAAiG;AAC1F,MAAM,WAAW,GAAG,CAAC,IAAU,EAAE,EAAE,CACxC,IAAI,CAAC,MAAM,CAAC,IAAI,eAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;AAD5C,QAAA,WAAW,eACiC"}
package/lib/stowed.ts ADDED
@@ -0,0 +1,6 @@
1
+ import Sleep from '@civ-clone/base-unit-action-sleep/Sleep';
2
+ import Unit from '@civ-clone/core-unit/Unit';
3
+
4
+ // A unit that has just boarded a transport sleeps aboard it. Returns what `unit.action` returns.
5
+ export const sleepAboard = (unit: Unit) =>
6
+ unit.action(new Sleep(unit.tile(), unit.tile(), unit));
@@ -0,0 +1,26 @@
1
+ import { InteractionRegistry } from '@civ-clone/core-diplomacy/InteractionRegistry';
2
+ import { TileImprovementRegistry } from '@civ-clone/core-tile-improvement/TileImprovementRegistry';
3
+ import { WorkedTileRegistry } from '@civ-clone/core-city/WorkedTileRegistry';
4
+ import Player from '@civ-clone/core-player/Player';
5
+ import Tile from '@civ-clone/core-world/Tile';
6
+ import TileImprovement from '@civ-clone/core-tile-improvement/TileImprovement';
7
+ import Unit from '@civ-clone/core-unit/Unit';
8
+ export interface TreatyPartnerDependencies {
9
+ interactionRegistry?: InteractionRegistry;
10
+ tileImprovementRegistry?: TileImprovementRegistry;
11
+ workedTileRegistry?: WorkedTileRegistry;
12
+ }
13
+ /**
14
+ * Whose land a tile is, among a unit's treaty partners, or `null`: a tile that one of the partner's cities is working
15
+ * and that has one of `improvements` on it, for a land unit that isn't a `NonCombatant`. Moving onto or pillaging such
16
+ * a tile breaks the treaty. A tile counts as land while it's worked (Civ1's territory model, simplified).
17
+ */
18
+ export declare const treatyPartner: (
19
+ improvements: (typeof TileImprovement)[],
20
+ {
21
+ interactionRegistry,
22
+ tileImprovementRegistry,
23
+ workedTileRegistry,
24
+ }?: TreatyPartnerDependencies
25
+ ) => (unit: Unit, tile: Tile) => Player | null;
26
+ export default treatyPartner;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.treatyPartner = void 0;
4
+ const InteractionRegistry_1 = require("@civ-clone/core-diplomacy/InteractionRegistry");
5
+ const TileImprovementRegistry_1 = require("@civ-clone/core-tile-improvement/TileImprovementRegistry");
6
+ const WorkedTileRegistry_1 = require("@civ-clone/core-city/WorkedTileRegistry");
7
+ const Land_1 = require("@civ-clone/base-unit-type-land/Land");
8
+ const NonCombatant_1 = require("@civ-clone/base-unit-trait-non-combatant/NonCombatant");
9
+ const Declarations_1 = require("@civ-clone/library-diplomacy/Declarations");
10
+ const traits_1 = require("@civ-clone/core-unit/traits");
11
+ /**
12
+ * Whose land a tile is, among a unit's treaty partners, or `null`: a tile that one of the partner's cities is working
13
+ * and that has one of `improvements` on it, for a land unit that isn't a `NonCombatant`. Moving onto or pillaging such
14
+ * a tile breaks the treaty. A tile counts as land while it's worked (Civ1's territory model, simplified).
15
+ */
16
+ const treatyPartner = (improvements, { interactionRegistry = InteractionRegistry_1.instance, tileImprovementRegistry = TileImprovementRegistry_1.instance, workedTileRegistry = WorkedTileRegistry_1.instance, } = {}) => (unit, tile) => {
17
+ if (!(0, traits_1.isA)(unit, Land_1.default) || unit.hasTrait(NonCombatant_1.default)) {
18
+ return null;
19
+ }
20
+ const workedTile = workedTileRegistry.getByTile(tile);
21
+ if (workedTile === null) {
22
+ return null;
23
+ }
24
+ const partner = workedTile.city().player();
25
+ if (partner === unit.player() ||
26
+ !interactionRegistry
27
+ .getByPlayers(unit.player(), partner)
28
+ .some((interaction) => interaction instanceof Declarations_1.Peace && interaction.active()) ||
29
+ !tileImprovementRegistry
30
+ .getByTile(tile)
31
+ .some((improvement) => improvements.some((Improvement) => improvement instanceof Improvement))) {
32
+ return null;
33
+ }
34
+ return partner;
35
+ };
36
+ exports.treatyPartner = treatyPartner;
37
+ exports.default = exports.treatyPartner;
38
+ //# sourceMappingURL=treatyPartner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"treatyPartner.js","sourceRoot":"","sources":["treatyPartner.ts"],"names":[],"mappings":";;;AAAA,uFAGuD;AACvD,sGAGkE;AAClE,gFAGiD;AACjD,8DAAuD;AACvD,wFAAiF;AACjF,4EAAkE;AAKlE,wDAAkD;AAQlD;;;;GAIG;AACI,MAAM,aAAa,GACxB,CACE,YAAwC,EACxC,EACE,mBAAmB,GAAG,8BAA2B,EACjD,uBAAuB,GAAG,kCAA+B,EACzD,kBAAkB,GAAG,6BAA0B,MAClB,EAAE,EACjC,EAAE,CACJ,CAAC,IAAU,EAAE,IAAU,EAAiB,EAAE;IACxC,IAAI,CAAC,IAAA,YAAG,EAAC,IAAI,EAAE,cAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,sBAAY,CAAC,EAAE;QACnD,OAAO,IAAI,CAAC;KACb;IAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAEtD,IAAI,UAAU,KAAK,IAAI,EAAE;QACvB,OAAO,IAAI,CAAC;KACb;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;IAE3C,IACE,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE;QACzB,CAAC,mBAAmB;aACjB,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC;aACpC,IAAI,CACH,CAAC,WAAW,EAAwB,EAAE,CACpC,WAAW,YAAY,oBAAK,IAAI,WAAW,CAAC,MAAM,EAAE,CACvD;QACH,CAAC,uBAAuB;aACrB,SAAS,CAAC,IAAI,CAAC;aACf,IAAI,CAAC,CAAC,WAA4B,EAAW,EAAE,CAC9C,YAAY,CAAC,IAAI,CACf,CAAC,WAAmC,EAAW,EAAE,CAC/C,WAAW,YAAY,WAAW,CACrC,CACF,EACH;QACA,OAAO,IAAI,CAAC;KACb;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AA3CS,QAAA,aAAa,iBA2CtB;AAEJ,kBAAe,qBAAa,CAAC"}
@@ -0,0 +1,78 @@
1
+ import {
2
+ InteractionRegistry,
3
+ instance as interactionRegistryInstance,
4
+ } from '@civ-clone/core-diplomacy/InteractionRegistry';
5
+ import {
6
+ TileImprovementRegistry,
7
+ instance as tileImprovementRegistryInstance,
8
+ } from '@civ-clone/core-tile-improvement/TileImprovementRegistry';
9
+ import {
10
+ WorkedTileRegistry,
11
+ instance as workedTileRegistryInstance,
12
+ } from '@civ-clone/core-city/WorkedTileRegistry';
13
+ import Land from '@civ-clone/base-unit-type-land/Land';
14
+ import NonCombatant from '@civ-clone/base-unit-trait-non-combatant/NonCombatant';
15
+ import { Peace } from '@civ-clone/library-diplomacy/Declarations';
16
+ import Player from '@civ-clone/core-player/Player';
17
+ import Tile from '@civ-clone/core-world/Tile';
18
+ import TileImprovement from '@civ-clone/core-tile-improvement/TileImprovement';
19
+ import Unit from '@civ-clone/core-unit/Unit';
20
+ import { isA } from '@civ-clone/core-unit/traits';
21
+
22
+ export interface TreatyPartnerDependencies {
23
+ interactionRegistry?: InteractionRegistry;
24
+ tileImprovementRegistry?: TileImprovementRegistry;
25
+ workedTileRegistry?: WorkedTileRegistry;
26
+ }
27
+
28
+ /**
29
+ * Whose land a tile is, among a unit's treaty partners, or `null`: a tile that one of the partner's cities is working
30
+ * and that has one of `improvements` on it, for a land unit that isn't a `NonCombatant`. Moving onto or pillaging such
31
+ * a tile breaks the treaty. A tile counts as land while it's worked (Civ1's territory model, simplified).
32
+ */
33
+ export const treatyPartner =
34
+ (
35
+ improvements: (typeof TileImprovement)[],
36
+ {
37
+ interactionRegistry = interactionRegistryInstance,
38
+ tileImprovementRegistry = tileImprovementRegistryInstance,
39
+ workedTileRegistry = workedTileRegistryInstance,
40
+ }: TreatyPartnerDependencies = {}
41
+ ) =>
42
+ (unit: Unit, tile: Tile): Player | null => {
43
+ if (!isA(unit, Land) || unit.hasTrait(NonCombatant)) {
44
+ return null;
45
+ }
46
+
47
+ const workedTile = workedTileRegistry.getByTile(tile);
48
+
49
+ if (workedTile === null) {
50
+ return null;
51
+ }
52
+
53
+ const partner = workedTile.city().player();
54
+
55
+ if (
56
+ partner === unit.player() ||
57
+ !interactionRegistry
58
+ .getByPlayers(unit.player(), partner)
59
+ .some(
60
+ (interaction): interaction is Peace =>
61
+ interaction instanceof Peace && interaction.active()
62
+ ) ||
63
+ !tileImprovementRegistry
64
+ .getByTile(tile)
65
+ .some((improvement: TileImprovement): boolean =>
66
+ improvements.some(
67
+ (Improvement: typeof TileImprovement): boolean =>
68
+ improvement instanceof Improvement
69
+ )
70
+ )
71
+ ) {
72
+ return null;
73
+ }
74
+
75
+ return partner;
76
+ };
77
+
78
+ export default treatyPartner;
@@ -0,0 +1,6 @@
1
+ import City from '@civ-clone/core-city/City';
2
+ import Engine from '@civ-clone/core-engine/Engine';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ export declare const emitUnsupported: (
5
+ engine: Engine
6
+ ) => (city: City, unit: Unit) => void;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitUnsupported = void 0;
4
+ // Emits `unit:unsupported` with the city that could no longer support the unit, and the unit.
5
+ const emitUnsupported = (engine) => (city, unit) => {
6
+ engine.emit('unit:unsupported', city, unit);
7
+ };
8
+ exports.emitUnsupported = emitUnsupported;
9
+ //# sourceMappingURL=unsupported.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsupported.js","sourceRoot":"","sources":["unsupported.ts"],"names":[],"mappings":";;;AAIA,8FAA8F;AACvF,MAAM,eAAe,GAC1B,CAAC,MAAc,EAAE,EAAE,CACnB,CAAC,IAAU,EAAE,IAAU,EAAQ,EAAE;IAC/B,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC,CAAC;AAJS,QAAA,eAAe,mBAIxB"}
@@ -0,0 +1,10 @@
1
+ import City from '@civ-clone/core-city/City';
2
+ import Engine from '@civ-clone/core-engine/Engine';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+
5
+ // Emits `unit:unsupported` with the city that could no longer support the unit, and the unit.
6
+ export const emitUnsupported =
7
+ (engine: Engine) =>
8
+ (city: City, unit: Unit): void => {
9
+ engine.emit('unit:unsupported', city, unit);
10
+ };
@@ -0,0 +1,11 @@
1
+ import Unit from '@civ-clone/core-unit/Unit';
2
+ export declare const hasMovesFor: (unit: Unit, movementCost: number) => boolean;
3
+ export declare const spendMoves: (unit: Unit, movementCost: number) => boolean;
4
+ export declare const shortOfMovesFor: (
5
+ unit: Unit,
6
+ movementCost: number
7
+ ) => boolean;
8
+ export declare const spendShort: (
9
+ shortfallFactor: number,
10
+ randomNumberGenerator: () => number
11
+ ) => (unit: Unit, movementCost: number) => boolean;