sliver-engine 0.0.1-alpha-4

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 (178) hide show
  1. package/README.md +37 -0
  2. package/dist/Assets/Shapes/Arrow.d.ts +12 -0
  3. package/dist/Assets/Shapes/Arrow.d.ts.map +1 -0
  4. package/dist/Assets/Shapes/Arrow.js +32 -0
  5. package/dist/Assets/Shapes/Arrow.js.map +1 -0
  6. package/dist/Assets/Shapes/index.d.ts +4 -0
  7. package/dist/Assets/Shapes/index.d.ts.map +1 -0
  8. package/dist/Assets/Shapes/index.js +2 -0
  9. package/dist/Assets/Shapes/index.js.map +1 -0
  10. package/dist/CanvasController/SpriteLibrary.d.ts +58 -0
  11. package/dist/CanvasController/SpriteLibrary.d.ts.map +1 -0
  12. package/dist/CanvasController/SpriteLibrary.js +412 -0
  13. package/dist/CanvasController/SpriteLibrary.js.map +1 -0
  14. package/dist/CanvasController/Transformations.d.ts +5 -0
  15. package/dist/CanvasController/Transformations.d.ts.map +1 -0
  16. package/dist/CanvasController/Transformations.js +19 -0
  17. package/dist/CanvasController/Transformations.js.map +1 -0
  18. package/dist/CanvasController/index.d.ts +238 -0
  19. package/dist/CanvasController/index.d.ts.map +1 -0
  20. package/dist/CanvasController/index.js +418 -0
  21. package/dist/CanvasController/index.js.map +1 -0
  22. package/dist/Context/index.d.ts +53 -0
  23. package/dist/Context/index.d.ts.map +1 -0
  24. package/dist/Context/index.js +103 -0
  25. package/dist/Context/index.js.map +1 -0
  26. package/dist/Events/decorators.d.ts +74 -0
  27. package/dist/Events/decorators.d.ts.map +1 -0
  28. package/dist/Events/decorators.js +417 -0
  29. package/dist/Events/decorators.js.map +1 -0
  30. package/dist/Events/index.d.ts +50 -0
  31. package/dist/Events/index.d.ts.map +1 -0
  32. package/dist/Events/index.js +73 -0
  33. package/dist/Events/index.js.map +1 -0
  34. package/dist/Events/keyAccumulator.d.ts +9 -0
  35. package/dist/Events/keyAccumulator.d.ts.map +1 -0
  36. package/dist/Events/keyAccumulator.js +17 -0
  37. package/dist/Events/keyAccumulator.js.map +1 -0
  38. package/dist/Game/Saves/index.d.ts +39 -0
  39. package/dist/Game/Saves/index.d.ts.map +1 -0
  40. package/dist/Game/Saves/index.js +213 -0
  41. package/dist/Game/Saves/index.js.map +1 -0
  42. package/dist/Game/index.d.ts +45 -0
  43. package/dist/Game/index.d.ts.map +1 -0
  44. package/dist/Game/index.js +101 -0
  45. package/dist/Game/index.js.map +1 -0
  46. package/dist/GameObject/Decorators/index.d.ts +81 -0
  47. package/dist/GameObject/Decorators/index.d.ts.map +1 -0
  48. package/dist/GameObject/Decorators/index.js +183 -0
  49. package/dist/GameObject/Decorators/index.js.map +1 -0
  50. package/dist/GameObject/Hitboxes/ColisionHandler.d.ts +23 -0
  51. package/dist/GameObject/Hitboxes/ColisionHandler.d.ts.map +1 -0
  52. package/dist/GameObject/Hitboxes/ColisionHandler.js +458 -0
  53. package/dist/GameObject/Hitboxes/ColisionHandler.js.map +1 -0
  54. package/dist/GameObject/Hitboxes/index.d.ts +50 -0
  55. package/dist/GameObject/Hitboxes/index.d.ts.map +1 -0
  56. package/dist/GameObject/Hitboxes/index.js +306 -0
  57. package/dist/GameObject/Hitboxes/index.js.map +1 -0
  58. package/dist/GameObject/Library/Button.d.ts +15 -0
  59. package/dist/GameObject/Library/Button.d.ts.map +1 -0
  60. package/dist/GameObject/Library/Button.js +45 -0
  61. package/dist/GameObject/Library/Button.js.map +1 -0
  62. package/dist/GameObject/Library/Clickableshape.d.ts +14 -0
  63. package/dist/GameObject/Library/Clickableshape.d.ts.map +1 -0
  64. package/dist/GameObject/Library/Clickableshape.js +36 -0
  65. package/dist/GameObject/Library/Clickableshape.js.map +1 -0
  66. package/dist/GameObject/Library/FloatingView.d.ts +20 -0
  67. package/dist/GameObject/Library/FloatingView.d.ts.map +1 -0
  68. package/dist/GameObject/Library/FloatingView.js +39 -0
  69. package/dist/GameObject/Library/FloatingView.js.map +1 -0
  70. package/dist/GameObject/Library/Group.d.ts +8 -0
  71. package/dist/GameObject/Library/Group.d.ts.map +1 -0
  72. package/dist/GameObject/Library/Group.js +26 -0
  73. package/dist/GameObject/Library/Group.js.map +1 -0
  74. package/dist/GameObject/Library/HoverableShape.d.ts +15 -0
  75. package/dist/GameObject/Library/HoverableShape.d.ts.map +1 -0
  76. package/dist/GameObject/Library/HoverableShape.js +41 -0
  77. package/dist/GameObject/Library/HoverableShape.js.map +1 -0
  78. package/dist/GameObject/Library/SceneTranslator.d.ts +14 -0
  79. package/dist/GameObject/Library/SceneTranslator.d.ts.map +1 -0
  80. package/dist/GameObject/Library/SceneTranslator.js +104 -0
  81. package/dist/GameObject/Library/SceneTranslator.js.map +1 -0
  82. package/dist/GameObject/Library/ScrollView.d.ts +41 -0
  83. package/dist/GameObject/Library/ScrollView.d.ts.map +1 -0
  84. package/dist/GameObject/Library/ScrollView.js +112 -0
  85. package/dist/GameObject/Library/ScrollView.js.map +1 -0
  86. package/dist/GameObject/Library/ShowOnHover.d.ts +13 -0
  87. package/dist/GameObject/Library/ShowOnHover.d.ts.map +1 -0
  88. package/dist/GameObject/Library/ShowOnHover.js +33 -0
  89. package/dist/GameObject/Library/ShowOnHover.js.map +1 -0
  90. package/dist/GameObject/Library/Text.d.ts +25 -0
  91. package/dist/GameObject/Library/Text.d.ts.map +1 -0
  92. package/dist/GameObject/Library/Text.js +35 -0
  93. package/dist/GameObject/Library/Text.js.map +1 -0
  94. package/dist/GameObject/Library/TextBox.d.ts +45 -0
  95. package/dist/GameObject/Library/TextBox.d.ts.map +1 -0
  96. package/dist/GameObject/Library/TextBox.js +170 -0
  97. package/dist/GameObject/Library/TextBox.js.map +1 -0
  98. package/dist/GameObject/Library/index.d.ts +11 -0
  99. package/dist/GameObject/Library/index.d.ts.map +1 -0
  100. package/dist/GameObject/Library/index.js +11 -0
  101. package/dist/GameObject/Library/index.js.map +1 -0
  102. package/dist/GameObject/Walker.d.ts +91 -0
  103. package/dist/GameObject/Walker.d.ts.map +1 -0
  104. package/dist/GameObject/Walker.js +634 -0
  105. package/dist/GameObject/Walker.js.map +1 -0
  106. package/dist/GameObject/index.d.ts +97 -0
  107. package/dist/GameObject/index.d.ts.map +1 -0
  108. package/dist/GameObject/index.js +386 -0
  109. package/dist/GameObject/index.js.map +1 -0
  110. package/dist/Lib/Math.d.ts +2 -0
  111. package/dist/Lib/Math.d.ts.map +1 -0
  112. package/dist/Lib/Math.js +4 -0
  113. package/dist/Lib/Math.js.map +1 -0
  114. package/dist/Lib/MinHeap/index.d.ts +12 -0
  115. package/dist/Lib/MinHeap/index.d.ts.map +1 -0
  116. package/dist/Lib/MinHeap/index.js +64 -0
  117. package/dist/Lib/MinHeap/index.js.map +1 -0
  118. package/dist/Lib/Mixins/index.d.ts +8 -0
  119. package/dist/Lib/Mixins/index.d.ts.map +1 -0
  120. package/dist/Lib/Mixins/index.js +45 -0
  121. package/dist/Lib/Mixins/index.js.map +1 -0
  122. package/dist/Lib/PathfindingProxy/index.d.ts +16 -0
  123. package/dist/Lib/PathfindingProxy/index.d.ts.map +1 -0
  124. package/dist/Lib/PathfindingProxy/index.js +48 -0
  125. package/dist/Lib/PathfindingProxy/index.js.map +1 -0
  126. package/dist/Lib/Vector/index.d.ts +24 -0
  127. package/dist/Lib/Vector/index.d.ts.map +1 -0
  128. package/dist/Lib/Vector/index.js +94 -0
  129. package/dist/Lib/Vector/index.js.map +1 -0
  130. package/dist/Lib/index.d.ts +6 -0
  131. package/dist/Lib/index.d.ts.map +1 -0
  132. package/dist/Lib/index.js +6 -0
  133. package/dist/Lib/index.js.map +1 -0
  134. package/dist/Scenes/SceneManager/Transitions.d.ts +37 -0
  135. package/dist/Scenes/SceneManager/Transitions.d.ts.map +1 -0
  136. package/dist/Scenes/SceneManager/Transitions.js +165 -0
  137. package/dist/Scenes/SceneManager/Transitions.js.map +1 -0
  138. package/dist/Scenes/SceneManager/index.d.ts +24 -0
  139. package/dist/Scenes/SceneManager/index.d.ts.map +1 -0
  140. package/dist/Scenes/SceneManager/index.js +151 -0
  141. package/dist/Scenes/SceneManager/index.js.map +1 -0
  142. package/dist/Scenes/index.d.ts +44 -0
  143. package/dist/Scenes/index.d.ts.map +1 -0
  144. package/dist/Scenes/index.js +252 -0
  145. package/dist/Scenes/index.js.map +1 -0
  146. package/dist/ScriptedEvents/Combinators/index.d.ts +17 -0
  147. package/dist/ScriptedEvents/Combinators/index.d.ts.map +1 -0
  148. package/dist/ScriptedEvents/Combinators/index.js +127 -0
  149. package/dist/ScriptedEvents/Combinators/index.js.map +1 -0
  150. package/dist/ScriptedEvents/Library/TextBoxSequence.d.ts +7 -0
  151. package/dist/ScriptedEvents/Library/TextBoxSequence.d.ts.map +1 -0
  152. package/dist/ScriptedEvents/Library/TextBoxSequence.js +16 -0
  153. package/dist/ScriptedEvents/Library/TextBoxSequence.js.map +1 -0
  154. package/dist/ScriptedEvents/Library/WaitForKeyPress.d.ts +2 -0
  155. package/dist/ScriptedEvents/Library/WaitForKeyPress.d.ts.map +1 -0
  156. package/dist/ScriptedEvents/Library/WaitForKeyPress.js +1 -0
  157. package/dist/ScriptedEvents/Library/WaitForKeyPress.js.map +1 -0
  158. package/dist/ScriptedEvents/Library/WalkCharacter.d.ts +4 -0
  159. package/dist/ScriptedEvents/Library/WalkCharacter.d.ts.map +1 -0
  160. package/dist/ScriptedEvents/Library/WalkCharacter.js +14 -0
  161. package/dist/ScriptedEvents/Library/WalkCharacter.js.map +1 -0
  162. package/dist/ScriptedEvents/Library/index.d.ts +3 -0
  163. package/dist/ScriptedEvents/Library/index.d.ts.map +1 -0
  164. package/dist/ScriptedEvents/Library/index.js +3 -0
  165. package/dist/ScriptedEvents/Library/index.js.map +1 -0
  166. package/dist/ScriptedEvents/index.d.ts +42 -0
  167. package/dist/ScriptedEvents/index.d.ts.map +1 -0
  168. package/dist/ScriptedEvents/index.js +61 -0
  169. package/dist/ScriptedEvents/index.js.map +1 -0
  170. package/dist/SoundManager/index.d.ts +69 -0
  171. package/dist/SoundManager/index.d.ts.map +1 -0
  172. package/dist/SoundManager/index.js +208 -0
  173. package/dist/SoundManager/index.js.map +1 -0
  174. package/dist/index.d.ts +19 -0
  175. package/dist/index.d.ts.map +1 -0
  176. package/dist/index.js +19 -0
  177. package/dist/index.js.map +1 -0
  178. package/package.json +36 -0
