@bct-app/game-engine 0.1.16-beta.1 → 0.1.17
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/dist/index.js +64 -52
- package/dist/index.mjs +64 -52
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1074,11 +1074,16 @@ var evaluatePrecondition = ({
|
|
|
1074
1074
|
};
|
|
1075
1075
|
|
|
1076
1076
|
// src/apply-operation.ts
|
|
1077
|
-
var isDeferredOperation = (operation, abilityMap) => {
|
|
1078
|
-
const ability = abilityMap.get(operation.abilityId);
|
|
1079
|
-
return ability?.executionTiming === "DEFER_TO_END";
|
|
1080
|
-
};
|
|
1081
1077
|
var FIXED_POINT_MAX_ITERATIONS = 32;
|
|
1078
|
+
var seatSetsEqual = (left, right) => {
|
|
1079
|
+
if (left.length !== right.length) return false;
|
|
1080
|
+
const sortedLeft = [...left].sort((a, b) => a - b);
|
|
1081
|
+
const sortedRight = [...right].sort((a, b) => a - b);
|
|
1082
|
+
for (let i = 0; i < sortedLeft.length; i += 1) {
|
|
1083
|
+
if (sortedLeft[i] !== sortedRight[i]) return false;
|
|
1084
|
+
}
|
|
1085
|
+
return true;
|
|
1086
|
+
};
|
|
1082
1087
|
var applyOperationToPlayers = ({
|
|
1083
1088
|
players,
|
|
1084
1089
|
operations,
|
|
@@ -1104,20 +1109,11 @@ var applyOperationToPlayers = ({
|
|
|
1104
1109
|
}
|
|
1105
1110
|
operationsByTimeline.get(timelineIndex)?.push(operation);
|
|
1106
1111
|
});
|
|
1107
|
-
const
|
|
1108
|
-
const ops = operationsByTimeline.get(timelineIdx) ?? [];
|
|
1109
|
-
const normal = [];
|
|
1110
|
-
const deferred = [];
|
|
1111
|
-
ops.forEach((op) => {
|
|
1112
|
-
if (isDeferredOperation(op, abilityMap)) deferred.push(op);
|
|
1113
|
-
else normal.push(op);
|
|
1114
|
-
});
|
|
1115
|
-
return [...normal, ...deferred];
|
|
1116
|
-
};
|
|
1112
|
+
const opsForTimeline = (timelineIdx) => operationsByTimeline.get(timelineIdx) ?? [];
|
|
1117
1113
|
const allOpsInOrder = [];
|
|
1118
|
-
allOpsInOrder.push(...
|
|
1114
|
+
allOpsInOrder.push(...opsForTimeline(-1));
|
|
1119
1115
|
for (let i = 0; i <= nowTimelineIndex; i += 1) {
|
|
1120
|
-
if (timelines[i]) allOpsInOrder.push(...
|
|
1116
|
+
if (timelines[i]) allOpsInOrder.push(...opsForTimeline(i));
|
|
1121
1117
|
}
|
|
1122
1118
|
const processedOps = /* @__PURE__ */ new Set();
|
|
1123
1119
|
const records = [];
|
|
@@ -1166,7 +1162,7 @@ var applyOperationToPlayers = ({
|
|
|
1166
1162
|
};
|
|
1167
1163
|
const rebuildState = () => {
|
|
1168
1164
|
const playersWithStatus = copyPlayers(players);
|
|
1169
|
-
const settingsOps =
|
|
1165
|
+
const settingsOps = opsForTimeline(-1);
|
|
1170
1166
|
settingsOps.forEach((op) => {
|
|
1171
1167
|
if (processedOps.has(op)) applyRecordsForOp(op, playersWithStatus);
|
|
1172
1168
|
});
|
|
@@ -1187,7 +1183,7 @@ var applyOperationToPlayers = ({
|
|
|
1187
1183
|
});
|
|
1188
1184
|
});
|
|
1189
1185
|
}
|
|
1190
|
-
|
|
1186
|
+
opsForTimeline(i).forEach((op) => {
|
|
1191
1187
|
if (processedOps.has(op)) applyRecordsForOp(op, playersWithStatus);
|
|
1192
1188
|
});
|
|
1193
1189
|
}
|
|
@@ -1199,10 +1195,10 @@ var applyOperationToPlayers = ({
|
|
|
1199
1195
|
console.warn("Ability not found for operation:", op);
|
|
1200
1196
|
return [];
|
|
1201
1197
|
}
|
|
1198
|
+
const operationInTimelineIdx = operationTimelineMap.get(op) ?? -1;
|
|
1202
1199
|
const playerSeatMap = buildPlayerSeatMap(state);
|
|
1203
1200
|
const effector = playerSeatMap.get(op.effector);
|
|
1204
1201
|
const payloads = op.payloads || [];
|
|
1205
|
-
const operationInTimelineIdx = operationTimelineMap.get(op) ?? -1;
|
|
1206
1202
|
const newRecords = [];
|
|
1207
1203
|
ability.effects.forEach((effect, effectIdx) => {
|
|
1208
1204
|
const resolvedTargets = resolveEffectTargets({
|
|
@@ -1227,9 +1223,7 @@ var applyOperationToPlayers = ({
|
|
|
1227
1223
|
payloads,
|
|
1228
1224
|
customResolver
|
|
1229
1225
|
}) : resolvedTargets;
|
|
1230
|
-
if (gatedTargets.length === 0 && resolvedTargets.length > 0)
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1226
|
+
if (gatedTargets.length === 0 && resolvedTargets.length > 0) return;
|
|
1233
1227
|
const lifeTargets = evaluateLifetime({
|
|
1234
1228
|
effect,
|
|
1235
1229
|
operationTimelineIdx: operationInTimelineIdx,
|
|
@@ -1246,42 +1240,60 @@ var applyOperationToPlayers = ({
|
|
|
1246
1240
|
operation: op,
|
|
1247
1241
|
effectIdx,
|
|
1248
1242
|
operationInTimelineIdx,
|
|
1249
|
-
activeTargetSeats: lifeTargets.map((t) => t.seat)
|
|
1243
|
+
activeTargetSeats: lifeTargets.map((t) => t.seat),
|
|
1244
|
+
resolvedSeatsAtApply: new Set(resolvedTargets.map((t) => t.seat)),
|
|
1245
|
+
gatedSeatsAtApply: new Set(gatedTargets.map((t) => t.seat))
|
|
1250
1246
|
});
|
|
1251
1247
|
});
|
|
1252
1248
|
return newRecords;
|
|
1253
1249
|
};
|
|
1254
|
-
const
|
|
1250
|
+
const reResolveRecord = (record, state) => {
|
|
1251
|
+
const ability = abilityMap.get(record.operation.abilityId);
|
|
1252
|
+
if (!ability) return [];
|
|
1253
|
+
const effect = ability.effects[record.effectIdx];
|
|
1254
|
+
if (!effect) return [];
|
|
1255
1255
|
const playerSeatMap = buildPlayerSeatMap(state);
|
|
1256
|
-
|
|
1256
|
+
const effector = playerSeatMap.get(record.operation.effector);
|
|
1257
|
+
const payloads = record.operation.payloads || [];
|
|
1258
|
+
const resolvedTargets = resolveEffectTargets({
|
|
1259
|
+
effect,
|
|
1260
|
+
operation: record.operation,
|
|
1261
|
+
payloads,
|
|
1262
|
+
effector,
|
|
1263
|
+
playerSeatMap,
|
|
1264
|
+
characterMap
|
|
1265
|
+
});
|
|
1266
|
+
if (!resolvedTargets) return [];
|
|
1267
|
+
const gatedTargets = resolvedTargets.filter((target) => {
|
|
1268
|
+
if (record.resolvedSeatsAtApply.has(target.seat)) {
|
|
1269
|
+
return record.gatedSeatsAtApply.has(target.seat);
|
|
1270
|
+
}
|
|
1271
|
+
return true;
|
|
1272
|
+
});
|
|
1273
|
+
const lifeTargets = evaluateLifetime({
|
|
1274
|
+
effect,
|
|
1275
|
+
operationTimelineIdx: record.operationInTimelineIdx,
|
|
1276
|
+
nowTimelineIndex,
|
|
1277
|
+
timelines,
|
|
1278
|
+
effector,
|
|
1279
|
+
targets: gatedTargets,
|
|
1280
|
+
snapshotSeatMap: playerSeatMap,
|
|
1281
|
+
characterMap,
|
|
1282
|
+
payloads,
|
|
1283
|
+
customResolver
|
|
1284
|
+
});
|
|
1285
|
+
return lifeTargets.map((t) => t.seat);
|
|
1286
|
+
};
|
|
1287
|
+
const reResolveRecords = (state) => {
|
|
1288
|
+
let anyChanged = false;
|
|
1257
1289
|
records.forEach((record) => {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (!ability) return;
|
|
1261
|
-
const effect = ability.effects[record.effectIdx];
|
|
1262
|
-
if (!effect) return;
|
|
1263
|
-
const effector = playerSeatMap.get(record.operation.effector);
|
|
1264
|
-
const payloads = record.operation.payloads || [];
|
|
1265
|
-
const targets = record.activeTargetSeats.map((seat) => playerSeatMap.get(seat)).filter((p) => Boolean(p));
|
|
1266
|
-
const liveTargets = evaluateLifetime({
|
|
1267
|
-
effect,
|
|
1268
|
-
operationTimelineIdx: record.operationInTimelineIdx,
|
|
1269
|
-
nowTimelineIndex,
|
|
1270
|
-
timelines,
|
|
1271
|
-
effector,
|
|
1272
|
-
targets,
|
|
1273
|
-
snapshotSeatMap: playerSeatMap,
|
|
1274
|
-
characterMap,
|
|
1275
|
-
payloads,
|
|
1276
|
-
customResolver
|
|
1277
|
-
});
|
|
1278
|
-
const newSeats = liveTargets.map((t) => t.seat);
|
|
1279
|
-
if (newSeats.length < record.activeTargetSeats.length) {
|
|
1290
|
+
const newSeats = reResolveRecord(record, state);
|
|
1291
|
+
if (!seatSetsEqual(newSeats, record.activeTargetSeats)) {
|
|
1280
1292
|
record.activeTargetSeats = newSeats;
|
|
1281
|
-
|
|
1293
|
+
anyChanged = true;
|
|
1282
1294
|
}
|
|
1283
1295
|
});
|
|
1284
|
-
return
|
|
1296
|
+
return anyChanged;
|
|
1285
1297
|
};
|
|
1286
1298
|
let lastState = copyPlayers(players);
|
|
1287
1299
|
allOpsInOrder.forEach((op) => {
|
|
@@ -1296,13 +1308,13 @@ var applyOperationToPlayers = ({
|
|
|
1296
1308
|
let state = rebuildState();
|
|
1297
1309
|
let iter = 0;
|
|
1298
1310
|
while (iter < FIXED_POINT_MAX_ITERATIONS) {
|
|
1299
|
-
const
|
|
1300
|
-
if (!
|
|
1311
|
+
const changed = reResolveRecords(state);
|
|
1312
|
+
if (!changed) break;
|
|
1301
1313
|
state = rebuildState();
|
|
1302
1314
|
iter += 1;
|
|
1303
1315
|
}
|
|
1304
1316
|
if (iter >= FIXED_POINT_MAX_ITERATIONS) {
|
|
1305
|
-
console.warn("applyOperationToPlayers:
|
|
1317
|
+
console.warn("applyOperationToPlayers: target re-resolution fixed-point did not converge within %d iterations after op %s", FIXED_POINT_MAX_ITERATIONS, op.abilityId);
|
|
1306
1318
|
}
|
|
1307
1319
|
lastState = state;
|
|
1308
1320
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1034,11 +1034,16 @@ var evaluatePrecondition = ({
|
|
|
1034
1034
|
};
|
|
1035
1035
|
|
|
1036
1036
|
// src/apply-operation.ts
|
|
1037
|
-
var isDeferredOperation = (operation, abilityMap) => {
|
|
1038
|
-
const ability = abilityMap.get(operation.abilityId);
|
|
1039
|
-
return ability?.executionTiming === "DEFER_TO_END";
|
|
1040
|
-
};
|
|
1041
1037
|
var FIXED_POINT_MAX_ITERATIONS = 32;
|
|
1038
|
+
var seatSetsEqual = (left, right) => {
|
|
1039
|
+
if (left.length !== right.length) return false;
|
|
1040
|
+
const sortedLeft = [...left].sort((a, b) => a - b);
|
|
1041
|
+
const sortedRight = [...right].sort((a, b) => a - b);
|
|
1042
|
+
for (let i = 0; i < sortedLeft.length; i += 1) {
|
|
1043
|
+
if (sortedLeft[i] !== sortedRight[i]) return false;
|
|
1044
|
+
}
|
|
1045
|
+
return true;
|
|
1046
|
+
};
|
|
1042
1047
|
var applyOperationToPlayers = ({
|
|
1043
1048
|
players,
|
|
1044
1049
|
operations,
|
|
@@ -1064,20 +1069,11 @@ var applyOperationToPlayers = ({
|
|
|
1064
1069
|
}
|
|
1065
1070
|
operationsByTimeline.get(timelineIndex)?.push(operation);
|
|
1066
1071
|
});
|
|
1067
|
-
const
|
|
1068
|
-
const ops = operationsByTimeline.get(timelineIdx) ?? [];
|
|
1069
|
-
const normal = [];
|
|
1070
|
-
const deferred = [];
|
|
1071
|
-
ops.forEach((op) => {
|
|
1072
|
-
if (isDeferredOperation(op, abilityMap)) deferred.push(op);
|
|
1073
|
-
else normal.push(op);
|
|
1074
|
-
});
|
|
1075
|
-
return [...normal, ...deferred];
|
|
1076
|
-
};
|
|
1072
|
+
const opsForTimeline = (timelineIdx) => operationsByTimeline.get(timelineIdx) ?? [];
|
|
1077
1073
|
const allOpsInOrder = [];
|
|
1078
|
-
allOpsInOrder.push(...
|
|
1074
|
+
allOpsInOrder.push(...opsForTimeline(-1));
|
|
1079
1075
|
for (let i = 0; i <= nowTimelineIndex; i += 1) {
|
|
1080
|
-
if (timelines[i]) allOpsInOrder.push(...
|
|
1076
|
+
if (timelines[i]) allOpsInOrder.push(...opsForTimeline(i));
|
|
1081
1077
|
}
|
|
1082
1078
|
const processedOps = /* @__PURE__ */ new Set();
|
|
1083
1079
|
const records = [];
|
|
@@ -1126,7 +1122,7 @@ var applyOperationToPlayers = ({
|
|
|
1126
1122
|
};
|
|
1127
1123
|
const rebuildState = () => {
|
|
1128
1124
|
const playersWithStatus = copyPlayers(players);
|
|
1129
|
-
const settingsOps =
|
|
1125
|
+
const settingsOps = opsForTimeline(-1);
|
|
1130
1126
|
settingsOps.forEach((op) => {
|
|
1131
1127
|
if (processedOps.has(op)) applyRecordsForOp(op, playersWithStatus);
|
|
1132
1128
|
});
|
|
@@ -1147,7 +1143,7 @@ var applyOperationToPlayers = ({
|
|
|
1147
1143
|
});
|
|
1148
1144
|
});
|
|
1149
1145
|
}
|
|
1150
|
-
|
|
1146
|
+
opsForTimeline(i).forEach((op) => {
|
|
1151
1147
|
if (processedOps.has(op)) applyRecordsForOp(op, playersWithStatus);
|
|
1152
1148
|
});
|
|
1153
1149
|
}
|
|
@@ -1159,10 +1155,10 @@ var applyOperationToPlayers = ({
|
|
|
1159
1155
|
console.warn("Ability not found for operation:", op);
|
|
1160
1156
|
return [];
|
|
1161
1157
|
}
|
|
1158
|
+
const operationInTimelineIdx = operationTimelineMap.get(op) ?? -1;
|
|
1162
1159
|
const playerSeatMap = buildPlayerSeatMap(state);
|
|
1163
1160
|
const effector = playerSeatMap.get(op.effector);
|
|
1164
1161
|
const payloads = op.payloads || [];
|
|
1165
|
-
const operationInTimelineIdx = operationTimelineMap.get(op) ?? -1;
|
|
1166
1162
|
const newRecords = [];
|
|
1167
1163
|
ability.effects.forEach((effect, effectIdx) => {
|
|
1168
1164
|
const resolvedTargets = resolveEffectTargets({
|
|
@@ -1187,9 +1183,7 @@ var applyOperationToPlayers = ({
|
|
|
1187
1183
|
payloads,
|
|
1188
1184
|
customResolver
|
|
1189
1185
|
}) : resolvedTargets;
|
|
1190
|
-
if (gatedTargets.length === 0 && resolvedTargets.length > 0)
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1186
|
+
if (gatedTargets.length === 0 && resolvedTargets.length > 0) return;
|
|
1193
1187
|
const lifeTargets = evaluateLifetime({
|
|
1194
1188
|
effect,
|
|
1195
1189
|
operationTimelineIdx: operationInTimelineIdx,
|
|
@@ -1206,42 +1200,60 @@ var applyOperationToPlayers = ({
|
|
|
1206
1200
|
operation: op,
|
|
1207
1201
|
effectIdx,
|
|
1208
1202
|
operationInTimelineIdx,
|
|
1209
|
-
activeTargetSeats: lifeTargets.map((t) => t.seat)
|
|
1203
|
+
activeTargetSeats: lifeTargets.map((t) => t.seat),
|
|
1204
|
+
resolvedSeatsAtApply: new Set(resolvedTargets.map((t) => t.seat)),
|
|
1205
|
+
gatedSeatsAtApply: new Set(gatedTargets.map((t) => t.seat))
|
|
1210
1206
|
});
|
|
1211
1207
|
});
|
|
1212
1208
|
return newRecords;
|
|
1213
1209
|
};
|
|
1214
|
-
const
|
|
1210
|
+
const reResolveRecord = (record, state) => {
|
|
1211
|
+
const ability = abilityMap.get(record.operation.abilityId);
|
|
1212
|
+
if (!ability) return [];
|
|
1213
|
+
const effect = ability.effects[record.effectIdx];
|
|
1214
|
+
if (!effect) return [];
|
|
1215
1215
|
const playerSeatMap = buildPlayerSeatMap(state);
|
|
1216
|
-
|
|
1216
|
+
const effector = playerSeatMap.get(record.operation.effector);
|
|
1217
|
+
const payloads = record.operation.payloads || [];
|
|
1218
|
+
const resolvedTargets = resolveEffectTargets({
|
|
1219
|
+
effect,
|
|
1220
|
+
operation: record.operation,
|
|
1221
|
+
payloads,
|
|
1222
|
+
effector,
|
|
1223
|
+
playerSeatMap,
|
|
1224
|
+
characterMap
|
|
1225
|
+
});
|
|
1226
|
+
if (!resolvedTargets) return [];
|
|
1227
|
+
const gatedTargets = resolvedTargets.filter((target) => {
|
|
1228
|
+
if (record.resolvedSeatsAtApply.has(target.seat)) {
|
|
1229
|
+
return record.gatedSeatsAtApply.has(target.seat);
|
|
1230
|
+
}
|
|
1231
|
+
return true;
|
|
1232
|
+
});
|
|
1233
|
+
const lifeTargets = evaluateLifetime({
|
|
1234
|
+
effect,
|
|
1235
|
+
operationTimelineIdx: record.operationInTimelineIdx,
|
|
1236
|
+
nowTimelineIndex,
|
|
1237
|
+
timelines,
|
|
1238
|
+
effector,
|
|
1239
|
+
targets: gatedTargets,
|
|
1240
|
+
snapshotSeatMap: playerSeatMap,
|
|
1241
|
+
characterMap,
|
|
1242
|
+
payloads,
|
|
1243
|
+
customResolver
|
|
1244
|
+
});
|
|
1245
|
+
return lifeTargets.map((t) => t.seat);
|
|
1246
|
+
};
|
|
1247
|
+
const reResolveRecords = (state) => {
|
|
1248
|
+
let anyChanged = false;
|
|
1217
1249
|
records.forEach((record) => {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
if (!ability) return;
|
|
1221
|
-
const effect = ability.effects[record.effectIdx];
|
|
1222
|
-
if (!effect) return;
|
|
1223
|
-
const effector = playerSeatMap.get(record.operation.effector);
|
|
1224
|
-
const payloads = record.operation.payloads || [];
|
|
1225
|
-
const targets = record.activeTargetSeats.map((seat) => playerSeatMap.get(seat)).filter((p) => Boolean(p));
|
|
1226
|
-
const liveTargets = evaluateLifetime({
|
|
1227
|
-
effect,
|
|
1228
|
-
operationTimelineIdx: record.operationInTimelineIdx,
|
|
1229
|
-
nowTimelineIndex,
|
|
1230
|
-
timelines,
|
|
1231
|
-
effector,
|
|
1232
|
-
targets,
|
|
1233
|
-
snapshotSeatMap: playerSeatMap,
|
|
1234
|
-
characterMap,
|
|
1235
|
-
payloads,
|
|
1236
|
-
customResolver
|
|
1237
|
-
});
|
|
1238
|
-
const newSeats = liveTargets.map((t) => t.seat);
|
|
1239
|
-
if (newSeats.length < record.activeTargetSeats.length) {
|
|
1250
|
+
const newSeats = reResolveRecord(record, state);
|
|
1251
|
+
if (!seatSetsEqual(newSeats, record.activeTargetSeats)) {
|
|
1240
1252
|
record.activeTargetSeats = newSeats;
|
|
1241
|
-
|
|
1253
|
+
anyChanged = true;
|
|
1242
1254
|
}
|
|
1243
1255
|
});
|
|
1244
|
-
return
|
|
1256
|
+
return anyChanged;
|
|
1245
1257
|
};
|
|
1246
1258
|
let lastState = copyPlayers(players);
|
|
1247
1259
|
allOpsInOrder.forEach((op) => {
|
|
@@ -1256,13 +1268,13 @@ var applyOperationToPlayers = ({
|
|
|
1256
1268
|
let state = rebuildState();
|
|
1257
1269
|
let iter = 0;
|
|
1258
1270
|
while (iter < FIXED_POINT_MAX_ITERATIONS) {
|
|
1259
|
-
const
|
|
1260
|
-
if (!
|
|
1271
|
+
const changed = reResolveRecords(state);
|
|
1272
|
+
if (!changed) break;
|
|
1261
1273
|
state = rebuildState();
|
|
1262
1274
|
iter += 1;
|
|
1263
1275
|
}
|
|
1264
1276
|
if (iter >= FIXED_POINT_MAX_ITERATIONS) {
|
|
1265
|
-
console.warn("applyOperationToPlayers:
|
|
1277
|
+
console.warn("applyOperationToPlayers: target re-resolution fixed-point did not converge within %d iterations after op %s", FIXED_POINT_MAX_ITERATIONS, op.abilityId);
|
|
1266
1278
|
}
|
|
1267
1279
|
lastState = state;
|
|
1268
1280
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bct-app/game-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Game engine utilities for BCT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@bct-app/game-model": "0.1.
|
|
33
|
+
"@bct-app/game-model": "0.1.11"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@vitest/ui": "^4.1.3",
|