@v12-engine/engine 0.0.0-stage → 0.1.0
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.
- package/LICENSE +21 -0
- package/dist/abilities/buffs.d.ts +83 -0
- package/dist/abilities/castTold.d.ts +12 -0
- package/dist/abilities/charges.d.ts +28 -0
- package/dist/abilities/dispel.d.ts +9 -0
- package/dist/abilities/effectFields.d.ts +10 -0
- package/dist/abilities/effects/buff.d.ts +8 -0
- package/dist/abilities/effects/core.d.ts +1 -0
- package/dist/abilities/effects/damage.d.ts +1 -0
- package/dist/abilities/effects/heal.d.ts +2 -0
- package/dist/abilities/effects/missile.d.ts +1 -0
- package/dist/abilities/effects/moves.d.ts +11 -0
- package/dist/abilities/effects/passives.d.ts +15 -0
- package/dist/abilities/effects/sight.d.ts +4 -0
- package/dist/abilities/effects/spawn.d.ts +2 -0
- package/dist/abilities/effects/timed.d.ts +4 -0
- package/dist/abilities/effects/trade.d.ts +1 -0
- package/dist/abilities/effects.d.ts +122 -0
- package/dist/abilities/land.d.ts +21 -0
- package/dist/abilities/scaled.d.ts +21 -0
- package/dist/abilities/text.d.ts +113 -0
- package/dist/ai/area.d.ts +53 -0
- package/dist/ai/brain.d.ts +528 -0
- package/dist/ai/budget.d.ts +67 -0
- package/dist/ai/checks.d.ts +115 -0
- package/dist/ai/computer.d.ts +166 -0
- package/dist/ai/defence.d.ts +108 -0
- package/dist/ai/define.d.ts +21 -0
- package/dist/ai/economy.d.ts +129 -0
- package/dist/ai/geometry.d.ts +35 -0
- package/dist/ai/goals.d.ts +360 -0
- package/dist/ai/index.d.ts +38 -0
- package/dist/ai/knowledge.d.ts +418 -0
- package/dist/ai/lanes.d.ts +232 -0
- package/dist/ai/melee/build.d.ts +61 -0
- package/dist/ai/melee/goals.d.ts +299 -0
- package/dist/ai/melee/heroes.d.ts +27 -0
- package/dist/ai/melee/index.d.ts +14 -0
- package/dist/ai/melee/production.d.ts +8 -0
- package/dist/ai/melee/surrender.d.ts +24 -0
- package/dist/ai/micro.d.ts +278 -0
- package/dist/ai/mission.d.ts +195 -0
- package/dist/ai/missionRules.d.ts +18 -0
- package/dist/ai/module.d.ts +116 -0
- package/dist/ai/options.d.ts +61 -0
- package/dist/ai/plans/build.d.ts +280 -0
- package/dist/ai/plans/placement.d.ts +82 -0
- package/dist/ai/plans/plans.d.ts +105 -0
- package/dist/ai/plans/production.d.ts +210 -0
- package/dist/ai/plans/purse.d.ts +73 -0
- package/dist/ai/plans/tech.d.ts +37 -0
- package/dist/ai/profiles.d.ts +204 -0
- package/dist/ai/reach.d.ts +4 -0
- package/dist/ai/report.d.ts +203 -0
- package/dist/ai/roles.d.ts +133 -0
- package/dist/ai/rules.d.ts +288 -0
- package/dist/ai/scouting.d.ts +98 -0
- package/dist/ai/settings.d.ts +73 -0
- package/dist/ai/squads.d.ts +429 -0
- package/dist/ai/strength.d.ts +167 -0
- package/dist/ai/task.d.ts +118 -0
- package/dist/ai/tasks/attack.d.ts +7 -0
- package/dist/ai/tasks/capture.d.ts +21 -0
- package/dist/ai/tasks/creep.d.ts +16 -0
- package/dist/ai/tasks/defend.d.ts +29 -0
- package/dist/ai/tasks/drop.d.ts +34 -0
- package/dist/ai/tasks/escort.d.ts +16 -0
- package/dist/ai/tasks/gather.d.ts +23 -0
- package/dist/ai/tasks/harass.d.ts +7 -0
- package/dist/ai/tasks/hold.d.ts +20 -0
- package/dist/ai/tasks/index.d.ts +32 -0
- package/dist/ai/tasks/move.d.ts +19 -0
- package/dist/ai/tasks/patrol.d.ts +8 -0
- package/dist/ai/tasks/retreat.d.ts +20 -0
- package/dist/ai/tasks/scout.d.ts +29 -0
- package/dist/ai/tasks/shared.d.ts +74 -0
- package/dist/ai/team.d.ts +155 -0
- package/dist/ai/threat.d.ts +91 -0
- package/dist/ai/view.d.ts +366 -0
- package/dist/ai/waves.d.ts +126 -0
- package/dist/ai.js +6239 -0
- package/dist/ai.js.map +1 -0
- package/dist/aiTesting.d.ts +179 -0
- package/dist/areas.d.ts +149 -0
- package/dist/attacks.d.ts +66 -0
- package/dist/attributes.d.ts +66 -0
- package/dist/brain-BvVdUhX8.js +7659 -0
- package/dist/brain-BvVdUhX8.js.map +1 -0
- package/dist/builds-C6LfgvEU.js +460 -0
- package/dist/builds-C6LfgvEU.js.map +1 -0
- package/dist/bytes.d.ts +6 -0
- package/dist/camps.d.ts +95 -0
- package/dist/capture.d.ts +131 -0
- package/dist/cellLayers.d.ts +75 -0
- package/dist/cinematics.d.ts +194 -0
- package/dist/clock.d.ts +32 -0
- package/dist/commands.d.ts +235 -0
- package/dist/components.d.ts +184 -0
- package/dist/computers.d.ts +108 -0
- package/dist/concealment.d.ts +61 -0
- package/dist/coreComponents-syuh2fPG.js +5149 -0
- package/dist/coreComponents-syuh2fPG.js.map +1 -0
- package/dist/coreComponents.d.ts +1 -0
- package/dist/dashing.d.ts +8 -0
- package/dist/debug.d.ts +36 -0
- package/dist/debug.js +3 -0
- package/dist/debugCommands-CzjrSm66.js +14739 -0
- package/dist/debugCommands-CzjrSm66.js.map +1 -0
- package/dist/debugCommands.d.ts +213 -0
- package/dist/debugging.d.ts +10 -0
- package/dist/definitions.d.ts +43 -0
- package/dist/defs.d.ts +1162 -0
- package/dist/denying.d.ts +81 -0
- package/dist/destructibles.d.ts +27 -0
- package/dist/determinism-BCymQwiE.js +518 -0
- package/dist/determinism-BCymQwiE.js.map +1 -0
- package/dist/determinism.d.ts +121 -0
- package/dist/dialog.d.ts +94 -0
- package/dist/effectRefs.d.ts +16 -0
- package/dist/effects.d.ts +80 -0
- package/dist/endings.d.ts +22 -0
- package/dist/engagement.d.ts +108 -0
- package/dist/engineEvents.d.ts +710 -0
- package/dist/entity.d.ts +409 -0
- package/dist/entityIndex.d.ts +50 -0
- package/dist/errors.d.ts +85 -0
- package/dist/events.d.ts +97 -0
- package/dist/exactMath.d.ts +63 -0
- package/dist/features.d.ts +10 -0
- package/dist/fields.d.ts +114 -0
- package/dist/fixture.d.ts +17 -0
- package/dist/fog.d.ts +110 -0
- package/dist/formation.d.ts +111 -0
- package/dist/friendlyFire.d.ts +26 -0
- package/dist/game-DiJEN-Ks.js +5800 -0
- package/dist/game-DiJEN-Ks.js.map +1 -0
- package/dist/game.d.ts +501 -0
- package/dist/gameData.d.ts +325 -0
- package/dist/gameFiles.d.ts +26 -0
- package/dist/gates.d.ts +28 -0
- package/dist/generate.d.ts +403 -0
- package/dist/generateWorker.d.ts +39 -0
- package/dist/grid.d.ts +53 -0
- package/dist/ground.d.ts +127 -0
- package/dist/groundSpeed.d.ts +22 -0
- package/dist/hero.d.ts +177 -0
- package/dist/home.d.ts +14 -0
- package/dist/hooks.d.ts +780 -0
- package/dist/hotReload.d.ts +76 -0
- package/dist/ids.d.ts +26 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +4701 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect-D9dWHmZ0.js +491 -0
- package/dist/inspect-D9dWHmZ0.js.map +1 -0
- package/dist/inspect.d.ts +176 -0
- package/dist/inventory.d.ts +18 -0
- package/dist/items.d.ts +91 -0
- package/dist/lanes.d.ts +213 -0
- package/dist/life.d.ts +53 -0
- package/dist/local-Dkovq42k.js +1379 -0
- package/dist/local-Dkovq42k.js.map +1 -0
- package/dist/log.d.ts +140 -0
- package/dist/manaBurn.d.ts +39 -0
- package/dist/map/file.d.ts +114 -0
- package/dist/map/images.d.ts +18 -0
- package/dist/map/look.d.ts +47 -0
- package/dist/map/pathProfile.d.ts +13 -0
- package/dist/map/resize.d.ts +54 -0
- package/dist/map/schema.d.ts +10 -0
- package/dist/map/terrain.d.ts +32 -0
- package/dist/map/types.d.ts +613 -0
- package/dist/map.d.ts +7 -0
- package/dist/mapCheck/cells.d.ts +19 -0
- package/dist/mapCheck/rule.d.ts +30 -0
- package/dist/mapCheck/rules/names.d.ts +3 -0
- package/dist/mapCheck/rules/objects.d.ts +3 -0
- package/dist/mapCheck/rules/paths.d.ts +3 -0
- package/dist/mapCheck/rules/players.d.ts +3 -0
- package/dist/mapCheck/rules/races.d.ts +3 -0
- package/dist/mapCheck/rules/random.d.ts +3 -0
- package/dist/mapCheck/rules/regions.d.ts +3 -0
- package/dist/mapCheck/rules/targets.d.ts +3 -0
- package/dist/mapCheck/rules/walking.d.ts +3 -0
- package/dist/mapCheck.d.ts +23 -0
- package/dist/mapFields.d.ts +364 -0
- package/dist/mapFile.d.ts +23 -0
- package/dist/mapMeasure.d.ts +95 -0
- package/dist/mapMerge.d.ts +19 -0
- package/dist/mapObjectives.d.ts +82 -0
- package/dist/mapObjects.d.ts +1 -0
- package/dist/math.d.ts +44 -0
- package/dist/messages.d.ts +150 -0
- package/dist/missionRules-C-bGxm0Q.js +523 -0
- package/dist/missionRules-C-bGxm0Q.js.map +1 -0
- package/dist/missionRules.d.ts +179 -0
- package/dist/mods.d.ts +146 -0
- package/dist/names.d.ts +93 -0
- package/dist/navigation/agentParams.d.ts +4 -0
- package/dist/navigation/air.d.ts +89 -0
- package/dist/navigation/builds.d.ts +140 -0
- package/dist/navigation/groundAreas.d.ts +23 -0
- package/dist/navigation/navigation.d.ts +127 -0
- package/dist/navigation/recast.d.ts +214 -0
- package/dist/navigation/sidePaths.d.ts +69 -0
- package/dist/navigation/sized.d.ts +1 -0
- package/dist/navigation-worker.js +11 -0
- package/dist/navigation-worker.js.map +1 -0
- package/dist/nearest.d.ts +8 -0
- package/dist/noise.d.ts +12 -0
- package/dist/objectives.d.ts +93 -0
- package/dist/orders/basic.d.ts +96 -0
- package/dist/orders/build.d.ts +91 -0
- package/dist/orders/cast.d.ts +105 -0
- package/dist/orders/facts.d.ts +14 -0
- package/dist/orders/harvest.d.ts +94 -0
- package/dist/orders/idle.d.ts +7 -0
- package/dist/orders/items.d.ts +14 -0
- package/dist/orders/queue.d.ts +24 -0
- package/dist/pack.schema.json +6621 -0
- package/dist/packs/director/index.d.ts +81 -0
- package/dist/packs/heroes/campaign.d.ts +137 -0
- package/dist/packs/heroes/carry.d.ts +59 -0
- package/dist/packs/heroes/commands.d.ts +6 -0
- package/dist/packs/heroes/hire.d.ts +9 -0
- package/dist/packs/heroes/index.d.ts +12 -0
- package/dist/packs/heroes/making.d.ts +16 -0
- package/dist/packs/heroes/respawn.d.ts +42 -0
- package/dist/packs/moba/index.d.ts +34 -0
- package/dist/packs/pack.d.ts +84 -0
- package/dist/packs-g5thvmGn.js +3199 -0
- package/dist/packs-g5thvmGn.js.map +1 -0
- package/dist/packs.d.ts +84 -0
- package/dist/paths.d.ts +51 -0
- package/dist/plain.d.ts +17 -0
- package/dist/player.d.ts +117 -0
- package/dist/portals.d.ts +66 -0
- package/dist/prefabs.d.ts +139 -0
- package/dist/presetBuildings.d.ts +52 -0
- package/dist/presetGround.d.ts +22 -0
- package/dist/profiles-B3NS9jwk.js +335 -0
- package/dist/profiles-B3NS9jwk.js.map +1 -0
- package/dist/races.d.ts +136 -0
- package/dist/randomGroups.d.ts +2 -0
- package/dist/registry.d.ts +464 -0
- package/dist/remains.d.ts +119 -0
- package/dist/replication.d.ts +103 -0
- package/dist/rng.d.ts +17 -0
- package/dist/rules.d.ts +395 -0
- package/dist/rulesCheck.d.ts +37 -0
- package/dist/runes.d.ts +41 -0
- package/dist/save.d.ts +277 -0
- package/dist/scenario.d.ts +56 -0
- package/dist/scheduler.d.ts +67 -0
- package/dist/score.d.ts +306 -0
- package/dist/sequences.d.ts +132 -0
- package/dist/server/doorman.d.ts +21 -0
- package/dist/server/gameServer.d.ts +346 -0
- package/dist/server/index.d.ts +24 -0
- package/dist/server/lobby.d.ts +1 -0
- package/dist/server/log.d.ts +13 -0
- package/dist/server/match.d.ts +24 -0
- package/dist/server/mode.d.ts +168 -0
- package/dist/server/navigation-worker.d.ts +1 -0
- package/dist/server/navigation.d.ts +11 -0
- package/dist/server/ops.d.ts +78 -0
- package/dist/server/packs.d.ts +15 -0
- package/dist/server/ratings.d.ts +62 -0
- package/dist/server/relay.d.ts +34 -0
- package/dist/server/rooms.d.ts +162 -0
- package/dist/server/seat.d.ts +2 -0
- package/dist/server/seatSettings.d.ts +15 -0
- package/dist/server/secret.d.ts +5 -0
- package/dist/server/start.d.ts +54 -0
- package/dist/server/websocket.d.ts +74 -0
- package/dist/server.js +2861 -0
- package/dist/server.js.map +1 -0
- package/dist/session/can.d.ts +20 -0
- package/dist/session/commandReplay.d.ts +92 -0
- package/dist/session/debug.d.ts +65 -0
- package/dist/session/delay.d.ts +15 -0
- package/dist/session/delta.d.ts +98 -0
- package/dist/session/entityState.d.ts +1 -0
- package/dist/session/local.d.ts +214 -0
- package/dist/session/memory.d.ts +27 -0
- package/dist/session/protocol.d.ts +1155 -0
- package/dist/session/remote.d.ts +530 -0
- package/dist/session/replay.d.ts +114 -0
- package/dist/session/seed.d.ts +7 -0
- package/dist/session/share.d.ts +78 -0
- package/dist/session/socket.d.ts +8 -0
- package/dist/session/wire.d.ts +153 -0
- package/dist/session/worker.d.ts +227 -0
- package/dist/shared.d.ts +171 -0
- package/dist/sides.d.ts +75 -0
- package/dist/sight.d.ts +27 -0
- package/dist/simulate-oXBiw1Ff.js +167 -0
- package/dist/simulate-oXBiw1Ff.js.map +1 -0
- package/dist/simulate-worker.d.ts +1 -0
- package/dist/simulate-worker.js +28 -0
- package/dist/simulate-worker.js.map +1 -0
- package/dist/simulate.d.ts +154 -0
- package/dist/smart.d.ts +9 -0
- package/dist/spatial.d.ts +62 -0
- package/dist/spawning.d.ts +33 -0
- package/dist/stances.d.ts +76 -0
- package/dist/start.d.ts +71 -0
- package/dist/stats.d.ts +122 -0
- package/dist/summons.d.ts +63 -0
- package/dist/systems/abilities.d.ts +3 -0
- package/dist/systems/combat.d.ts +71 -0
- package/dist/systems/combatTold.d.ts +14 -0
- package/dist/systems/construction.d.ts +34 -0
- package/dist/systems/economy.d.ts +21 -0
- package/dist/systems/fog.d.ts +19 -0
- package/dist/systems/life.d.ts +3 -0
- package/dist/systems/movement.d.ts +105 -0
- package/dist/systems/orders.d.ts +7 -0
- package/dist/systems/production.d.ts +49 -0
- package/dist/systems/productionQueue.d.ts +40 -0
- package/dist/systems/victory.d.ts +14 -0
- package/dist/talk.d.ts +65 -0
- package/dist/tech.d.ts +62 -0
- package/dist/techTree.d.ts +38 -0
- package/dist/terrain.d.ts +173 -0
- package/dist/terrainSave.d.ts +29 -0
- package/dist/testing.d.ts +58 -0
- package/dist/testing.js +822 -0
- package/dist/testing.js.map +1 -0
- package/dist/tiles.d.ts +34 -0
- package/dist/timers.d.ts +146 -0
- package/dist/tomes.d.ts +48 -0
- package/dist/trade.d.ts +28 -0
- package/dist/transport.d.ts +76 -0
- package/dist/tunables.d.ts +45 -0
- package/dist/upkeep.d.ts +16 -0
- package/dist/veterancy.d.ts +80 -0
- package/dist/walls.d.ts +86 -0
- package/dist/world.d.ts +1118 -0
- package/package.json +80 -5
- package/README.md +0 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { GamePack } from './packs/pack';
|
|
2
|
+
import type { Entity, PlayerId } from './entity';
|
|
3
|
+
import { type HookDef } from './hooks';
|
|
4
|
+
import type { World } from './world';
|
|
5
|
+
/**
|
|
6
|
+
* Denying, as in a MOBA (features-plan E): a player may tell their units to attack one of their own side's, which
|
|
7
|
+
* they never do by themselves, once it is low enough on health that the other side would soon have it: the last hit
|
|
8
|
+
* on it is theirs, not the enemy's. Hooks for `GameModule.hooks`:
|
|
9
|
+
*
|
|
10
|
+
* hooks: [...denying({ below: 0.5 })]
|
|
11
|
+
*
|
|
12
|
+
* Only an attack a player orders (`CanAttack.ordered`): units never pick their own side to attack, nor fight back at it
|
|
13
|
+
*
|
|
14
|
+
* @beta
|
|
15
|
+
*/
|
|
16
|
+
export interface DenyOptions {
|
|
17
|
+
/** How low on health, of its most, one of one's own side has to be to be attacked: below half when left out */
|
|
18
|
+
below?: number;
|
|
19
|
+
/**
|
|
20
|
+
* What of one's own side may be attacked, past `below`: units, not heroes nor structures, when left out. A game's own,
|
|
21
|
+
* to let towers be denied, or only creeps
|
|
22
|
+
*/
|
|
23
|
+
denies?: (world: World, attacker: Entity, target: Entity) => boolean;
|
|
24
|
+
/** The id of the hook, to replace or leave out: `deny` when left out */
|
|
25
|
+
id?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether an attack on one's own side is a deny the rules allow (`DenyOptions`): told to, low enough, of what may be
|
|
29
|
+
*
|
|
30
|
+
* @beta
|
|
31
|
+
*/
|
|
32
|
+
export declare function mayDeny(world: World, attacker: Entity, target: Entity, options?: DenyOptions): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The hooks of denying (see `DenyOptions`)
|
|
35
|
+
*
|
|
36
|
+
* @beta
|
|
37
|
+
*/
|
|
38
|
+
export declare function denying(options?: DenyOptions): HookDef[];
|
|
39
|
+
/**
|
|
40
|
+
* What a game counts of last hits and denies (`withLastHits`)
|
|
41
|
+
*
|
|
42
|
+
* @beta
|
|
43
|
+
*/
|
|
44
|
+
export interface LastHitOptions {
|
|
45
|
+
/** The rule of denying (`denying`), or `false` for a game that only counts last hits: the engine's when left out */
|
|
46
|
+
deny?: DenyOptions | false;
|
|
47
|
+
/**
|
|
48
|
+
* The part of a unit's experience the enemy heroes near it still get when it is denied, shared as for a kill: a half
|
|
49
|
+
* when left out (none for a kill by the enemy would be no deny at all; all of it, no point in denying)
|
|
50
|
+
*/
|
|
51
|
+
experience?: number;
|
|
52
|
+
/** Which deaths count, as last hits or denies: units, not heroes nor structures, when left out */
|
|
53
|
+
counts?: (world: World, dead: Entity) => boolean;
|
|
54
|
+
}
|
|
55
|
+
/** Every player's last hits and denies, by player id: saved with the game */
|
|
56
|
+
export declare const counted: import("./components").Component<Record<string, {
|
|
57
|
+
lastHits: number;
|
|
58
|
+
denies: number;
|
|
59
|
+
}>, Record<string, {
|
|
60
|
+
lastHits: number;
|
|
61
|
+
denies: number;
|
|
62
|
+
}>>;
|
|
63
|
+
/**
|
|
64
|
+
* A player's last hits (enemy units they gave the killing blow to) and denies (their own side's), as counted
|
|
65
|
+
* (`withLastHits`)
|
|
66
|
+
*
|
|
67
|
+
* @beta
|
|
68
|
+
*/
|
|
69
|
+
export declare function lastHitsOf(world: World, player: PlayerId): {
|
|
70
|
+
lastHits: number;
|
|
71
|
+
denies: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* A game where last hits and denies count, as in a MOBA: each player's are counted (`lastHitsOf`, saved), a unit
|
|
75
|
+
* denied gives no bounty (as one killed by its own side never does) and only a part of its experience to the enemy
|
|
76
|
+
* heroes near it, and its death says so to the screen (`how: 'denied'`, which the client shows over it). With the rule
|
|
77
|
+
* of denying (`denying`) unless told otherwise
|
|
78
|
+
*
|
|
79
|
+
* @beta
|
|
80
|
+
*/
|
|
81
|
+
export declare function withLastHits(options?: LastHitOptions): GamePack;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Entity } from './entity';
|
|
2
|
+
import type { GamePack } from './packs/pack';
|
|
3
|
+
import type { World } from './world';
|
|
4
|
+
/**
|
|
5
|
+
* The destructibles pack's id
|
|
6
|
+
*
|
|
7
|
+
* @beta
|
|
8
|
+
*/
|
|
9
|
+
export declare const DESTRUCTIBLES = "core.destructibles";
|
|
10
|
+
/**
|
|
11
|
+
* What the destructibles pack is made with (`withDestructibles`)
|
|
12
|
+
*
|
|
13
|
+
* @beta
|
|
14
|
+
*/
|
|
15
|
+
export interface DestructiblesOptions {
|
|
16
|
+
/** Whether something is brought down by being attacked when told to, in code: its type's `destructible` when left out */
|
|
17
|
+
destructible?: (world: World, entity: Entity) => boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Destructibles, as in classic RTS games: what is `destructible` (`UnitTypeDef.destructible`, a building of the neutral player
|
|
21
|
+
* placed on the map: rocks, a barricade, a gate to break down) stands in the way until it is brought down, and any unit
|
|
22
|
+
* whose attack reaches it attacks it when told to (the attack command), never of its own accord, as trees are felled
|
|
23
|
+
* by what fells them. A gate that opens and shuts is the map's (`setGate`); one that is broken down is a destructible
|
|
24
|
+
*
|
|
25
|
+
* @beta
|
|
26
|
+
*/
|
|
27
|
+
export declare function withDestructibles(options?: DestructiblesOptions): GamePack;
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { Cc as xpWorth, Gc as engineMessage, Ju as unitHired, Kn as worthless, Ml as teamMemory, Na as index, Qr as defineHook, Rn as corpseSystem, Ua as string, Wd as distSq, aa as buy, ac as defaultHeroRules, cl as fighting, cr as producers, dc as heroesOf, el as shortOf, fc as learn, ga as shopStock, ia as atShop, ir as leastBusy, ja as id, lc as heroState, li as enqueueRevive, ma as sell, mc as maxLevelOf, oc as grantXp, or as owned, qd as hashText, rc as HEROES, rr as known, sc as heroOf, tl as spend, tr as defineCommand, za as optional } from "./debugCommands-CzjrSm66.js";
|
|
2
|
+
import { o as loadWorld } from "./game-DiJEN-Ks.js";
|
|
3
|
+
import { _t as campHooks, ct as saveWorld, it as encodeSave, rt as decodeSave, xt as campRespawnSystem } from "./coreComponents-syuh2fPG.js";
|
|
4
|
+
//#region src/packs/heroes/hire.ts
|
|
5
|
+
/**
|
|
6
|
+
* Whether someone of a player's stands at a place that hires (`UnitTypeDef.hires`) to hire there: any unit of theirs
|
|
7
|
+
* close enough, as at a shop. The first one found
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
function hirerAt(world, shop, player) {
|
|
12
|
+
for (const entity of world.index.ownedBy(player)) if (entity.alive && !entity.hidden && entity.def.kind === "unit" && atShop(entity, shop)) return entity;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Why a player cannot hire a unit at a place now, as the message the player is told: none of theirs there, a hero
|
|
16
|
+
* they have already (a hero is hired once), what it costs. Nothing when they can
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
function hireRefusal(world, shop, unit, player) {
|
|
21
|
+
if (!shop.def.hires?.includes(unit) || !world.registry.units.has(unit)) return engineMessage("cannotDoThat");
|
|
22
|
+
if (!shop.alive || !shop.operational || world.areEnemies(shop.owner, player)) return engineMessage("cannotDoThat");
|
|
23
|
+
const def = world.registry.unit(unit);
|
|
24
|
+
if (def.hero && heroesOf(world, player).some((hero) => hero.def.id === unit)) return engineMessage("alreadyHired", { unit });
|
|
25
|
+
if (!hirerAt(world, shop, player)) return engineMessage("unitAtShop");
|
|
26
|
+
const short = shortOf(world.player(player).resources, def.cost);
|
|
27
|
+
if (short) return engineMessage("notEnough", { resource: short });
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hires a unit at a place that hires for a player: a tavern's hero, a dock's boat. Paid for, and made next to the
|
|
31
|
+
* place, a boat on the water nearest it. The unit, or nothing with the player told why
|
|
32
|
+
*
|
|
33
|
+
* @beta
|
|
34
|
+
*/
|
|
35
|
+
function hire(world, shop, unit, player) {
|
|
36
|
+
const refusal = hireRefusal(world, shop, unit, player);
|
|
37
|
+
if (refusal) {
|
|
38
|
+
world.message(player, refusal);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const def = world.registry.unit(unit);
|
|
42
|
+
spend(world, player, def.cost, "hire", unit);
|
|
43
|
+
const hired = world.spawn(unit, player, {
|
|
44
|
+
x: shop.pos.x,
|
|
45
|
+
z: shop.pos.z + shop.def.radius + def.radius + .5
|
|
46
|
+
});
|
|
47
|
+
world.events.emit(unitHired, {
|
|
48
|
+
shop,
|
|
49
|
+
unit: hired,
|
|
50
|
+
player
|
|
51
|
+
});
|
|
52
|
+
return hired;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/packs/heroes/commands.ts
|
|
56
|
+
/**
|
|
57
|
+
* The heroes pack's commands: buying and selling at a shop, hiring, putting a point into an ability, bringing a hero
|
|
58
|
+
* back at an altar. Taken by a world whose game has the pack on (`withHeroes`); a game without it refuses them as it
|
|
59
|
+
* does any command it does not know. Their type is among `Command`s, as the pack adds it (`Commands`)
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Buys from a shop, for the hero that is named, or else the one of the player that stands closest to it
|
|
63
|
+
*
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
var buyCommand = defineCommand("buy", {
|
|
67
|
+
shop: id,
|
|
68
|
+
item: string,
|
|
69
|
+
hero: optional(id)
|
|
70
|
+
}, (world, player, command) => {
|
|
71
|
+
const shop = known(world, player, command.shop);
|
|
72
|
+
const [buyer] = command.hero === void 0 ? [] : owned(world, player, [command.hero]);
|
|
73
|
+
if (shop && !world.areEnemies(shop.owner, player)) buy(world, shop, command.item, player, buyer);
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Hires a unit at a place that hires, a tavern or a dock: someone of the player's has to be there
|
|
77
|
+
*
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
var hireCommand = defineCommand("hire", {
|
|
81
|
+
shop: id,
|
|
82
|
+
unit: string
|
|
83
|
+
}, (world, player, command) => {
|
|
84
|
+
const shop = known(world, player, command.shop);
|
|
85
|
+
if (shop) hire(world, shop, command.unit, player);
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* Gives something a hero carries back at a shop nearby, for a part of its price
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
var sellCommand = defineCommand("sell", {
|
|
93
|
+
entity: id,
|
|
94
|
+
item: index
|
|
95
|
+
}, (world, player, command) => {
|
|
96
|
+
const [seller] = owned(world, player, [command.entity]);
|
|
97
|
+
if (seller) sell(world, seller, command.item);
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Puts a point a hero has to spend into an ability
|
|
101
|
+
*
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
var learnCommand = defineCommand("learn", {
|
|
105
|
+
entity: id,
|
|
106
|
+
ability: string
|
|
107
|
+
}, (world, player, command) => {
|
|
108
|
+
const [hero] = owned(world, player, [command.entity]);
|
|
109
|
+
if (hero) learn(world, hero, command.ability);
|
|
110
|
+
});
|
|
111
|
+
/** @internal */
|
|
112
|
+
var reviveCommand = defineCommand("revive", {
|
|
113
|
+
...producers,
|
|
114
|
+
hero: id
|
|
115
|
+
}, (world, player, command) => {
|
|
116
|
+
const building = leastBusy(world, player, command, (entity) => !!entity.def.revives);
|
|
117
|
+
if (building) enqueueRevive(world, building, command.hero);
|
|
118
|
+
});
|
|
119
|
+
/**
|
|
120
|
+
* The commands of the heroes pack (`withHeroes`): buying, hiring, selling, learning, bringing a hero back. A game
|
|
121
|
+
* without the pack refuses them as it does any command it does not know
|
|
122
|
+
*
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
var heroCommands = [
|
|
126
|
+
buyCommand,
|
|
127
|
+
hireCommand,
|
|
128
|
+
sellCommand,
|
|
129
|
+
learnCommand,
|
|
130
|
+
reviveCommand
|
|
131
|
+
];
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/packs/heroes/making.ts
|
|
134
|
+
/**
|
|
135
|
+
* Makes a unit just made a hero, as its type says (`UnitTypeDef.hero`): level 1, a point to spend, the abilities it can
|
|
136
|
+
* learn (none learned) and the spells it starts with, those it has not got already. What `World.spawn` does for a
|
|
137
|
+
* hero's type, and `morph` for what becomes one
|
|
138
|
+
*
|
|
139
|
+
* @beta
|
|
140
|
+
*/
|
|
141
|
+
function makeHero(world, entity) {
|
|
142
|
+
const def = entity.def.hero;
|
|
143
|
+
if (!def) return;
|
|
144
|
+
entity.set(heroState, {
|
|
145
|
+
level: 1,
|
|
146
|
+
xp: 0,
|
|
147
|
+
points: 1
|
|
148
|
+
});
|
|
149
|
+
const add = (id, level) => {
|
|
150
|
+
if (!entity.abilities.some((state) => state.id === id)) entity.addAbility({
|
|
151
|
+
id: world.registry.ability(id).id,
|
|
152
|
+
level,
|
|
153
|
+
cooldown: 0,
|
|
154
|
+
autocast: false
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
for (const id of def.learns) add(id, 0);
|
|
158
|
+
for (const id of def.spells ?? []) add(id, 1);
|
|
159
|
+
}
|
|
160
|
+
/** Shares the experience for a kill between the heroes of the killer's side that were around */
|
|
161
|
+
function awardKill(world, dead, killer) {
|
|
162
|
+
if (!killer || !world.areEnemies(killer.owner, dead.owner)) return;
|
|
163
|
+
const worth = xpWorth(world, dead);
|
|
164
|
+
if (worth <= 0) return;
|
|
165
|
+
const heroes = [];
|
|
166
|
+
const { xpRange } = world.rules.heroes;
|
|
167
|
+
for (const player of world.players.values()) {
|
|
168
|
+
if (!world.areAllies(player.id, killer.owner)) continue;
|
|
169
|
+
for (const entity of world.index.with(heroState, player.id)) {
|
|
170
|
+
if (!entity.alive || heroOf(entity).level >= maxLevelOf(world, entity)) continue;
|
|
171
|
+
if (distSq(entity.pos, dead.pos) <= xpRange * xpRange) heroes.push(entity);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const hero of heroes) grantXp(world, hero, worth / heroes.length, dead);
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/packs/heroes/index.ts
|
|
178
|
+
/** A shop's wares, each as many as it stocks; what it stocked already as it was, where it sells it still */
|
|
179
|
+
function stockShop(world, entity) {
|
|
180
|
+
const sells = entity.def.sells ?? [];
|
|
181
|
+
const had = entity.get(shopStock);
|
|
182
|
+
if (!sells.length) {
|
|
183
|
+
if (had) entity.remove(shopStock);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
entity.set(shopStock, sells.map((item) => {
|
|
187
|
+
const kept = had?.find((stock) => stock.item === item);
|
|
188
|
+
if (kept) return kept;
|
|
189
|
+
const stock = world.registry.item(item).stock;
|
|
190
|
+
return {
|
|
191
|
+
item,
|
|
192
|
+
count: stock?.max ?? 1,
|
|
193
|
+
restock: stock?.restock ?? 60
|
|
194
|
+
};
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
/** What the pack does as things are made, change type, die and fall: heroes, their experience, shops' wares */
|
|
198
|
+
var heroHooks = [
|
|
199
|
+
defineHook({
|
|
200
|
+
hook: "made",
|
|
201
|
+
id: "hero",
|
|
202
|
+
run(world, entity) {
|
|
203
|
+
if (entity.def.hero) makeHero(world, entity);
|
|
204
|
+
stockShop(world, entity);
|
|
205
|
+
}
|
|
206
|
+
}),
|
|
207
|
+
defineHook({
|
|
208
|
+
hook: "morphed",
|
|
209
|
+
id: "hero",
|
|
210
|
+
run(world, { entity }) {
|
|
211
|
+
if (entity.def.hero && !entity.has(heroState)) makeHero(world, entity);
|
|
212
|
+
if (entity.def.sells?.length || entity.has(shopStock)) stockShop(world, entity);
|
|
213
|
+
}
|
|
214
|
+
}),
|
|
215
|
+
defineHook({
|
|
216
|
+
hook: "death",
|
|
217
|
+
id: "experience",
|
|
218
|
+
run(world, { entity, killer }) {
|
|
219
|
+
if (worthless(entity)) return;
|
|
220
|
+
awardKill(world, entity, killer);
|
|
221
|
+
}
|
|
222
|
+
}),
|
|
223
|
+
defineHook({
|
|
224
|
+
hook: "fallenHero",
|
|
225
|
+
id: "heroesStay",
|
|
226
|
+
run(_world, event) {
|
|
227
|
+
if (event.entity.has(heroState)) event.stays = true;
|
|
228
|
+
}
|
|
229
|
+
}),
|
|
230
|
+
...campHooks
|
|
231
|
+
];
|
|
232
|
+
/**
|
|
233
|
+
* A hero's stats grow with its level, as its type says (`UnitTypeDef.hero.growth`): stamped with the level past the
|
|
234
|
+
* first, so that a level set however it is set is added up again
|
|
235
|
+
*/
|
|
236
|
+
var heroGrowth = {
|
|
237
|
+
id: "heroGrowth",
|
|
238
|
+
stamp(entity) {
|
|
239
|
+
if (!entity.def.hero) return 0;
|
|
240
|
+
const level = heroOf(entity)?.level ?? 1;
|
|
241
|
+
return level > 1 ? level : 0;
|
|
242
|
+
},
|
|
243
|
+
add(_world, entity, sum) {
|
|
244
|
+
const growth = entity.def.hero?.growth;
|
|
245
|
+
const hero = growth && heroOf(entity);
|
|
246
|
+
if (hero && hero.level > 1) sum.add(growth, hero.level - 1);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
/** Camps come back as the engine's own did: in the clean-up, before the corpses go */
|
|
250
|
+
var campsComeBack = {
|
|
251
|
+
...campRespawnSystem,
|
|
252
|
+
before: [corpseSystem.name]
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* The heroes pack: what makes a game a classic hero RTS, opted into (`GameModule.packs: [withHeroes()]`). Heroes, with
|
|
256
|
+
* their experience, levels, points and abilities to learn, brought back at an altar (`revive`); creeps' camps that guard
|
|
257
|
+
* their ground, sleep at night and come back (`Camp.respawn`); shops that sell (`buy`, `sell`) and places that hire
|
|
258
|
+
* (`hire`). `rules` change its numbers (`HeroRules`: how many heroes, the experience a level takes), the classic RTS numbers for
|
|
259
|
+
* those left out. A game without it pays nothing for any of it: no hero is made of a hero's type, no command of these
|
|
260
|
+
* is taken, no camp is looked at
|
|
261
|
+
*
|
|
262
|
+
* @beta
|
|
263
|
+
*/
|
|
264
|
+
function withHeroes(rules = {}) {
|
|
265
|
+
for (const key of Object.keys(rules)) if (!(key in defaultHeroRules)) throw new Error(`"${key}" is not a rule of the heroes pack`);
|
|
266
|
+
return {
|
|
267
|
+
id: HEROES,
|
|
268
|
+
options: rules,
|
|
269
|
+
rules: () => ({ heroes: rules }),
|
|
270
|
+
commands: [...heroCommands],
|
|
271
|
+
systems: [campsComeBack],
|
|
272
|
+
hooks: heroHooks,
|
|
273
|
+
statParts: [heroGrowth]
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/determinism.ts
|
|
278
|
+
/**
|
|
279
|
+
* Whether a game comes out the same (determinism plan D1): the simulation's state as data and as a hash, and a check
|
|
280
|
+
* that runs a world twice, and on from a save, and says the first tick and the first thing they differ at. What a
|
|
281
|
+
* replay of commands, a save that goes on exactly and a game the same in every browser stand on
|
|
282
|
+
*/
|
|
283
|
+
/**
|
|
284
|
+
* The simulation's state as plain data: its save, which holds all of it, but what each team's screens remember of the
|
|
285
|
+
* map, which is the watchers' (`teamMemory`)
|
|
286
|
+
*
|
|
287
|
+
* @beta
|
|
288
|
+
*/
|
|
289
|
+
function worldState(world) {
|
|
290
|
+
return decided(saveWorld(world, { reseat: false }), world.registry);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* A save as what decides the game: without what each team's screens remember of the map (`teamMemory`: kept in the
|
|
294
|
+
* world for who comes back, written by whoever watches it, and read by nothing that decides), and, with the rules, the
|
|
295
|
+
* components nobody would have made had nobody asked for them (`unasked`)
|
|
296
|
+
*/
|
|
297
|
+
function decided(save, registry) {
|
|
298
|
+
const { [teamMemory.id]: _memory, ...state } = save.state;
|
|
299
|
+
const kept = (components) => registry ? unasked(registry, components) : components;
|
|
300
|
+
return {
|
|
301
|
+
...save,
|
|
302
|
+
state: kept(state),
|
|
303
|
+
players: save.players.map((player) => ({
|
|
304
|
+
...player,
|
|
305
|
+
state: kept(player.state)
|
|
306
|
+
})),
|
|
307
|
+
entities: save.entities.map((entity) => entity.components ? {
|
|
308
|
+
...entity,
|
|
309
|
+
components: kept(entity.components)
|
|
310
|
+
} : entity)
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Where two saves of a game first differ, as `checkDeterminism` looks (what decides first: the tick, the random state,
|
|
315
|
+
* the players, the entities): the path to it (`entities[id 11].pos.x`) and what each holds there, or nothing when they
|
|
316
|
+
* are the same. What the screens remember of the map, and the hash of the rules they were made with, are left out;
|
|
317
|
+
* with the game's `registry`, so are components that hold what they are made with (a screen asking for one makes it).
|
|
318
|
+
* What a replay that came apart says at its checkpoints (`runReplay`), and `v12 save diff`
|
|
319
|
+
*
|
|
320
|
+
* @beta
|
|
321
|
+
*/
|
|
322
|
+
function saveDifference(a, b, registry) {
|
|
323
|
+
const ordered = (save) => {
|
|
324
|
+
const { hash: _rules, ...state } = decided(save, registry);
|
|
325
|
+
return {
|
|
326
|
+
...Object.fromEntries(FIRST.map((key) => [key, state[key]])),
|
|
327
|
+
...state
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
return firstDifference(ordered(a), ordered(b));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Components as they would be had nobody asked for one that was not there: one that holds what its component makes
|
|
334
|
+
* when asked for (its `default`) is left out. Asking makes one, and a watcher's screen asks for what it shows (a
|
|
335
|
+
* player's objectives), so a world watched and the same world unwatched differ in that alone
|
|
336
|
+
*/
|
|
337
|
+
function unasked(registry, components) {
|
|
338
|
+
const kept = {};
|
|
339
|
+
for (const [id, value] of Object.entries(components)) {
|
|
340
|
+
const made = registry.component(id)?.default?.();
|
|
341
|
+
if (made !== void 0 && JSON.stringify(made) === JSON.stringify(value)) continue;
|
|
342
|
+
kept[id] = value;
|
|
343
|
+
}
|
|
344
|
+
return kept;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The simulation's state as a hash: two worlds with the same hash are, to all that is saved of them, the same. Fourteen
|
|
348
|
+
* hex digits (cyrb53, of the save as JSON, but the hash of the rules, which another build of the same code writes
|
|
349
|
+
* otherwise): enough to tell drift, not to stand against someone forging a state
|
|
350
|
+
*
|
|
351
|
+
* @beta
|
|
352
|
+
*/
|
|
353
|
+
var worldHash = (world) => {
|
|
354
|
+
const { hash: _rules, ...state } = worldState(world);
|
|
355
|
+
return hashText(canonical(state));
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* JSON with the keys of every object in order: the same state as text however it was put together (a component a
|
|
359
|
+
* watcher made early comes first in its object, and later where nobody watched)
|
|
360
|
+
*/
|
|
361
|
+
function canonical(value) {
|
|
362
|
+
return JSON.stringify(value, (_key, each) => {
|
|
363
|
+
if (!each || typeof each !== "object" || Array.isArray(each)) return each;
|
|
364
|
+
const sorted = {};
|
|
365
|
+
for (const key of Object.keys(each).sort()) sorted[key] = each[key];
|
|
366
|
+
return sorted;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* A quick hash of what a game that came apart differs in first: the tick, the random state, each player's resources,
|
|
371
|
+
* upgrades and supply, and each entity's id, type, owner, place, facing, health, mana, whether it lives, its orders (how
|
|
372
|
+
* many, and the first's kind and target), its buffs (which, and the time left) and its attack's cooldown and swing. Read
|
|
373
|
+
* off the world as it is, with no save: 0.4 ms at 1000 units a side, where `worldHash` takes 66. What a
|
|
374
|
+
* replay of commands keeps every few seconds and checks as it plays (`recordCommands`), and what `checkDeterminism`
|
|
375
|
+
* compares every tick, for the tick a game came apart at; what it leaves out (components, areas, the fog) the whole
|
|
376
|
+
* state, compared every so often, has
|
|
377
|
+
*
|
|
378
|
+
* @beta
|
|
379
|
+
*/
|
|
380
|
+
function worldFingerprint(world) {
|
|
381
|
+
const values = [world.tick, world.rng.save()];
|
|
382
|
+
for (const player of world.players.values()) {
|
|
383
|
+
values.push(player.id, player.supplyUsed, player.supplyCap);
|
|
384
|
+
for (const key of Object.keys(player.resources).sort()) values.push(player.resources[key] ?? 0);
|
|
385
|
+
for (const [upgrade, level] of player.upgrades) values.push(wordOf(upgrade), level);
|
|
386
|
+
}
|
|
387
|
+
for (const entity of world.entities.values()) {
|
|
388
|
+
values.push(entity.id, wordOf(entity.def.id), entity.owner, entity.pos.x, entity.pos.z, entity.facing, entity.hp, entity.mana, entity.alive ? 1 : 0);
|
|
389
|
+
const order = entity.orders[0];
|
|
390
|
+
values.push(entity.orders.length, order ? wordOf(order.type) : 0, order?.init.target ?? 0);
|
|
391
|
+
values.push(entity.buffs.length);
|
|
392
|
+
for (const buff of entity.buffs) values.push(wordOf(buff.id), buff.remaining);
|
|
393
|
+
if (entity.get(fighting)) values.push(entity.get(fighting).cooldown, entity.get(fighting).swing);
|
|
394
|
+
}
|
|
395
|
+
return hashWords(new Uint32Array(Float64Array.from(values).buffer));
|
|
396
|
+
}
|
|
397
|
+
/** A short text as a number, for the fingerprint: FNV-1a of its characters */
|
|
398
|
+
function wordOf(text) {
|
|
399
|
+
let h = 2166136261;
|
|
400
|
+
for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619);
|
|
401
|
+
return h >>> 0;
|
|
402
|
+
}
|
|
403
|
+
/** cyrb53 of 32-bit words: the bits of numbers, hashed as they are */
|
|
404
|
+
function hashWords(words) {
|
|
405
|
+
let h1 = 3735928559;
|
|
406
|
+
let h2 = 1103547991;
|
|
407
|
+
for (const word of words) {
|
|
408
|
+
h1 = Math.imul(h1 ^ word, 2654435761);
|
|
409
|
+
h2 = Math.imul(h2 ^ word, 1597334677);
|
|
410
|
+
}
|
|
411
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
412
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
413
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0");
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Where two states first differ: the path to it (`entities[id 11].pos.x`) and what each holds there. Nothing when they
|
|
417
|
+
* are the same. Lists of things with an `id` are named by it, the rest by their place
|
|
418
|
+
*
|
|
419
|
+
* @beta
|
|
420
|
+
*/
|
|
421
|
+
function firstDifference(a, b, path = "") {
|
|
422
|
+
if (Object.is(a, b)) return void 0;
|
|
423
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null || Array.isArray(a) !== Array.isArray(b)) return {
|
|
424
|
+
path,
|
|
425
|
+
a,
|
|
426
|
+
b
|
|
427
|
+
};
|
|
428
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
429
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
430
|
+
const each = a[i] ?? b[i];
|
|
431
|
+
const named = each && typeof each === "object" && "id" in each ? `[id ${String(each.id)}]` : `[${i}]`;
|
|
432
|
+
const found = firstDifference(a[i], b[i], `${path}${named}`);
|
|
433
|
+
if (found) return found;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
438
|
+
for (const key of keys) {
|
|
439
|
+
const found = firstDifference(a[key], b[key], path ? `${path}.${key}` : key);
|
|
440
|
+
if (found) return found;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Makes a world twice with `make` (the same seed, the same commands given in it, as a replay would) and runs them side
|
|
445
|
+
* by side, comparing their fingerprints every tick and their states (and the order each player's entities are filed
|
|
446
|
+
* in) every `every` ticks; with `from`, a third is saved at that tick and a fourth loaded
|
|
447
|
+
* from the save, and the two run on side by side. Says, for each, the first tick and thing it differs at, or that it does not. A test of a game's
|
|
448
|
+
* own systems: one that reads `Math.random`, the clock, or the order of something unordered is found here
|
|
449
|
+
*
|
|
450
|
+
* const report = await checkDeterminism(() => makeMyGame(1), { ticks: 1800, from: 300, game: rules })
|
|
451
|
+
* expect(report).toEqual({ twice: null, fromSave: null })
|
|
452
|
+
*
|
|
453
|
+
* @beta
|
|
454
|
+
*/
|
|
455
|
+
async function checkDeterminism(make, options) {
|
|
456
|
+
const { ticks, every = 30, from } = options;
|
|
457
|
+
if (from !== void 0 && !options.game) throw new Error("checkDeterminism: a check from a save needs the game to load it with (game)");
|
|
458
|
+
const first = await make();
|
|
459
|
+
const second = await make();
|
|
460
|
+
const saved = from === void 0 ? void 0 : await make();
|
|
461
|
+
let loaded;
|
|
462
|
+
const report = {
|
|
463
|
+
twice: null,
|
|
464
|
+
...from !== void 0 && { fromSave: null }
|
|
465
|
+
};
|
|
466
|
+
try {
|
|
467
|
+
for (let tick = 1; tick <= ticks; tick++) {
|
|
468
|
+
first.step();
|
|
469
|
+
if (!report.twice) second.step();
|
|
470
|
+
if (saved && !report.fromSave) {
|
|
471
|
+
saved.step();
|
|
472
|
+
loaded?.step();
|
|
473
|
+
if (saved.tick === from) loaded = await loadWorld(decodeSave(encodeSave(saveWorld(saved))), { game: options.game });
|
|
474
|
+
}
|
|
475
|
+
const whole = tick % every === 0;
|
|
476
|
+
if (!report.twice && (whole || worldFingerprint(first) !== worldFingerprint(second))) report.twice = apartAt(first.tick, compared(first), compared(second));
|
|
477
|
+
if (loaded && !report.fromSave && saved.tick > from && (whole || worldFingerprint(saved) !== worldFingerprint(loaded))) report.fromSave = apartAt(saved.tick, compared(saved), compared(loaded));
|
|
478
|
+
if (report.twice && (from === void 0 || report.fromSave)) break;
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
first.dispose();
|
|
482
|
+
second.dispose();
|
|
483
|
+
saved?.dispose();
|
|
484
|
+
loaded?.dispose();
|
|
485
|
+
}
|
|
486
|
+
return report;
|
|
487
|
+
}
|
|
488
|
+
var compared = (world) => ({
|
|
489
|
+
...worldState(world),
|
|
490
|
+
owned: Object.fromEntries([...world.players.keys()].map((player) => [player, [...world.index.ownedBy(player)].map((entity) => entity.id)]))
|
|
491
|
+
});
|
|
492
|
+
/**
|
|
493
|
+
* What is looked at first: what is decided (the random state, the players, the entities, the order they are filed in)
|
|
494
|
+
* before what follows from it (the fog, which units see), so that what is said is where it began
|
|
495
|
+
*/
|
|
496
|
+
var FIRST = [
|
|
497
|
+
"tick",
|
|
498
|
+
"rng",
|
|
499
|
+
"players",
|
|
500
|
+
"entities",
|
|
501
|
+
"owned",
|
|
502
|
+
"actors"
|
|
503
|
+
];
|
|
504
|
+
var apartAt = (tick, a, b) => {
|
|
505
|
+
const ordered = (state) => ({
|
|
506
|
+
...Object.fromEntries(FIRST.map((key) => [key, state[key]])),
|
|
507
|
+
...state
|
|
508
|
+
});
|
|
509
|
+
const found = firstDifference(ordered(a), ordered(b));
|
|
510
|
+
return found ? {
|
|
511
|
+
tick,
|
|
512
|
+
...found
|
|
513
|
+
} : null;
|
|
514
|
+
};
|
|
515
|
+
//#endregion
|
|
516
|
+
export { hirerAt as _, worldHash as a, makeHero as c, hireCommand as d, learnCommand as f, hireRefusal as g, hire as h, worldFingerprint as i, buyCommand as l, sellCommand as m, firstDifference as n, worldState as o, reviveCommand as p, saveDifference as r, withHeroes as s, checkDeterminism as t, heroCommands as u };
|
|
517
|
+
|
|
518
|
+
//# sourceMappingURL=determinism-BCymQwiE.js.map
|