@@ -0,0 +1,634 @@
1
+ import { MinHeap } from "../Lib/MinHeap";
2
+ import { PathfindingProxy } from "../Lib/PathfindingProxy";
3
+ import { Vector } from "../Lib/Vector";
4
+ import { CircleHitbox, SquareHitbox } from "./Hitboxes";
5
+ const normalizePathfindingOptions = (options) => {
6
+ const gridCellSize = Math.max(1, Math.floor(options?.gridCellSize ?? 16));
7
+ const recalculateEveryTicks = options?.recalculateEveryTicks == null
8
+ ? Number.POSITIVE_INFINITY
9
+ : Math.max(1, Math.floor(options.recalculateEveryTicks));
10
+ const maxExpandedNodes = options?.maxExpandedNodes == null
11
+ ? 20_000
12
+ : Math.max(1, Math.floor(options.maxExpandedNodes));
13
+ const maxSearchRadiusTiles = options?.maxSearchRadiusTiles == null
14
+ ? 256
15
+ : Math.max(1, Math.floor(options.maxSearchRadiusTiles));
16
+ const snapTargetToEdgeDistance = Math.max(0, options?.snapTargetToEdgeDistance ?? gridCellSize);
17
+ return {
18
+ avoidObstacles: options?.avoidObstacles ?? false,
19
+ gridCellSize,
20
+ recalculateEveryTicks,
21
+ shouldRecalculatePath: options?.shouldRecalculatePath ?? (() => false),
22
+ maxExpandedNodes,
23
+ maxSearchRadiusTiles,
24
+ pathNotFoundBehavior: options?.pathNotFoundBehavior ?? "throw",
25
+ snapTargetToEdgeDistance,
26
+ onPathNotFound: options?.onPathNotFound ?? (() => undefined),
27
+ };
28
+ };
29
+ class Walker {
30
+ gameObject;
31
+ waypoints;
32
+ speed;
33
+ debug;
34
+ waypointIndex = 0;
35
+ active = false;
36
+ ciclic;
37
+ onComplete;
38
+ pathfindingOptions = normalizePathfindingOptions();
39
+ currentPath = [];
40
+ currentPathIndex = 0;
41
+ recalcRequested = true;
42
+ tickCounter = 0;
43
+ adjustedWaypoint = null;
44
+ pathfindingProxy = null;
45
+ constructor(gameObject, waypoints = [], speed, debug = false, ciclic = true, pathfindingOptions) {
46
+ this.gameObject = gameObject;
47
+ this.waypoints = waypoints;
48
+ this.speed = speed;
49
+ this.debug = debug;
50
+ this.ciclic = ciclic;
51
+ if (pathfindingOptions) {
52
+ this.pathfindingOptions = normalizePathfindingOptions(pathfindingOptions);
53
+ }
54
+ }
55
+ getTargetedWaypoint() {
56
+ if (this.waypoints.length === 0)
57
+ return null;
58
+ if (this.ciclic) {
59
+ return this.waypoints[this.waypointIndex % this.waypoints.length];
60
+ }
61
+ const clampedIndex = Math.min(this.waypointIndex, this.waypoints.length - 1);
62
+ return this.waypoints[clampedIndex];
63
+ }
64
+ nextWaypoint() {
65
+ if (!this.ciclic && this.waypointIndex >= this.waypoints.length - 1) {
66
+ return this.waypoints[this.waypoints.length - 1];
67
+ }
68
+ this.waypointIndex = this.ciclic
69
+ ? (this.waypointIndex + 1) % this.waypoints.length
70
+ : this.waypointIndex + 1;
71
+ this.adjustedWaypoint = null;
72
+ return this.getTargetedWaypoint();
73
+ }
74
+ setWaypoints(waypoints, ciclic = false) {
75
+ this.waypoints = waypoints;
76
+ this.ciclic = ciclic;
77
+ this.waypointIndex = 0;
78
+ this.active = false;
79
+ this.currentPath = [];
80
+ this.currentPathIndex = 0;
81
+ this.recalcRequested = true;
82
+ this.adjustedWaypoint = null;
83
+ this.gameObject.speed = Vector.zero();
84
+ }
85
+ isActive() {
86
+ return this.active;
87
+ }
88
+ setOnComplete(callback) {
89
+ this.onComplete = callback;
90
+ return this;
91
+ }
92
+ toggle() {
93
+ this.active = !this.active;
94
+ if (!this.active) {
95
+ this.gameObject.speed = Vector.zero(); //stop moving
96
+ return;
97
+ }
98
+ this.recalcRequested = true;
99
+ }
100
+ start() {
101
+ this.active = true;
102
+ this.recalcRequested = true;
103
+ return this;
104
+ }
105
+ reset() {
106
+ this.waypointIndex = 0;
107
+ this.currentPath = [];
108
+ this.currentPathIndex = 0;
109
+ this.recalcRequested = true;
110
+ this.adjustedWaypoint = null;
111
+ }
112
+ hardReset() {
113
+ this.waypointIndex = 0;
114
+ this.currentPath = [];
115
+ this.currentPathIndex = 0;
116
+ this.recalcRequested = true;
117
+ this.adjustedWaypoint = null;
118
+ if (!this.waypoints[0]) {
119
+ return console.warn(`${this.gameObject.name} has no waypoints, impossible to hard reset it's walker!`);
120
+ }
121
+ this.gameObject.setPosition(this.waypoints[0]);
122
+ }
123
+ setPathfindingOptions(options) {
124
+ this.pathfindingOptions = normalizePathfindingOptions(options);
125
+ this.requestPathRecalculation();
126
+ return this;
127
+ }
128
+ requestPathRecalculation() {
129
+ this.recalcRequested = true;
130
+ }
131
+ getCurrentPath() {
132
+ return this.currentPath.slice(this.currentPathIndex).map((p) => p.clone());
133
+ }
134
+ compactCurrentPathIfNeeded() {
135
+ if (this.currentPathIndex < 64)
136
+ return;
137
+ if (this.currentPathIndex < Math.floor(this.currentPath.length / 2))
138
+ return;
139
+ this.currentPath = this.currentPath.slice(this.currentPathIndex);
140
+ this.currentPathIndex = 0;
141
+ }
142
+ getSolidHitboxes(obj) {
143
+ return obj.getHitboxes().filter((hitbox) => hitbox.solid);
144
+ }
145
+ getObstacleHitboxes(scene) {
146
+ if (!scene)
147
+ return [];
148
+ const obstacleHitboxes = [];
149
+ for (const obj of scene.getGameObjects()) {
150
+ if (obj === this.gameObject)
151
+ continue;
152
+ if (!obj.isActive())
153
+ continue;
154
+ const solid = this.getSolidHitboxes(obj);
155
+ if (solid.length === 0)
156
+ continue;
157
+ obstacleHitboxes.push(...solid);
158
+ }
159
+ return obstacleHitboxes;
160
+ }
161
+ ensurePathfindingProxy() {
162
+ const solid = this.getSolidHitboxes(this.gameObject);
163
+ if (solid.length === 0) {
164
+ return null;
165
+ }
166
+ const signature = solid
167
+ .map((hitbox) => {
168
+ if (hitbox instanceof CircleHitbox) {
169
+ return `c:${hitbox.offset.x},${hitbox.offset.y},${hitbox.radius}`;
170
+ }
171
+ return `s:${hitbox.offset.x},${hitbox.offset.y},${hitbox.size.x},${hitbox.size.y}`;
172
+ })
173
+ .join("|");
174
+ if (this.pathfindingProxy?.signature === signature) {
175
+ return this.pathfindingProxy.proxy;
176
+ }
177
+ const proxy = new PathfindingProxy(this.gameObject.getPosition().clone());
178
+ proxy.rotation = this.gameObject.rotation ?? 0;
179
+ proxy.angularVelocity = 0;
180
+ proxy.hitboxes = solid.map((hitbox) => {
181
+ if (hitbox instanceof CircleHitbox) {
182
+ return new CircleHitbox(hitbox.offset.clone(), hitbox.radius, proxy, { debug: false, solid: true });
183
+ }
184
+ return new SquareHitbox(hitbox.offset.clone(), hitbox.size.clone(), proxy, { debug: false, solid: true });
185
+ });
186
+ this.pathfindingProxy = { proxy, signature };
187
+ return proxy;
188
+ }
189
+ tileKey(tile) {
190
+ return `${tile.x},${tile.y}`;
191
+ }
192
+ worldToTile(position, cellSize) {
193
+ return {
194
+ x: Math.floor(position.x / cellSize),
195
+ y: Math.floor(position.y / cellSize),
196
+ };
197
+ }
198
+ tileToWorldCenter(tile, cellSize) {
199
+ return new Vector((tile.x + 0.5) * cellSize, (tile.y + 0.5) * cellSize);
200
+ }
201
+ isProxyPositionFree(proxy, proxyScenePosition, obstacleHitboxes) {
202
+ proxy.rotation = this.gameObject.rotation ?? 0;
203
+ proxy.speed = Vector.zero();
204
+ const sceneOffset = this.gameObject.scene?.getOffset() ?? Vector.zero();
205
+ proxy.setPosition(proxyScenePosition.toAdded(sceneOffset));
206
+ for (const selfHitbox of proxy.hitboxes) {
207
+ for (const obstacleHitbox of obstacleHitboxes) {
208
+ if (selfHitbox.intersects(obstacleHitbox)) {
209
+ return false;
210
+ }
211
+ }
212
+ }
213
+ return true;
214
+ }
215
+ isProxyEdgeFree(proxy, fromScenePosition, toScenePosition, obstacleHitboxes) {
216
+ const mid = fromScenePosition.toAdded(toScenePosition).toMultiplied(0.5);
217
+ return (this.isProxyPositionFree(proxy, toScenePosition, obstacleHitboxes) &&
218
+ this.isProxyPositionFree(proxy, mid, obstacleHitboxes));
219
+ }
220
+ clamp(value, min, max) {
221
+ return Math.max(min, Math.min(max, value));
222
+ }
223
+ closestPointOnSquareEdge(hitbox, point) {
224
+ const vertices = hitbox.getTransformedVertices();
225
+ const center = vertices[0].toAdded(vertices[2]).toMultiplied(0.5);
226
+ const axisX = vertices[1].toSubtracted(vertices[0]).normalize();
227
+ const axisY = vertices[3].toSubtracted(vertices[0]).normalize();
228
+ const halfX = hitbox.size.x / 2;
229
+ const halfY = hitbox.size.y / 2;
230
+ const d = point.toSubtracted(center);
231
+ const distX = d.dotProduct(axisX);
232
+ const distY = d.dotProduct(axisY);
233
+ let clampedX = this.clamp(distX, -halfX, halfX);
234
+ let clampedY = this.clamp(distY, -halfY, halfY);
235
+ const absX = Math.abs(distX);
236
+ const absY = Math.abs(distY);
237
+ const insideX = absX <= halfX;
238
+ const insideY = absY <= halfY;
239
+ if (insideX && insideY) {
240
+ const distToEdgeX = halfX - absX;
241
+ const distToEdgeY = halfY - absY;
242
+ if (distToEdgeX <= distToEdgeY) {
243
+ clampedX = Math.sign(distX || 1) * halfX;
244
+ }
245
+ else {
246
+ clampedY = Math.sign(distY || 1) * halfY;
247
+ }
248
+ }
249
+ return center
250
+ .toAdded(axisX.toMultiplied(clampedX))
251
+ .toAdded(axisY.toMultiplied(clampedY));
252
+ }
253
+ getSnappedGoal(goalScenePosition, obstacleHitboxes, proxy) {
254
+ const snapDistance = this.pathfindingOptions.snapTargetToEdgeDistance;
255
+ if (snapDistance <= 0)
256
+ return null;
257
+ const nudgeDistance = Math.max(1, Math.min(4, this.pathfindingOptions.gridCellSize * 0.25));
258
+ let bestCandidate = null;
259
+ for (const hitbox of obstacleHitboxes) {
260
+ let edgePoint = null;
261
+ let distanceToEdge = Number.POSITIVE_INFINITY;
262
+ let direction = null;
263
+ if (hitbox instanceof SquareHitbox) {
264
+ edgePoint = this.closestPointOnSquareEdge(hitbox, goalScenePosition);
265
+ distanceToEdge = goalScenePosition
266
+ .toSubtracted(edgePoint)
267
+ .magnitude();
268
+ const vertices = hitbox.getTransformedVertices();
269
+ const center = vertices[0].toAdded(vertices[2]).toMultiplied(0.5);
270
+ direction = edgePoint.toSubtracted(center);
271
+ }
272
+ else if (hitbox instanceof CircleHitbox) {
273
+ const center = hitbox.getTransformedPosition();
274
+ const delta = goalScenePosition.toSubtracted(center);
275
+ const deltaMagnitude = delta.magnitude();
276
+ const normalized = deltaMagnitude === 0 ? new Vector(1, 0) : delta.toNormalized();
277
+ edgePoint = center.toAdded(normalized.toMultiplied(hitbox.radius));
278
+ distanceToEdge = Math.abs(deltaMagnitude - hitbox.radius);
279
+ direction = normalized;
280
+ }
281
+ if (!edgePoint || distanceToEdge > snapDistance)
282
+ continue;
283
+ const normalizedDirection = direction && direction.squaredMagnitude() > 0
284
+ ? direction.toNormalized()
285
+ : new Vector(1, 0);
286
+ const candidates = [
287
+ edgePoint.toAdded(normalizedDirection.toMultiplied(nudgeDistance)),
288
+ edgePoint,
289
+ ];
290
+ for (const candidate of candidates) {
291
+ if (!this.isProxyPositionFree(proxy, candidate, obstacleHitboxes)) {
292
+ continue;
293
+ }
294
+ if (!bestCandidate || distanceToEdge < bestCandidate.distance) {
295
+ bestCandidate = { point: candidate, distance: distanceToEdge };
296
+ }
297
+ }
298
+ }
299
+ return bestCandidate ? bestCandidate.point.clone() : null;
300
+ }
301
+ resolvePathNotFound(startScenePosition, goalScenePosition, obstacleHitboxes, proxy) {
302
+ const context = {
303
+ walker: this,
304
+ gameObject: this.gameObject,
305
+ scene: this.gameObject.scene,
306
+ start: startScenePosition,
307
+ goal: goalScenePosition,
308
+ obstacleHitboxes,
309
+ };
310
+ const decision = this.pathfindingOptions.onPathNotFound(context);
311
+ const resolved = typeof decision === "string" ? { behavior: decision } : decision;
312
+ const behavior = resolved?.behavior ?? this.pathfindingOptions.pathNotFoundBehavior;
313
+ if (behavior === "throw") {
314
+ throw new Error(`Walker could not find a path for ${this.gameObject.name} to ${goalScenePosition.x},${goalScenePosition.y}.`);
315
+ }
316
+ if (behavior === "stop") {
317
+ this.currentPath = [];
318
+ this.currentPathIndex = 0;
319
+ this.adjustedWaypoint = null;
320
+ this.gameObject.speed = Vector.zero();
321
+ this.active = false;
322
+ return { stop: true };
323
+ }
324
+ if (behavior === "continue") {
325
+ this.currentPath = [];
326
+ this.currentPathIndex = 0;
327
+ this.adjustedWaypoint = null;
328
+ return { stop: false };
329
+ }
330
+ const snapGoal = resolved && "goal" in resolved && resolved.goal
331
+ ? resolved.goal
332
+ : this.getSnappedGoal(goalScenePosition, obstacleHitboxes, proxy);
333
+ if (!snapGoal) {
334
+ this.currentPath = [];
335
+ this.currentPathIndex = 0;
336
+ this.adjustedWaypoint = null;
337
+ this.gameObject.speed = Vector.zero();
338
+ this.active = false;
339
+ return { stop: true };
340
+ }
341
+ const snapResult = this.computePathToTarget(startScenePosition, snapGoal);
342
+ if (snapResult.status === "found") {
343
+ this.currentPath = snapResult.path;
344
+ this.currentPathIndex = 0;
345
+ this.adjustedWaypoint = snapGoal.clone();
346
+ return { stop: false };
347
+ }
348
+ if (snapResult.status === "skipped") {
349
+ this.currentPath = [];
350
+ this.currentPathIndex = 0;
351
+ this.adjustedWaypoint = snapGoal.clone();
352
+ return { stop: false };
353
+ }
354
+ this.currentPath = [];
355
+ this.currentPathIndex = 0;
356
+ this.adjustedWaypoint = null;
357
+ this.gameObject.speed = Vector.zero();
358
+ this.active = false;
359
+ return { stop: true };
360
+ }
361
+ computePathToTarget(startScenePosition, goalScenePosition) {
362
+ if (!this.pathfindingOptions.avoidObstacles) {
363
+ return { status: "skipped", path: [] };
364
+ }
365
+ const scene = this.gameObject.scene;
366
+ if (!scene) {
367
+ return { status: "skipped", path: [] };
368
+ }
369
+ const proxy = this.ensurePathfindingProxy();
370
+ if (!proxy) {
371
+ return { status: "skipped", path: [] };
372
+ }
373
+ const obstacleHitboxes = this.getObstacleHitboxes(scene);
374
+ if (obstacleHitboxes.length === 0) {
375
+ return { status: "skipped", path: [] };
376
+ }
377
+ const cellSize = this.pathfindingOptions.gridCellSize;
378
+ const startTile = this.worldToTile(startScenePosition, cellSize);
379
+ const goalTile = this.worldToTile(goalScenePosition, cellSize);
380
+ const startKey = this.tileKey(startTile);
381
+ const goalKey = this.tileKey(goalTile);
382
+ const isInSearchRadius = (tile) => {
383
+ const dx = Math.abs(tile.x - startTile.x);
384
+ const dy = Math.abs(tile.y - startTile.y);
385
+ return (dx <= this.pathfindingOptions.maxSearchRadiusTiles &&
386
+ dy <= this.pathfindingOptions.maxSearchRadiusTiles);
387
+ };
388
+ const walkableCache = new Map();
389
+ const isTileWalkable = (tile) => {
390
+ const key = this.tileKey(tile);
391
+ if (key === startKey)
392
+ return true;
393
+ if (key === goalKey) {
394
+ return this.isProxyPositionFree(proxy, goalScenePosition, obstacleHitboxes);
395
+ }
396
+ if (!isInSearchRadius(tile))
397
+ return false;
398
+ const cached = walkableCache.get(key);
399
+ if (cached != null)
400
+ return cached;
401
+ const worldCenter = this.tileToWorldCenter(tile, cellSize);
402
+ const ok = this.isProxyPositionFree(proxy, worldCenter, obstacleHitboxes);
403
+ walkableCache.set(key, ok);
404
+ return ok;
405
+ };
406
+ const heuristic = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
407
+ const open = new MinHeap((n) => n.f);
408
+ open.push({ tile: startTile, g: 0, f: heuristic(startTile, goalTile) });
409
+ const cameFrom = new Map();
410
+ const gScore = new Map([[startKey, 0]]);
411
+ const dirs = [
412
+ { x: 1, y: 0 },
413
+ { x: -1, y: 0 },
414
+ { x: 0, y: 1 },
415
+ { x: 0, y: -1 },
416
+ ];
417
+ let expanded = 0;
418
+ while (open.size > 0 &&
419
+ expanded < this.pathfindingOptions.maxExpandedNodes) {
420
+ expanded++;
421
+ const current = open.pop();
422
+ const currentKey = this.tileKey(current.tile);
423
+ if (currentKey === goalKey) {
424
+ const tiles = [goalTile];
425
+ let cursorKey = goalKey;
426
+ while (cursorKey !== startKey) {
427
+ const parentKey = cameFrom.get(cursorKey);
428
+ if (!parentKey)
429
+ break;
430
+ const [x, y] = parentKey.split(",").map((v) => Number(v));
431
+ tiles.push({ x: x, y: y });
432
+ cursorKey = parentKey;
433
+ }
434
+ tiles.reverse();
435
+ const centers = tiles.map((tile) => {
436
+ const key = this.tileKey(tile);
437
+ if (key === goalKey)
438
+ return goalScenePosition.clone();
439
+ return this.tileToWorldCenter(tile, cellSize);
440
+ });
441
+ const path = centers.slice(1);
442
+ if (path.length === 0) {
443
+ return { status: "found", path: [] };
444
+ }
445
+ return { status: "found", path: this.simplifyPath(path) };
446
+ }
447
+ for (const dir of dirs) {
448
+ const neighbor = {
449
+ x: current.tile.x + dir.x,
450
+ y: current.tile.y + dir.y,
451
+ };
452
+ if (!isTileWalkable(neighbor))
453
+ continue;
454
+ const currentWorld = this.tileToWorldCenter(current.tile, cellSize);
455
+ const neighborKey = this.tileKey(neighbor);
456
+ const neighborWorld = neighborKey === goalKey
457
+ ? goalScenePosition
458
+ : this.tileToWorldCenter(neighbor, cellSize);
459
+ if (currentKey !== startKey &&
460
+ !this.isProxyEdgeFree(proxy, currentWorld, neighborWorld, obstacleHitboxes)) {
461
+ continue;
462
+ }
463
+ const tentativeG = current.g + 1;
464
+ const bestKnownG = gScore.get(neighborKey);
465
+ if (bestKnownG != null && tentativeG >= bestKnownG) {
466
+ continue;
467
+ }
468
+ cameFrom.set(neighborKey, currentKey);
469
+ gScore.set(neighborKey, tentativeG);
470
+ open.push({
471
+ tile: neighbor,
472
+ g: tentativeG,
473
+ f: tentativeG + heuristic(neighbor, goalTile),
474
+ });
475
+ }
476
+ }
477
+ return { status: "no_path", path: [], obstacleHitboxes, proxy };
478
+ }
479
+ simplifyPath(path) {
480
+ if (path.length <= 2)
481
+ return path;
482
+ const simplified = [path[0].clone()];
483
+ for (let i = 1; i < path.length - 1; i++) {
484
+ const prev = simplified[simplified.length - 1];
485
+ const current = path[i];
486
+ const next = path[i + 1];
487
+ const a = current.toSubtracted(prev);
488
+ const b = next.toSubtracted(current);
489
+ const cross = a.x * b.y - a.y * b.x;
490
+ const collinear = Math.abs(cross) < 0.0001;
491
+ const sameDirection = a.dotProduct(b) >= 0;
492
+ if (collinear && sameDirection) {
493
+ continue;
494
+ }
495
+ simplified.push(current.clone());
496
+ }
497
+ simplified.push(path[path.length - 1].clone());
498
+ return simplified;
499
+ }
500
+ tick() {
501
+ if (!this.active || this.waypoints.length === 0) {
502
+ return;
503
+ }
504
+ this.tickCounter++;
505
+ const currentPosition = this.gameObject.getScenePosition();
506
+ const targetedWaypoint = this.getTargetedWaypoint();
507
+ if (!targetedWaypoint) {
508
+ this.onComplete?.();
509
+ return;
510
+ }
511
+ if (!this.pathfindingOptions.avoidObstacles) {
512
+ this.currentPath = [];
513
+ this.currentPathIndex = 0;
514
+ this.recalcRequested = false;
515
+ this.adjustedWaypoint = null;
516
+ }
517
+ else {
518
+ const shouldRecalculate = this.recalcRequested ||
519
+ this.tickCounter % this.pathfindingOptions.recalculateEveryTicks ===
520
+ 0 ||
521
+ this.pathfindingOptions.shouldRecalculatePath({
522
+ walker: this,
523
+ gameObject: this.gameObject,
524
+ scene: this.gameObject.scene,
525
+ sceneObjects: this.gameObject.scene?.getGameObjects() ?? [],
526
+ tick: this.tickCounter,
527
+ start: currentPosition,
528
+ goal: targetedWaypoint,
529
+ currentPath: this.currentPath,
530
+ currentPathIndex: this.currentPathIndex,
531
+ });
532
+ if (shouldRecalculate) {
533
+ this.recalcRequested = false;
534
+ const pathResult = this.computePathToTarget(currentPosition, targetedWaypoint);
535
+ if (pathResult.status === "found") {
536
+ this.currentPath = pathResult.path;
537
+ this.currentPathIndex = 0;
538
+ this.adjustedWaypoint = null;
539
+ }
540
+ else if (pathResult.status === "skipped") {
541
+ this.currentPath = [];
542
+ this.currentPathIndex = 0;
543
+ this.adjustedWaypoint = null;
544
+ }
545
+ else if (pathResult.obstacleHitboxes && pathResult.proxy) {
546
+ const result = this.resolvePathNotFound(currentPosition, targetedWaypoint, pathResult.obstacleHitboxes, pathResult.proxy);
547
+ if (result.stop) {
548
+ return;
549
+ }
550
+ }
551
+ }
552
+ }
553
+ // Drop nodes that were reached via collisions/teleports.
554
+ while (this.currentPathIndex < this.currentPath.length &&
555
+ this.currentPath[this.currentPathIndex].toSubtracted(currentPosition).squaredMagnitude() <= 0.5) {
556
+ this.currentPathIndex++;
557
+ }
558
+ this.compactCurrentPathIfNeeded();
559
+ const movementTarget = this.currentPath[this.currentPathIndex] ??
560
+ (this.adjustedWaypoint ?? targetedWaypoint);
561
+ const movementVector = movementTarget.toSubtracted(currentPosition);
562
+ const movementDirection = movementVector.toNormalized();
563
+ const intendedMovementVector = movementDirection.toMultiplied(this.speed);
564
+ const maxMovement = movementVector.squaredMagnitude();
565
+ const shouldClipMovement = intendedMovementVector.squaredMagnitude() >= maxMovement;
566
+ const movement = shouldClipMovement
567
+ ? movementVector
568
+ : intendedMovementVector;
569
+ if (shouldClipMovement) {
570
+ if (this.currentPathIndex < this.currentPath.length) {
571
+ // Reached a path node (not the actual waypoint yet).
572
+ this.currentPathIndex++;
573
+ this.compactCurrentPathIfNeeded();
574
+ }
575
+ else {
576
+ const isAtLastWaypoint = !this.ciclic && this.waypointIndex >= this.waypoints.length - 1;
577
+ if (isAtLastWaypoint) {
578
+ // Snap to the final waypoint, stop, and notify listeners.
579
+ this.gameObject.setPosition((this.adjustedWaypoint ?? targetedWaypoint).clone());
580
+ this.gameObject.speed = Vector.zero();
581
+ this.active = false;
582
+ this.onComplete?.();
583
+ return;
584
+ }
585
+ this.currentPath = [];
586
+ this.currentPathIndex = 0;
587
+ this.recalcRequested = true;
588
+ this.adjustedWaypoint = null;
589
+ this.nextWaypoint();
590
+ }
591
+ }
592
+ this.gameObject.speed = movement;
593
+ }
594
+ renderDebug(canvas) {
595
+ if (!this.debug)
596
+ return;
597
+ const drawer = canvas.getShapeDrawer();
598
+ const rotation = this.gameObject.rotation ?? 0;
599
+ const draw = () => {
600
+ const sceneOffset = this.gameObject.scene?.getOffset() ?? Vector.zero();
601
+ this.waypoints.forEach((waypoint, index) => {
602
+ const renderedWaypoint = waypoint.toAdded(sceneOffset);
603
+ drawer.drawCircle(renderedWaypoint.x, renderedWaypoint.y, 8, "red", true);
604
+ if (index == 0)
605
+ return;
606
+ drawer.drawLine(this.waypoints[index - 1].toAdded(sceneOffset), renderedWaypoint, 8);
607
+ });
608
+ if (this.ciclic && this.waypoints.length > 1) {
609
+ drawer.drawLine(this.waypoints[this.waypoints.length - 1].toAdded(sceneOffset), this.waypoints[0].toAdded(sceneOffset), 8);
610
+ }
611
+ if (this.currentPathIndex < this.currentPath.length) {
612
+ const start = this.gameObject.getPosition();
613
+ let previous = start;
614
+ for (let index = this.currentPathIndex; index < this.currentPath.length; index++) {
615
+ const node = this.currentPath[index];
616
+ const rendered = node.toAdded(sceneOffset);
617
+ drawer.drawCircle(rendered.x, rendered.y, 6, "cyan", true);
618
+ drawer.drawLine(previous, rendered, 4, "cyan");
619
+ previous = rendered;
620
+ }
621
+ }
622
+ };
623
+ // Walker debug drawing is called from within the GameObject's rotation block.
624
+ // Cancel it so waypoints/path are drawn in world space (non-rotated).
625
+ if (rotation !== 0) {
626
+ const pivot = this.gameObject.getRotationCenter();
627
+ drawer.withRotation(pivot.x, pivot.y, -rotation, draw);
628
+ return;
629
+ }
630
+ draw();
631
+ }
632
+ }
633
+ export { Walker };
634
+ //# sourceMappingURL=Walker.js.map