kipphi 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bpm.ts +213 -0
- package/chart.ts +172 -80
- package/chartTypes.ts +62 -20
- package/easing.ts +89 -83
- package/env.ts +173 -0
- package/evaluator.ts +35 -11
- package/event.ts +273 -225
- package/index.ts +14 -11
- package/judgeline.ts +381 -84
- package/jumparray.ts +11 -11
- package/note.ts +31 -54
- package/operation.ts +1378 -0
- package/package.json +1 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +1 -2
- package/util.ts +21 -1
- package/version.ts +1 -1
package/operation.ts
ADDED
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
|
|
2
|
+
import TC from "./time";
|
|
3
|
+
import { EventType, EventValueESType, ExtendedEventTypeName, NoteType, RGB, TimeT } from "./chartTypes";
|
|
4
|
+
import { Chart, JudgeLineGroup, BasicEventName, UIName } from "./chart";
|
|
5
|
+
import { TemplateEasing, TemplateEasingLib } from "./easing";
|
|
6
|
+
import { EventEndNode, EventStartNode, EventNodeSequence, EventNode, EventNodeLike, NonLastStartNode, SpeedENS } from "./event";
|
|
7
|
+
import { JudgeLine, ExtendedLayer } from "./judgeline";
|
|
8
|
+
import { Note, notePropTypes, NoteNode, HNList, NNList } from "./note";
|
|
9
|
+
import { checkType, NodeType } from "./util";
|
|
10
|
+
import { EasedEvaluator, Evaluator, NumericEasedEvaluator } from "./evaluator";
|
|
11
|
+
import { err, ERROR_IDS, KPAError } from "./env";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// 有点怪异,感觉破坏了纯净性。不管了(
|
|
17
|
+
enum JudgeLinesEditorLayoutType {
|
|
18
|
+
ordered = 0b001,
|
|
19
|
+
tree = 0b010,
|
|
20
|
+
grouped = 0b100
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NeedsReflowEvent extends Event {
|
|
25
|
+
constructor(public condition: number) {
|
|
26
|
+
super("needsreflow");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class OperationEvent extends Event {
|
|
31
|
+
constructor(t: string, public operation: Operation) {
|
|
32
|
+
super(t);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class OperationErrorEvent extends OperationEvent {
|
|
37
|
+
constructor(operation: Operation, public error: Error) {
|
|
38
|
+
super("error", operation);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class OperationList extends EventTarget {
|
|
43
|
+
operations: Operation[];
|
|
44
|
+
undoneOperations: Operation[];
|
|
45
|
+
constructor(public chart: Chart) {
|
|
46
|
+
super()
|
|
47
|
+
this.operations = [];
|
|
48
|
+
this.undoneOperations = [];
|
|
49
|
+
}
|
|
50
|
+
undo() {
|
|
51
|
+
const op = this.operations.pop()
|
|
52
|
+
if (op) {
|
|
53
|
+
if (!this.chart.modified){
|
|
54
|
+
this.chart.modified = true;
|
|
55
|
+
this.dispatchEvent(new Event("firstmodified"))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
op.undo(this.chart);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
this.dispatchEvent(new OperationErrorEvent(op, e as Error))
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
this.undoneOperations.push(op)
|
|
65
|
+
this.dispatchEvent(new OperationEvent("undo", op))
|
|
66
|
+
this.processFlags(op);
|
|
67
|
+
} else {
|
|
68
|
+
this.dispatchEvent(new Event("noundo"))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
redo() {
|
|
72
|
+
const op = this.undoneOperations.pop()
|
|
73
|
+
if (op) {
|
|
74
|
+
if (!this.chart.modified){
|
|
75
|
+
this.chart.modified = true;
|
|
76
|
+
this.dispatchEvent(new Event("firstmodified"))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
op.do(this.chart);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
this.dispatchEvent(new OperationErrorEvent(op, e as Error))
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
this.operations.push(op)
|
|
86
|
+
this.dispatchEvent(new OperationEvent("redo", op))
|
|
87
|
+
this.processFlags(op);
|
|
88
|
+
} else {
|
|
89
|
+
this.dispatchEvent(new Event("noredo"))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
do(operation: Operation) {
|
|
93
|
+
if (operation.ineffective) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
if (!this.chart.modified){
|
|
97
|
+
this.chart.modified = true;
|
|
98
|
+
this.dispatchEvent(new Event("firstmodified"))
|
|
99
|
+
}
|
|
100
|
+
// 如果上一个操作是同一个构造器的,那么修改上一个操作而不是推入新的操作
|
|
101
|
+
if (this.operations.length !== 0) {
|
|
102
|
+
|
|
103
|
+
const lastOp = this.operations[this.operations.length - 1]
|
|
104
|
+
if (operation.constructor === lastOp.constructor) {
|
|
105
|
+
// 返回值指示是否重写成功
|
|
106
|
+
if (lastOp.rewrite(operation)) {
|
|
107
|
+
this.processFlags(operation)
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
operation.do(this.chart);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
this.dispatchEvent(new OperationErrorEvent(operation, e as Error))
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
this.dispatchEvent(new OperationEvent("do", operation));
|
|
119
|
+
this.processFlags(operation);
|
|
120
|
+
this.operations.push(operation);
|
|
121
|
+
}
|
|
122
|
+
processFlags(operation: Operation) {
|
|
123
|
+
|
|
124
|
+
if (operation.updatesEditor) {
|
|
125
|
+
this.dispatchEvent(new Event("needsupdate"));
|
|
126
|
+
}
|
|
127
|
+
if (operation.needsComboRecount) {
|
|
128
|
+
this.dispatchEvent(new Event("maxcombochanged"));
|
|
129
|
+
}
|
|
130
|
+
if (operation.reflows) {
|
|
131
|
+
this.dispatchEvent(new NeedsReflowEvent(operation.reflows))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
clear() {
|
|
135
|
+
this.operations = [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
export abstract class Operation {
|
|
142
|
+
ineffective: boolean;
|
|
143
|
+
updatesEditor: boolean;
|
|
144
|
+
// 用于判定线编辑区的重排,若操作完成时的布局为这个值就会重排
|
|
145
|
+
reflows: number;
|
|
146
|
+
needsComboRecount: boolean;
|
|
147
|
+
constructor() {
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
abstract do(chart: Chart): void
|
|
151
|
+
abstract undo(chart: Chart): void
|
|
152
|
+
rewrite(op: typeof this): boolean {return false;}
|
|
153
|
+
toString(): string {
|
|
154
|
+
return this.constructor.name;
|
|
155
|
+
}
|
|
156
|
+
static lazy<C extends new (...args: any[]) => any = typeof this>(this: C, ...args: ConstructorParameters<C>) {
|
|
157
|
+
return new LazyOperation<C>(this, ...args)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 懒操作,实例化的时候不记录任何数据,do的时候才执行真正实例化
|
|
166
|
+
* 防止连续的操作中状态改变导致的错误
|
|
167
|
+
*/
|
|
168
|
+
class LazyOperation<C extends new (...args: any[]) => any> extends Operation {
|
|
169
|
+
public operationClass: C;
|
|
170
|
+
public args: ConstructorParameters<C>;
|
|
171
|
+
public operation: InstanceType<C> | null = null;
|
|
172
|
+
constructor(
|
|
173
|
+
operationClass: C,
|
|
174
|
+
...args: ConstructorParameters<C>
|
|
175
|
+
) {
|
|
176
|
+
super();
|
|
177
|
+
this.operationClass = operationClass;
|
|
178
|
+
this.args = args;
|
|
179
|
+
}
|
|
180
|
+
do(chart: Chart) {
|
|
181
|
+
this.operation = new this.operationClass(...this.args);
|
|
182
|
+
this.operation.do(chart);
|
|
183
|
+
}
|
|
184
|
+
undo(chart: Chart) {
|
|
185
|
+
this.operation.undo(chart);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* C语言借来的概念
|
|
192
|
+
*
|
|
193
|
+
* 一个不确定类型的子操作
|
|
194
|
+
*
|
|
195
|
+
* 注意这个操作不懒,会在构造时就实例化子操作
|
|
196
|
+
*/
|
|
197
|
+
class UnionOperation<T extends Operation> extends Operation {
|
|
198
|
+
operation: T;
|
|
199
|
+
constructor(matcher: () => T) {
|
|
200
|
+
super();
|
|
201
|
+
this.operation = matcher();
|
|
202
|
+
if (!this.operation) {
|
|
203
|
+
this.ineffective = true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// 这样子写不够严密,如果要继承这个类,并且子操作需要谱面,就要重写这个方法的签名
|
|
207
|
+
do(chart?: Chart) {
|
|
208
|
+
this.operation.do(chart);
|
|
209
|
+
}
|
|
210
|
+
undo(chart?: Chart) {
|
|
211
|
+
this.operation.undo(chart);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
export class ComplexOperation<T extends Operation[]> extends Operation {
|
|
217
|
+
subOperations: T;
|
|
218
|
+
length: number;
|
|
219
|
+
constructor(...sub: T) {
|
|
220
|
+
super()
|
|
221
|
+
this.subOperations = sub
|
|
222
|
+
this.length = sub.length
|
|
223
|
+
this.reflows = sub.reduce((prev, op) => prev | op.reflows, 0);
|
|
224
|
+
this.updatesEditor = sub.some((op) => op.updatesEditor);
|
|
225
|
+
this.needsComboRecount = sub.some((op) => op.needsComboRecount);
|
|
226
|
+
}
|
|
227
|
+
// 这样子写不够严密,如果要继承这个类,并且子操作需要谱面,就要重写这个方法的签名
|
|
228
|
+
do(chart?: Chart) {
|
|
229
|
+
const length = this.length
|
|
230
|
+
for (let i = 0; i < length; i++) {
|
|
231
|
+
const op = this.subOperations[i]
|
|
232
|
+
if (op.ineffective) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
op.do(chart)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
undo(chart?: Chart) {
|
|
239
|
+
const length = this.length
|
|
240
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
241
|
+
const op = this.subOperations[i]
|
|
242
|
+
if (op.ineffective) { continue; }
|
|
243
|
+
op.undo(chart)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
type NotePropNamePhiZone = "judgeSize" | "tint" | "tintHitEffects";
|
|
249
|
+
|
|
250
|
+
type NotePropName = "speed" | "type" | "positionX" | "startTime" | "endTime" | "alpha" | "size" | "visibleBeats" | "yOffset" | "above" | "isFake" | NotePropNamePhiZone;
|
|
251
|
+
|
|
252
|
+
export class NotePropChangeOperation<T extends NotePropName> extends Operation {
|
|
253
|
+
field: T;
|
|
254
|
+
note: Note;
|
|
255
|
+
previousValue: Note[T]
|
|
256
|
+
value: Note[T];
|
|
257
|
+
updatesEditor = true;
|
|
258
|
+
constructor(note: Note, field: T, value: Note[T]) {
|
|
259
|
+
super()
|
|
260
|
+
this.field = field
|
|
261
|
+
this.note = note;
|
|
262
|
+
this.value = value;
|
|
263
|
+
if (!checkType(value, notePropTypes[field])) {
|
|
264
|
+
throw err.INVALID_NOTE_PROP_TYPE(field, value, notePropTypes[field]);
|
|
265
|
+
}
|
|
266
|
+
this.previousValue = note[field]
|
|
267
|
+
if (field === "isFake") {
|
|
268
|
+
this.needsComboRecount = true;
|
|
269
|
+
}
|
|
270
|
+
if (value === note[field]) {
|
|
271
|
+
this.ineffective = true
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
do() {
|
|
275
|
+
this.note[this.field] = this.value
|
|
276
|
+
}
|
|
277
|
+
undo() {
|
|
278
|
+
this.note[this.field] = this.previousValue
|
|
279
|
+
}
|
|
280
|
+
override rewrite(operation: NotePropChangeOperation<T>): boolean {
|
|
281
|
+
if (operation.note === this.note && this.field === operation.field) {
|
|
282
|
+
this.value = operation.value;
|
|
283
|
+
this.note[this.field] = operation.value
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export class NoteRemoveOperation extends Operation {
|
|
290
|
+
noteNode: NoteNode;
|
|
291
|
+
note: Note;
|
|
292
|
+
isHold: boolean;
|
|
293
|
+
override needsComboRecount = true;
|
|
294
|
+
constructor(note: Note) {
|
|
295
|
+
super()
|
|
296
|
+
this.note = note // In memory of forgettting to add this(
|
|
297
|
+
this.isHold = note.type === NoteType.hold;
|
|
298
|
+
if (!note.parentNode) {
|
|
299
|
+
this.ineffective = true
|
|
300
|
+
} else {
|
|
301
|
+
this.noteNode = note.parentNode
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
do() {
|
|
305
|
+
const {note, noteNode} = this;
|
|
306
|
+
noteNode.remove(note);
|
|
307
|
+
const needsUpdate = this.isHold && TC.lt(noteNode.endTime, note.endTime)
|
|
308
|
+
if (needsUpdate) {
|
|
309
|
+
const endBeats = TC.toBeats(note.endTime);
|
|
310
|
+
const tailJump = (noteNode.parentSeq as HNList).holdTailJump;
|
|
311
|
+
const updateFrom = tailJump.header
|
|
312
|
+
const updateTo = tailJump.tailer;
|
|
313
|
+
// tailJump.getPreviousOf(noteNode, endBeats);
|
|
314
|
+
tailJump.updateRange(updateFrom, noteNode.next);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
undo() {
|
|
318
|
+
const {note, noteNode} = this;
|
|
319
|
+
const needsUpdate = this.isHold && TC.lt(noteNode.endTime, note.endTime);
|
|
320
|
+
if (needsUpdate) {
|
|
321
|
+
const endBeats = TC.toBeats(note.endTime);
|
|
322
|
+
const tailJump = (noteNode.parentSeq as HNList).holdTailJump;
|
|
323
|
+
const updateFrom = tailJump.getNodeAt(endBeats).previous;
|
|
324
|
+
noteNode.add(note)
|
|
325
|
+
tailJump.updateRange(updateFrom, noteNode.next);
|
|
326
|
+
} else {
|
|
327
|
+
noteNode.add(note);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* 删除一个note
|
|
334
|
+
* 从语义上删除Note要用这个操作
|
|
335
|
+
* 结果上,这个会更新编辑器
|
|
336
|
+
*/
|
|
337
|
+
export class NoteDeleteOperation extends NoteRemoveOperation {
|
|
338
|
+
updatesEditor = true
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export class MultiNoteDeleteOperation extends ComplexOperation<NoteDeleteOperation[]> {
|
|
342
|
+
updatesEditor = true
|
|
343
|
+
constructor(notes: Set<Note> | Note[]) {
|
|
344
|
+
if (notes instanceof Set) {
|
|
345
|
+
notes = [...notes];
|
|
346
|
+
}
|
|
347
|
+
super(...notes.map(note => new NoteDeleteOperation(note)))
|
|
348
|
+
if (notes.length === 0) {
|
|
349
|
+
this.ineffective = true
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export class NoteAddOperation extends Operation {
|
|
356
|
+
noteNode: NoteNode
|
|
357
|
+
note: Note;
|
|
358
|
+
isHold: boolean;
|
|
359
|
+
updatesEditor = true
|
|
360
|
+
needsComboRecount = true;
|
|
361
|
+
constructor(note: Note, node: NoteNode) {
|
|
362
|
+
super()
|
|
363
|
+
this.note = note;
|
|
364
|
+
this.isHold = note.type === NoteType.hold;
|
|
365
|
+
this.noteNode = node
|
|
366
|
+
}
|
|
367
|
+
do() {
|
|
368
|
+
const {note, noteNode} = this;
|
|
369
|
+
const needsUpdate = this.isHold && TC.lt(noteNode.endTime, note.endTime);
|
|
370
|
+
if (needsUpdate) {
|
|
371
|
+
const endBeats = TC.toBeats(note.endTime);
|
|
372
|
+
const tailJump = (noteNode.parentSeq as HNList).holdTailJump;
|
|
373
|
+
const updateFrom = tailJump.header
|
|
374
|
+
// tailJump.getNodeAt(endBeats).previous;
|
|
375
|
+
noteNode.add(note)
|
|
376
|
+
tailJump.updateRange(updateFrom, noteNode.next);
|
|
377
|
+
} else {
|
|
378
|
+
noteNode.add(note);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
undo() {
|
|
382
|
+
const {note, noteNode} = this;
|
|
383
|
+
noteNode.remove(note);
|
|
384
|
+
const needsUpdate = this.isHold && TC.lt(noteNode.endTime, note.endTime)
|
|
385
|
+
if (needsUpdate) {
|
|
386
|
+
const endBeats = TC.toBeats(note.endTime);
|
|
387
|
+
const tailJump = (noteNode.parentSeq as HNList).holdTailJump;
|
|
388
|
+
const updateFrom = tailJump.getPreviousOf(noteNode, endBeats);
|
|
389
|
+
tailJump.updateRange(updateFrom, noteNode.next);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export class MultiNoteAddOperation extends ComplexOperation<NoteAddOperation[]> {
|
|
395
|
+
updatesEditor = true
|
|
396
|
+
needsComboRecount = true;
|
|
397
|
+
constructor(notes: Set<Note> | Note[], judgeLine: JudgeLine) {
|
|
398
|
+
if (notes instanceof Set) {
|
|
399
|
+
notes = [...notes];
|
|
400
|
+
}
|
|
401
|
+
super(...notes.map(note => {
|
|
402
|
+
const node = judgeLine.getNode(note, true)
|
|
403
|
+
return new NoteAddOperation(note, node);
|
|
404
|
+
}))
|
|
405
|
+
if (notes.length === 0) {
|
|
406
|
+
this.ineffective = true
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export class NoteTimeChangeOperation extends ComplexOperation<[
|
|
412
|
+
NoteRemoveOperation,
|
|
413
|
+
NotePropChangeOperation<"startTime">,
|
|
414
|
+
NoteAddOperation,
|
|
415
|
+
UnionOperation<NotePropChangeOperation<"endTime"> | null>]> {
|
|
416
|
+
note: Note
|
|
417
|
+
constructor(note: Note, noteNode: NoteNode) {
|
|
418
|
+
super(
|
|
419
|
+
new NoteRemoveOperation(note),
|
|
420
|
+
new NotePropChangeOperation(note, "startTime", noteNode.startTime),
|
|
421
|
+
new NoteAddOperation(note, noteNode),
|
|
422
|
+
new UnionOperation(() => {
|
|
423
|
+
if (note.type !== NoteType.hold) { // 非hold,endTime跟随startTime
|
|
424
|
+
return new NotePropChangeOperation(note, "endTime", noteNode.startTime)
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
this.updatesEditor = true
|
|
429
|
+
this.needsComboRecount = false;
|
|
430
|
+
if (note.type === NoteType.hold && !TC.gt(note.endTime, noteNode.startTime)) {
|
|
431
|
+
this.ineffective = true
|
|
432
|
+
}
|
|
433
|
+
this.note = note
|
|
434
|
+
if (note.parentNode === noteNode) {
|
|
435
|
+
this.ineffective = true
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// 真的是巨坑啊
|
|
439
|
+
rewrite(operation: NoteTimeChangeOperation): boolean {
|
|
440
|
+
if (operation.note === this.note) {
|
|
441
|
+
this.subOperations[0] = new NoteRemoveOperation(this.note)
|
|
442
|
+
if (!this.subOperations[0].ineffective) {
|
|
443
|
+
this.subOperations[0].do()
|
|
444
|
+
}
|
|
445
|
+
this.subOperations[1].value = operation.subOperations[1].value
|
|
446
|
+
this.subOperations[1].do()
|
|
447
|
+
this.subOperations[2].noteNode = operation.subOperations[2].noteNode
|
|
448
|
+
this.subOperations[2].do()
|
|
449
|
+
if (this.subOperations[3].operation) {
|
|
450
|
+
this.subOperations[3].operation.value = operation.subOperations[3].operation.value;
|
|
451
|
+
this.subOperations[3].operation.do();
|
|
452
|
+
} else {
|
|
453
|
+
this.subOperations[3] = operation.subOperations[3];
|
|
454
|
+
this.subOperations[3].do();
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
return false
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export class HoldEndTimeChangeOperation extends NotePropChangeOperation<"endTime"> {
|
|
463
|
+
|
|
464
|
+
needsComboRecount = false;
|
|
465
|
+
constructor(note: Note, value: TimeT) {
|
|
466
|
+
super(note, "endTime", value)
|
|
467
|
+
if (!TC.gt(value, note.startTime)) {
|
|
468
|
+
this.ineffective = true
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
do() {
|
|
472
|
+
super.do()
|
|
473
|
+
const node = this.note.parentNode;
|
|
474
|
+
node.sort(this.note);
|
|
475
|
+
const tailJump = (node.parentSeq as HNList).holdTailJump;
|
|
476
|
+
tailJump.updateRange(tailJump.header, tailJump.tailer);
|
|
477
|
+
}
|
|
478
|
+
undo() {
|
|
479
|
+
super.undo()
|
|
480
|
+
const node = this.note.parentNode;
|
|
481
|
+
node.sort(this.note);
|
|
482
|
+
const tailJump = (node.parentSeq as HNList).holdTailJump;
|
|
483
|
+
tailJump.updateRange(tailJump.header, tailJump.tailer);
|
|
484
|
+
}
|
|
485
|
+
rewrite(operation: HoldEndTimeChangeOperation): boolean { // 看懂了,不重写的话会出问题
|
|
486
|
+
if (operation.note === this.note && this.field === operation.field) {
|
|
487
|
+
if (operation.value === this.value) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
this.value = operation.value;
|
|
491
|
+
this.note[this.field] = operation.value;
|
|
492
|
+
const tailJump = (this.note.parentNode.parentSeq as HNList).holdTailJump;
|
|
493
|
+
tailJump.updateRange(tailJump.header, tailJump.tailer);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
export class NoteSpeedChangeOperation
|
|
502
|
+
extends ComplexOperation<[NotePropChangeOperation<"speed">, NoteRemoveOperation, NoteAddOperation]> {
|
|
503
|
+
updatesEditor = true
|
|
504
|
+
originalTree: NNList;
|
|
505
|
+
judgeLine: JudgeLine;
|
|
506
|
+
targetTree: NNList
|
|
507
|
+
constructor(note: Note, value: number, line: JudgeLine) {
|
|
508
|
+
const valueChange = new NotePropChangeOperation(note, "speed", value);
|
|
509
|
+
const tree = line.getNNList(value, note.yOffset, note.type === NoteType.hold, true)
|
|
510
|
+
const node = tree.getNodeOf(note.startTime);
|
|
511
|
+
const removal = new NoteRemoveOperation(note);
|
|
512
|
+
const insert = new NoteAddOperation(note, node)
|
|
513
|
+
super(valueChange, removal, insert);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export class NoteYOffsetChangeOperation
|
|
518
|
+
extends ComplexOperation<[NotePropChangeOperation<"yOffset">, NoteRemoveOperation, NoteAddOperation]> {
|
|
519
|
+
updatesEditor = true
|
|
520
|
+
originalTree: NNList;
|
|
521
|
+
judgeLine: JudgeLine;
|
|
522
|
+
targetTree: NNList
|
|
523
|
+
constructor(note: Note, value: number, line: JudgeLine) {
|
|
524
|
+
const valueChange = new NotePropChangeOperation(note, "yOffset", value);
|
|
525
|
+
const tree = line.getNNList(note.speed, value, note.type === NoteType.hold, true)
|
|
526
|
+
const node = tree.getNodeOf(note.startTime);
|
|
527
|
+
const removal = new NoteRemoveOperation(note);
|
|
528
|
+
const insert = new NoteAddOperation(note, node)
|
|
529
|
+
super(valueChange, removal, insert);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
export class NoteTypeChangeOperation
|
|
535
|
+
extends ComplexOperation<[NotePropChangeOperation<"type">, NoteRemoveOperation, NoteAddOperation] | [NotePropChangeOperation<"type">]> {
|
|
536
|
+
constructor(note: Note, value: number) {
|
|
537
|
+
const isHold = note.type === NoteType.hold
|
|
538
|
+
const valueChange = new NotePropChangeOperation(note, "type", value);
|
|
539
|
+
if (isHold !== (value === NoteType.hold)) {
|
|
540
|
+
const tree = note.parentNode.parentSeq.parentLine.getNNList(note.speed, note.yOffset, !isHold, true)
|
|
541
|
+
const node = tree.getNodeOf(note.startTime);
|
|
542
|
+
const removal = new NoteRemoveOperation(note);
|
|
543
|
+
const insert = new NoteAddOperation(note, node);
|
|
544
|
+
super(valueChange, removal, insert);
|
|
545
|
+
} else {
|
|
546
|
+
super(valueChange);
|
|
547
|
+
}
|
|
548
|
+
this.updatesEditor = true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
class NoteTreeChangeOperation extends NoteAddOperation {
|
|
553
|
+
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* 移除一对节点(背靠背)
|
|
558
|
+
*/
|
|
559
|
+
export class EventNodePairRemoveOperation extends Operation {
|
|
560
|
+
updatesEditor = true;
|
|
561
|
+
endNode: EventEndNode<any>;
|
|
562
|
+
startNode: EventStartNode<any>;
|
|
563
|
+
sequence: EventNodeSequence<any>;
|
|
564
|
+
originalPrev: EventStartNode<any>;
|
|
565
|
+
updatesFP = false;
|
|
566
|
+
constructor(node: EventStartNode<any>, updatesFP = true) {
|
|
567
|
+
super();
|
|
568
|
+
if (node.previous === null) {
|
|
569
|
+
this.ineffective = true;
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (node.isFirstStart()) {
|
|
573
|
+
this.ineffective = true;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (node.isSpeed()) {
|
|
577
|
+
updatesFP = updatesFP;
|
|
578
|
+
}
|
|
579
|
+
[this.endNode, this.startNode] = EventNode.getEndStart(node)
|
|
580
|
+
this.sequence = this.startNode.parentSeq
|
|
581
|
+
this.originalPrev = (<EventEndNode>node.previous).previous
|
|
582
|
+
}
|
|
583
|
+
do(chart: Chart) {
|
|
584
|
+
this.sequence.updateJump(...EventNode.removeNodePair(this.endNode, this.startNode))
|
|
585
|
+
if (this.updatesFP) {
|
|
586
|
+
// updatesFP的校验确保了序列为速度序列
|
|
587
|
+
(this.sequence as SpeedENS).updateFloorPositionAfter(this.originalPrev, chart.timeCalculator)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
undo(chart: Chart) {
|
|
591
|
+
this.sequence.updateJump(...EventNode.insert(this.startNode, this.originalPrev))
|
|
592
|
+
if (this.updatesFP) {
|
|
593
|
+
(this.sequence as SpeedENS).updateFloorPositionAfter(this.originalPrev, chart.timeCalculator)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* 将一对孤立的节点对插入到一个开始节点之后的操作。
|
|
600
|
+
*
|
|
601
|
+
* 如果这个节点对的时刻与节点对的时刻相同,那么抛出错误。
|
|
602
|
+
*/
|
|
603
|
+
export class EventNodePairInsertOperation<VT> extends Operation {
|
|
604
|
+
updatesEditor = true
|
|
605
|
+
node: EventStartNode<VT>;
|
|
606
|
+
tarPrev: EventStartNode<VT>;
|
|
607
|
+
sequence: EventNodeSequence<VT>;
|
|
608
|
+
originalValue: VT;
|
|
609
|
+
value: VT;
|
|
610
|
+
updatesFP = false;
|
|
611
|
+
/**
|
|
612
|
+
*
|
|
613
|
+
* @param node 要插入的节点 the node to insert
|
|
614
|
+
* @param targetPrevious 要插在谁后面 The node to insert after, accessed through `EventNodeSequence.getNodeAt(TC.toBeats(node))`
|
|
615
|
+
*/
|
|
616
|
+
constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP = true) {
|
|
617
|
+
super()
|
|
618
|
+
this.node = node;
|
|
619
|
+
this.tarPrev = targetPrevious
|
|
620
|
+
this.sequence = targetPrevious.parentSeq
|
|
621
|
+
if (TC.eq(node.time, targetPrevious.time)) {
|
|
622
|
+
throw err.SEQUENCE_NODE_TIME_OCCUPIED(node.time, this.sequence.id);
|
|
623
|
+
}
|
|
624
|
+
if (!this.sequence) {
|
|
625
|
+
throw err.PARENT_SEQUENCE_NOT_FOUND(targetPrevious?.time);
|
|
626
|
+
}
|
|
627
|
+
this.updatesFP = updatesFP && node.isSpeed();
|
|
628
|
+
}
|
|
629
|
+
do() {
|
|
630
|
+
const [endNode, startNode] = EventNode.insert(this.node, this.tarPrev);
|
|
631
|
+
this.node.parentSeq.updateJump(endNode, startNode)
|
|
632
|
+
}
|
|
633
|
+
undo() {
|
|
634
|
+
this.sequence.updateJump(...EventNode.removeNodePair(...EventNode.getEndStart(this.node)))
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export class EventNodePairInsertOrOverwriteOperation<VT>
|
|
639
|
+
extends UnionOperation<EventNodePairInsertOperation<VT> | EventNodeValueChangeOperation<VT>> {
|
|
640
|
+
overlapping: boolean = false;
|
|
641
|
+
constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP = true) {
|
|
642
|
+
super(() => {
|
|
643
|
+
if (TC.eq(node.time, targetPrevious.time)) {
|
|
644
|
+
this.overlapping = true;
|
|
645
|
+
return new EventNodeValueChangeOperation(targetPrevious, node.value);
|
|
646
|
+
} else {
|
|
647
|
+
return new EventNodePairInsertOperation(node, targetPrevious, updatesFP);
|
|
648
|
+
}
|
|
649
|
+
})
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* 批量添加节点对
|
|
656
|
+
*
|
|
657
|
+
* 节点对需要有序的,且不能有重叠
|
|
658
|
+
|
|
659
|
+
*/
|
|
660
|
+
export class MultiNodeAddOperation<VT> extends ComplexOperation<EventNodePairInsertOrOverwriteOperation<VT>[]> {
|
|
661
|
+
updatesEditor = true
|
|
662
|
+
updatesFP = false;
|
|
663
|
+
constructor(public nodes: EventStartNode<VT>[], public seq: EventNodeSequence<VT>) {
|
|
664
|
+
let prev = seq.getNodeAt(TC.toBeats(nodes[0].time));
|
|
665
|
+
super(...nodes.map(node => {
|
|
666
|
+
const op = new EventNodePairInsertOrOverwriteOperation(node, prev,false);
|
|
667
|
+
if (!op.overlapping) prev = node; // 有种reduce的感觉
|
|
668
|
+
return op
|
|
669
|
+
}));
|
|
670
|
+
this.updatesFP = seq.type === EventType.speed
|
|
671
|
+
}
|
|
672
|
+
do(chart: Chart) {
|
|
673
|
+
super.do();
|
|
674
|
+
if (this.updatesFP) {
|
|
675
|
+
(this.seq as SpeedENS).updateFloorPositionAfter(this.nodes[0] as EventStartNode<number>, chart.timeCalculator)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
undo(chart: Chart) {
|
|
679
|
+
super.undo();
|
|
680
|
+
if (this.updatesFP) {
|
|
681
|
+
(this.seq as SpeedENS).updateFloorPositionAfter(this.nodes[0] as EventStartNode<number>, chart.timeCalculator)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
export class MultiNodeDeleteOperation extends ComplexOperation<LazyOperation<typeof EventNodePairRemoveOperation>[]> {
|
|
687
|
+
updatesEditor = true;
|
|
688
|
+
constructor(nodes: EventStartNode<any>[]) {
|
|
689
|
+
super(...nodes.map(node => EventNodePairRemoveOperation.lazy(node)));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export class EventNodeValueChangeOperation<VT> extends Operation {
|
|
694
|
+
updatesEditor = true
|
|
695
|
+
node: EventNode<VT>
|
|
696
|
+
value: VT;
|
|
697
|
+
originalValue: VT
|
|
698
|
+
constructor(node: EventNode<VT>, val: VT) {
|
|
699
|
+
super()
|
|
700
|
+
this.node = node
|
|
701
|
+
this.value = val;
|
|
702
|
+
this.originalValue = node.value
|
|
703
|
+
}
|
|
704
|
+
do() {
|
|
705
|
+
this.node.value = this.value
|
|
706
|
+
}
|
|
707
|
+
undo() {
|
|
708
|
+
this.node.value = this.originalValue
|
|
709
|
+
}
|
|
710
|
+
rewrite(operation: EventNodeValueChangeOperation<VT>): boolean {
|
|
711
|
+
if (operation.node === this.node) {
|
|
712
|
+
this.value = operation.value;
|
|
713
|
+
this.node.value = operation.value
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export class EventNodeTimeChangeOperation extends Operation {
|
|
721
|
+
updatesEditor = true
|
|
722
|
+
sequence: EventNodeSequence;
|
|
723
|
+
/**
|
|
724
|
+
* 这里两个node不是面对面,而是背靠背
|
|
725
|
+
* i. e. EndNode -> StartNode
|
|
726
|
+
*/
|
|
727
|
+
startNode: EventStartNode<any>;
|
|
728
|
+
endNode: EventEndNode<any>;
|
|
729
|
+
value: TimeT;
|
|
730
|
+
originalValue: TimeT;
|
|
731
|
+
originalPrevious: EventStartNode<any>;
|
|
732
|
+
newPrevious: EventStartNode<any>;
|
|
733
|
+
constructor(node: EventStartNode<any> | EventEndNode<any>, val: TimeT) {
|
|
734
|
+
super()
|
|
735
|
+
if (node.previous.type === NodeType.HEAD) {
|
|
736
|
+
this.ineffective = true;
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!TC.gt(val, [0, 0, 1])) {
|
|
740
|
+
this.ineffective = true;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
[this.endNode, this.startNode] = EventNode.getEndStart(node)
|
|
744
|
+
const seq = this.sequence = node.parentSeq
|
|
745
|
+
const mayBeThere = seq.getNodeAt(TC.toBeats(val))
|
|
746
|
+
if (mayBeThere && TC.eq(mayBeThere.time, val)) { // 不是arrayEq,这里踩坑
|
|
747
|
+
this.ineffective = true;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
this.originalPrevious = this.endNode.previous;
|
|
751
|
+
this.newPrevious = mayBeThere === this.startNode ? (<EventEndNode>this.startNode.previous).previous : mayBeThere
|
|
752
|
+
this.value = val;
|
|
753
|
+
this.originalValue = node.time
|
|
754
|
+
console.log("操作:", this)
|
|
755
|
+
}
|
|
756
|
+
do() { // 这里其实还要设计重新选址的问题
|
|
757
|
+
this.startNode.time = this.endNode.time = this.value;
|
|
758
|
+
if (this.newPrevious !== this.originalPrevious) {
|
|
759
|
+
this.sequence.updateJump(...EventNode.removeNodePair(this.endNode, this.startNode))
|
|
760
|
+
EventNode.insert(this.startNode, this.newPrevious)
|
|
761
|
+
}
|
|
762
|
+
this.sequence.updateJump(EventNode.previousStartOfStart(this.endNode.previous), EventNode.nextStartOfStart(this.startNode))
|
|
763
|
+
}
|
|
764
|
+
undo() {
|
|
765
|
+
this.endNode.time = this.startNode.time = this.originalValue;
|
|
766
|
+
if (this.newPrevious !== this.originalPrevious) {
|
|
767
|
+
this.sequence.updateJump(...EventNode.removeNodePair(this.endNode, this.startNode))
|
|
768
|
+
EventNode.insert(this.startNode, this.originalPrevious)
|
|
769
|
+
}
|
|
770
|
+
this.sequence.updateJump(this.endNode.previous, EventNode.nextStartOfStart(this.startNode))
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
export class EventNodeEvaluatorChangeOperation<VT> extends Operation {
|
|
777
|
+
updatesEditor = true
|
|
778
|
+
originalValue: Evaluator<VT>
|
|
779
|
+
constructor(public node: EventStartNode<VT>, public value: Evaluator<VT>) {
|
|
780
|
+
super();
|
|
781
|
+
this.originalValue = this.node.evaluator
|
|
782
|
+
}
|
|
783
|
+
do() {
|
|
784
|
+
this.node.evaluator = this.value
|
|
785
|
+
}
|
|
786
|
+
undo() {
|
|
787
|
+
this.node.evaluator = this.originalValue
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
// 这个地方得懒一下,不然每亩,导致撤回操作时只能撤回第一个插值节点。
|
|
795
|
+
export class EventInterpolationOperation<VT> extends ComplexOperation<LazyOperation<typeof EventNodePairInsertOperation>[]> {
|
|
796
|
+
updatesEditor = true;
|
|
797
|
+
constructor(public eventStartNode: EventStartNode<VT>, public step: TimeT) {
|
|
798
|
+
if (eventStartNode.next.type === NodeType.TAIL) {
|
|
799
|
+
throw err.CANNOT_INTERPOLATE_TAILING_START_NODE();
|
|
800
|
+
}
|
|
801
|
+
const subOps = [];
|
|
802
|
+
const endTime = eventStartNode.next.time;
|
|
803
|
+
let time = TC.validateIp(TC.add(eventStartNode.time, step));
|
|
804
|
+
let lastStart = eventStartNode;
|
|
805
|
+
for (; TC.lt(time, endTime); time = TC.validateIp(TC.add(time, step))) {
|
|
806
|
+
const value = eventStartNode.getValueAt(TC.toBeats(time));
|
|
807
|
+
const start = new EventStartNode(time, value);
|
|
808
|
+
const end = new EventEndNode(time, value);
|
|
809
|
+
EventNode.connect(end, start); // 之前搞成面对面了,写注释留念
|
|
810
|
+
subOps.push(EventNodePairInsertOperation.lazy(start, lastStart));
|
|
811
|
+
lastStart = start;
|
|
812
|
+
}
|
|
813
|
+
super(...subOps);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
export class EventSubstituteOperation<VT extends EventValueESType> extends ComplexOperation<[...LazyOperation<typeof EventNodePairInsertOperation>[], EventNodeEvaluatorChangeOperation<VT>, EventNodeValueChangeOperation<VT>]> {
|
|
821
|
+
updatesEditor = true;
|
|
822
|
+
/**
|
|
823
|
+
*
|
|
824
|
+
* @param node
|
|
825
|
+
* @throws {KPAError<ERROR_IDS.CANNOT_SUBSTITUTE_EXPRESSION_EVALUATOR>}
|
|
826
|
+
* @throws {KPAError<ERROR_IDS.MUST_INTERPOLATE_TEMPLATE_EASING>}
|
|
827
|
+
*/
|
|
828
|
+
constructor(public node: NonLastStartNode<VT>) {
|
|
829
|
+
const evaluator = node.evaluator;
|
|
830
|
+
if (!(evaluator instanceof EasedEvaluator)) {
|
|
831
|
+
throw err.CANNOT_SUBSTITUTE_EXPRESSION_EVALUATOR();
|
|
832
|
+
}
|
|
833
|
+
const easing = evaluator.easing;
|
|
834
|
+
if (!(easing instanceof TemplateEasing)) {
|
|
835
|
+
throw err.MUST_INTERPOLATE_TEMPLATE_EASING();
|
|
836
|
+
}
|
|
837
|
+
const srcSeq = easing.eventNodeSequence;
|
|
838
|
+
const srcStartTime = srcSeq.head.next.time;
|
|
839
|
+
const srcEndTime = srcSeq.tail.previous.time;
|
|
840
|
+
const srcTimeDelta = TC.vsub(srcEndTime, srcStartTime);
|
|
841
|
+
const startValue = node.value;
|
|
842
|
+
const endValue = node.next.value;
|
|
843
|
+
const startTime = node.time;
|
|
844
|
+
const timeDelta = TC.vsub(node.next.time, startTime);
|
|
845
|
+
const convert = (progress: number) => (evaluator as EasedEvaluator<VT>).convert(startValue, endValue, progress);
|
|
846
|
+
|
|
847
|
+
// startTime + (srcTime - srcStartTime) / srcTimeDelta * timeDelta
|
|
848
|
+
const convertTime = (srcTime: TimeT) => TC.vadd(startTime, TC.vmul(timeDelta, TC.div(TC.vsub(srcTime, srcStartTime), srcTimeDelta)));
|
|
849
|
+
const inserts = [];
|
|
850
|
+
let currentNode = srcSeq.head.next.next as EventEndNode;
|
|
851
|
+
let lastPos: EventStartNode<VT> = node;
|
|
852
|
+
while (true) {
|
|
853
|
+
const startNode = currentNode.next;
|
|
854
|
+
const next = startNode.next;
|
|
855
|
+
// 最后一个节点对在循环外处理
|
|
856
|
+
if (next.type === NodeType.TAIL) {
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
const newEndNode = new EventEndNode(convertTime(currentNode.time), convert(currentNode.value));
|
|
860
|
+
const newStartNode = new EventStartNode(convertTime(startNode.time), convert(startNode.value));
|
|
861
|
+
const srcEvaluator = startNode.evaluator;
|
|
862
|
+
if (!(srcEvaluator instanceof EasedEvaluator)) {
|
|
863
|
+
throw new Error()
|
|
864
|
+
}
|
|
865
|
+
newStartNode.evaluator = (evaluator as EasedEvaluator<VT>).deriveWithEasing(srcEvaluator.easing)
|
|
866
|
+
EventNode.connect(newEndNode, newStartNode);
|
|
867
|
+
inserts.push(EventNodePairInsertOperation.lazy(newStartNode, lastPos));
|
|
868
|
+
lastPos = newStartNode;
|
|
869
|
+
|
|
870
|
+
currentNode = next;
|
|
871
|
+
}
|
|
872
|
+
const evaluatorChange = new EventNodeEvaluatorChangeOperation(node, evaluator.deriveWithEasing((srcSeq.head.next.evaluator as NumericEasedEvaluator).easing));
|
|
873
|
+
const valueChange = new EventNodeValueChangeOperation(node.next, convert(currentNode.value));
|
|
874
|
+
super(...inserts, evaluatorChange, valueChange);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
export class EncapsuleOperation extends ComplexOperation<[MultiNodeDeleteOperation, EventNodeEvaluatorChangeOperation<number>, EventNodeValueChangeOperation<number>]> {
|
|
880
|
+
updatesEditor = true;
|
|
881
|
+
constructor(nodes: EventStartNode<number>[], easing: TemplateEasing) {
|
|
882
|
+
const len = nodes.length;
|
|
883
|
+
super(
|
|
884
|
+
new MultiNodeDeleteOperation(nodes.slice(1, -1)),
|
|
885
|
+
new EventNodeEvaluatorChangeOperation(nodes[0], (nodes[0].evaluator as NumericEasedEvaluator).deriveWithEasing(easing)),
|
|
886
|
+
// 这里nodes至少都有两个,最后一个node不可能是第一个StartNode
|
|
887
|
+
new EventNodeValueChangeOperation(<EventEndNode>nodes[len - 1].previous, nodes[len - 1].value)
|
|
888
|
+
)
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* 将一些来自sourceSequence的节点打包为一个用于模板缓动的事件序列
|
|
892
|
+
* 然后把sourceSequence中的源节点集合替换为单个使用了该模板的事件
|
|
893
|
+
* @param sourceSequence
|
|
894
|
+
* @param sourceNodes
|
|
895
|
+
*/
|
|
896
|
+
static encapsule(templateEasingLib: TemplateEasingLib, sourceSequence: EventNodeSequence, sourceNodes: Set<EventStartNode>, name: string): EncapsuleOperation {
|
|
897
|
+
if (!EventNode.belongToSequence(sourceNodes, sourceSequence)) {
|
|
898
|
+
throw err.NODES_NOT_BELONG_TO_SAME_SEQUENCE();
|
|
899
|
+
}
|
|
900
|
+
const [oldArray, nodeArray] = EventNode.setToNewOrderedArray([0, 0, 1], sourceNodes);
|
|
901
|
+
if (Math.abs(nodeArray[0].value - nodeArray[nodeArray.length - 1].value) < 1e-10) {
|
|
902
|
+
throw err.NODES_HAS_ZERO_DELTA();
|
|
903
|
+
}
|
|
904
|
+
if (!EventNode.isContinuous(oldArray)) {
|
|
905
|
+
throw err.NODES_NOT_CONTINUOUS();
|
|
906
|
+
}
|
|
907
|
+
const easing = templateEasingLib.getOrNew(name);
|
|
908
|
+
const sequence = easing.eventNodeSequence;
|
|
909
|
+
sequence.effectiveBeats = TC.toBeats(nodeArray[nodeArray.length - 1].time);
|
|
910
|
+
// 直接do,这个不需要做成可撤销的
|
|
911
|
+
// @ts-expect-error 这里序列类型确定,为easing,不需要传入谱面
|
|
912
|
+
new MultiNodeAddOperation(nodeArray, sequence).do();
|
|
913
|
+
|
|
914
|
+
return new EncapsuleOperation(oldArray, easing);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
export class JudgeLineInheritanceChangeOperation extends Operation {
|
|
924
|
+
originalValue: JudgeLine | null;
|
|
925
|
+
updatesEditor = true;
|
|
926
|
+
static REFLOWS = JudgeLinesEditorLayoutType.tree;
|
|
927
|
+
reflows = JudgeLineInheritanceChangeOperation.REFLOWS;
|
|
928
|
+
constructor(public chart: Chart, public judgeLine: JudgeLine, public value: JudgeLine | null) {
|
|
929
|
+
super();
|
|
930
|
+
this.originalValue = judgeLine.father;
|
|
931
|
+
// 这里只会让它静默失败,外面调用的时候能够在判断一次并抛错误才是最好的
|
|
932
|
+
if (JudgeLine.checkinterdependency(judgeLine, value)) {
|
|
933
|
+
this.ineffective = true;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
do() {
|
|
937
|
+
const line = this.judgeLine;
|
|
938
|
+
line.father = this.value;
|
|
939
|
+
if (this.originalValue) {
|
|
940
|
+
this.originalValue.children.delete(line);
|
|
941
|
+
} else {
|
|
942
|
+
const index = this.chart.orphanLines.indexOf(line);
|
|
943
|
+
if (index >= 0) // Impossible to be false, theoretically
|
|
944
|
+
this.chart.orphanLines.splice(index, 1)
|
|
945
|
+
}
|
|
946
|
+
if (this.value) {
|
|
947
|
+
this.value.children.add(line);
|
|
948
|
+
} else {
|
|
949
|
+
this.chart.orphanLines.push(line);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
undo() {
|
|
953
|
+
const line = this.judgeLine;
|
|
954
|
+
line.father = this.originalValue;
|
|
955
|
+
if (this.originalValue) {
|
|
956
|
+
this.originalValue.children.add(line);
|
|
957
|
+
} else {
|
|
958
|
+
this.chart.orphanLines.push(line);
|
|
959
|
+
}
|
|
960
|
+
if (this.value) {
|
|
961
|
+
this.value.children.delete(line);
|
|
962
|
+
} else {
|
|
963
|
+
const index = this.chart.orphanLines.indexOf(line);
|
|
964
|
+
if (index >= 0) // Impossible to be false, theoretically
|
|
965
|
+
this.chart.orphanLines.splice(index, 1)
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export class JudgeLineRenameOperation extends Operation {
|
|
971
|
+
updatesEditor = true;
|
|
972
|
+
originalValue: string;
|
|
973
|
+
constructor(public judgeLine: JudgeLine, public value: string) {
|
|
974
|
+
super();
|
|
975
|
+
this.originalValue = judgeLine.name;
|
|
976
|
+
}
|
|
977
|
+
do() {
|
|
978
|
+
this.judgeLine.name = this.value;
|
|
979
|
+
}
|
|
980
|
+
undo() {
|
|
981
|
+
this.judgeLine.name = this.originalValue;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
type JudgeLinePropName = "name" | "rotatesWithFather" | "anchor" | "texture" | "cover" | "zOrder";
|
|
986
|
+
|
|
987
|
+
export class JudgeLinePropChangeOperation<T extends JudgeLinePropName> extends Operation {
|
|
988
|
+
updatesEditor = true;
|
|
989
|
+
originalValue: JudgeLine[T];
|
|
990
|
+
constructor(public judgeLine: JudgeLine, public field: T, public value: JudgeLine[T]) {
|
|
991
|
+
super();
|
|
992
|
+
this.originalValue = judgeLine[field];
|
|
993
|
+
}
|
|
994
|
+
do() {
|
|
995
|
+
this.judgeLine[this.field] = this.value;
|
|
996
|
+
}
|
|
997
|
+
undo() {
|
|
998
|
+
this.judgeLine[this.field] = this.originalValue;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export class JudgeLineRegroupOperation extends Operation {
|
|
1003
|
+
updatesEditor = true;
|
|
1004
|
+
reflows = JudgeLinesEditorLayoutType.grouped;
|
|
1005
|
+
originalValue: JudgeLineGroup;
|
|
1006
|
+
constructor(public judgeLine: JudgeLine, public value: JudgeLineGroup) {
|
|
1007
|
+
super();
|
|
1008
|
+
this.originalValue = judgeLine.group;
|
|
1009
|
+
}
|
|
1010
|
+
do() {
|
|
1011
|
+
this.judgeLine.group = this.value;
|
|
1012
|
+
this.value.add(this.judgeLine);
|
|
1013
|
+
this.originalValue.remove(this.judgeLine);
|
|
1014
|
+
}
|
|
1015
|
+
undo() {
|
|
1016
|
+
this.judgeLine.group = this.originalValue;
|
|
1017
|
+
this.originalValue.add(this.judgeLine);
|
|
1018
|
+
this.value.remove(this.judgeLine);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export class JudgeLineCreateOperation extends Operation {
|
|
1023
|
+
reflows = JudgeLinesEditorLayoutType.grouped | JudgeLinesEditorLayoutType.tree | JudgeLinesEditorLayoutType.ordered;
|
|
1024
|
+
// 之前把=写成了:半天不知道咋错了
|
|
1025
|
+
constructor(public chart: Chart, public judgeLine: JudgeLine) {
|
|
1026
|
+
super();
|
|
1027
|
+
}
|
|
1028
|
+
do() {
|
|
1029
|
+
const id = this.chart.judgeLines.length;
|
|
1030
|
+
this.judgeLine.id = id;
|
|
1031
|
+
this.chart.judgeLines.push(this.judgeLine);
|
|
1032
|
+
this.chart.orphanLines.push(this.judgeLine);
|
|
1033
|
+
this.chart.judgeLineGroups[0].add(this.judgeLine);
|
|
1034
|
+
}
|
|
1035
|
+
undo() {
|
|
1036
|
+
this.chart.judgeLineGroups[0].remove(this.judgeLine);
|
|
1037
|
+
this.chart.judgeLines.splice(this.chart.judgeLines.indexOf(this.judgeLine), 1);
|
|
1038
|
+
this.chart.orphanLines.splice(this.chart.orphanLines.indexOf(this.judgeLine), 1);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export class JudgeLineDeleteOperation extends Operation {
|
|
1043
|
+
readonly originalGroup: JudgeLineGroup;
|
|
1044
|
+
constructor(public chart: Chart, public judgeLine: JudgeLine) {
|
|
1045
|
+
super();
|
|
1046
|
+
if (!this.chart.judgeLines.includes(this.judgeLine)) {
|
|
1047
|
+
this.ineffective = true;
|
|
1048
|
+
}
|
|
1049
|
+
this.originalGroup = judgeLine.group;
|
|
1050
|
+
}
|
|
1051
|
+
do() {
|
|
1052
|
+
this.chart.judgeLines.splice(this.chart.judgeLines.indexOf(this.judgeLine), 1);
|
|
1053
|
+
if (this.chart.orphanLines.includes(this.judgeLine)) {
|
|
1054
|
+
this.chart.orphanLines.splice(this.chart.orphanLines.indexOf(this.judgeLine), 1);
|
|
1055
|
+
}
|
|
1056
|
+
this.originalGroup.remove(this.judgeLine);
|
|
1057
|
+
}
|
|
1058
|
+
undo() {
|
|
1059
|
+
this.chart.judgeLines.push(this.judgeLine);
|
|
1060
|
+
this.chart.orphanLines.push(this.judgeLine);
|
|
1061
|
+
this.originalGroup.add(this.judgeLine);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
export class JudgeLineENSChangeOperation extends Operation {
|
|
1068
|
+
originalValue: EventNodeSequence;
|
|
1069
|
+
constructor(public judgeLine: JudgeLine, public layerId: number, public typeStr: BasicEventName, public value: EventNodeSequence) {
|
|
1070
|
+
super();
|
|
1071
|
+
this.originalValue = judgeLine.eventLayers[layerId][typeStr];
|
|
1072
|
+
}
|
|
1073
|
+
do() {
|
|
1074
|
+
this.judgeLine.eventLayers[this.layerId][this.typeStr] = this.value;
|
|
1075
|
+
}
|
|
1076
|
+
undo() {
|
|
1077
|
+
this.judgeLine.eventLayers[this.layerId][this.typeStr] = this.originalValue;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
export type ENSOfTypeName<T extends ExtendedEventTypeName> = {
|
|
1083
|
+
"scaleX": EventNodeSequence<number>,
|
|
1084
|
+
"scaleY": EventNodeSequence<number>
|
|
1085
|
+
"text": EventNodeSequence<string>,
|
|
1086
|
+
"color": EventNodeSequence<RGB>
|
|
1087
|
+
}[T]
|
|
1088
|
+
export class JudgeLineExtendENSChangeOperation<T extends ExtendedEventTypeName> extends Operation {
|
|
1089
|
+
originalValue: ENSOfTypeName<T>;
|
|
1090
|
+
constructor(public judgeLine: JudgeLine, public typeStr: T, public value: ENSOfTypeName<T> | null) {
|
|
1091
|
+
super();
|
|
1092
|
+
this.originalValue = judgeLine.extendedLayer[typeStr satisfies keyof ExtendedLayer] as ENSOfTypeName<T>;
|
|
1093
|
+
}
|
|
1094
|
+
do() {
|
|
1095
|
+
this.judgeLine.extendedLayer[this.typeStr] = this.value
|
|
1096
|
+
}
|
|
1097
|
+
undo() {
|
|
1098
|
+
this.judgeLine.extendedLayer[this.typeStr] = this.originalValue
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export class EventNodeSequenceRenameOperation extends Operation {
|
|
1104
|
+
updatesEditor: boolean = true;
|
|
1105
|
+
originalName: string;
|
|
1106
|
+
constructor(public sequence: EventNodeSequence, public newName: string) {
|
|
1107
|
+
super();
|
|
1108
|
+
this.originalName = sequence.id;
|
|
1109
|
+
if (this.originalName === newName) {
|
|
1110
|
+
this.ineffective = true;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
do(chart: Chart) {
|
|
1114
|
+
chart.sequenceMap.set(this.newName, this.sequence)
|
|
1115
|
+
chart.sequenceMap.delete(this.originalName);
|
|
1116
|
+
this.sequence.id = this.newName;
|
|
1117
|
+
}
|
|
1118
|
+
undo(chart: Chart) {
|
|
1119
|
+
chart.sequenceMap.set(this.originalName, this.sequence)
|
|
1120
|
+
chart.sequenceMap.delete(this.newName);
|
|
1121
|
+
this.sequence.id = this.originalName;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
export class UIAttachOperation extends Operation {
|
|
1126
|
+
updatesEditor = true;
|
|
1127
|
+
constructor(public chart: Chart, public judgeLine: JudgeLine, public ui: UIName) {
|
|
1128
|
+
super();
|
|
1129
|
+
}
|
|
1130
|
+
do() {
|
|
1131
|
+
this.chart.attachUIToLine(this.ui, this.judgeLine);
|
|
1132
|
+
}
|
|
1133
|
+
undo() {
|
|
1134
|
+
this.chart.detachUI(this.ui);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
export class UIDetachOperation extends Operation {
|
|
1139
|
+
updatesEditor = true;
|
|
1140
|
+
judgeLine: JudgeLine;
|
|
1141
|
+
constructor(public chart: Chart, public ui: UIName) {
|
|
1142
|
+
super();
|
|
1143
|
+
if (chart[`${ui}Attach` satisfies keyof Chart]) {
|
|
1144
|
+
this.judgeLine = chart[`${ui}Attach` satisfies keyof Chart];
|
|
1145
|
+
} else {
|
|
1146
|
+
this.ineffective = true;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
do() {
|
|
1150
|
+
this.chart.detachUI(this.ui);
|
|
1151
|
+
}
|
|
1152
|
+
undo() {
|
|
1153
|
+
this.chart.attachUIToLine(this.ui, this.judgeLine);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
export class JudgeLineDetachAllUIOperation extends Operation {
|
|
1158
|
+
updatesEditor = true;
|
|
1159
|
+
uinames: UIName[];
|
|
1160
|
+
constructor(public chart: Chart, public judgeLine: JudgeLine) {
|
|
1161
|
+
super();
|
|
1162
|
+
this.uinames = chart.queryJudgeLineUI(this.judgeLine);
|
|
1163
|
+
}
|
|
1164
|
+
do() {
|
|
1165
|
+
for (const ui of this.uinames) {
|
|
1166
|
+
this.chart.detachUI(ui);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
undo() {
|
|
1170
|
+
for (const ui of this.uinames) {
|
|
1171
|
+
this.chart.attachUIToLine(ui, this.judgeLine);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
type ChartPropName = "name" | "level" | "composer" | "illustrator" | "charter" | "offset"
|
|
1177
|
+
|
|
1178
|
+
export class ChartPropChangeOperation<T extends ChartPropName> extends Operation {
|
|
1179
|
+
originalValue: Chart[T];
|
|
1180
|
+
constructor(public chart: Chart, public field: T, public value: Chart[T]) {
|
|
1181
|
+
super();
|
|
1182
|
+
this.originalValue = chart[field];
|
|
1183
|
+
if (field === "level" || field === "name") {
|
|
1184
|
+
this.updatesEditor = true;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
do() {
|
|
1188
|
+
this.chart[this.field] = this.value;
|
|
1189
|
+
}
|
|
1190
|
+
undo() {
|
|
1191
|
+
this.chart[this.field] = this.originalValue;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
type TimeRange = [TimeT, TimeT]
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* 所有节点事件加上一个值。
|
|
1201
|
+
* 此操作假定了节点被偏移时不会产生“碰撞”。
|
|
1202
|
+
* 节点要有序
|
|
1203
|
+
* @private
|
|
1204
|
+
*/
|
|
1205
|
+
export class MultiNodeOffsetOperation extends Operation {
|
|
1206
|
+
constructor(public nodes: readonly EventStartNode<any>[], public offset: TimeT) {
|
|
1207
|
+
super();
|
|
1208
|
+
}
|
|
1209
|
+
do() {
|
|
1210
|
+
const offset = this.offset;
|
|
1211
|
+
const nodes = this.nodes;
|
|
1212
|
+
const len = nodes.length;
|
|
1213
|
+
const node = nodes[0];
|
|
1214
|
+
if (node.previous.type !== NodeType.HEAD) {
|
|
1215
|
+
node.time = TC.validateIp(TC.add(node.time, offset));
|
|
1216
|
+
node.previous.time = TC.validateIp(TC.add(node.previous.time, offset));
|
|
1217
|
+
}
|
|
1218
|
+
for (let i = 1; i < len; i++) {
|
|
1219
|
+
const node = nodes[i];
|
|
1220
|
+
node.time = TC.validateIp(TC.add(node.time, offset));
|
|
1221
|
+
const previous = node.previous as EventEndNode<any>;
|
|
1222
|
+
previous.time = TC.validateIp(TC.add(previous.time, offset));
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
undo() {
|
|
1226
|
+
const offset = this.offset;
|
|
1227
|
+
const nodes = this.nodes;
|
|
1228
|
+
const len = nodes.length;
|
|
1229
|
+
const node = nodes[0];
|
|
1230
|
+
if (node.previous.type !== NodeType.HEAD) {
|
|
1231
|
+
node.time = TC.vadd(node.time, offset);
|
|
1232
|
+
node.previous.time = TC.vadd(node.previous.time, offset);
|
|
1233
|
+
}
|
|
1234
|
+
for (let i = 1; i < len; i++) {
|
|
1235
|
+
const node = nodes[i];
|
|
1236
|
+
node.time = TC.vadd(node.time, offset);
|
|
1237
|
+
const previous = node.previous as EventEndNode<any>;
|
|
1238
|
+
previous.time = TC.vadd(previous.time, offset);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
export class ENSTimeRangeDeleteOperation extends ComplexOperation<[MultiNodeDeleteOperation, MultiNodeOffsetOperation]> {
|
|
1245
|
+
beforeToStart: EventStartNode<any> | EventNodeLike<NodeType.HEAD, any>;
|
|
1246
|
+
constructor(public eventNodeSequence: EventNodeSequence<any>, public timeRange: TimeRange) {
|
|
1247
|
+
// 找出所有在范围内的节点并删除
|
|
1248
|
+
const node = eventNodeSequence.getNodeAt(TC.toBeats(timeRange[0]));
|
|
1249
|
+
const beforeToStart = EventNode.previousStartOfStart(node);
|
|
1250
|
+
const toBeDeleted = eventNodeSequence.getNodesFromOneAndRangeRight(node, timeRange[1]);
|
|
1251
|
+
// 将后续所有节点加入
|
|
1252
|
+
const toBeOffset = eventNodeSequence.getNodesAfterOne(toBeDeleted[toBeDeleted.length - 1]);
|
|
1253
|
+
super(new MultiNodeDeleteOperation(toBeDeleted), new MultiNodeOffsetOperation(toBeOffset, TC.vsub(timeRange[0], timeRange[1])))
|
|
1254
|
+
this.beforeToStart = beforeToStart;
|
|
1255
|
+
}
|
|
1256
|
+
do() {
|
|
1257
|
+
super.do();
|
|
1258
|
+
const ens = this.eventNodeSequence;
|
|
1259
|
+
ens.updateJump(this.beforeToStart, ens.tail);
|
|
1260
|
+
}
|
|
1261
|
+
undo() {
|
|
1262
|
+
super.undo();
|
|
1263
|
+
const ens = this.eventNodeSequence;
|
|
1264
|
+
ens.updateJump(this.beforeToStart, ens.tail);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
export class ENSAddBlankOperation extends MultiNodeOffsetOperation {
|
|
1269
|
+
updatesEditor = true;
|
|
1270
|
+
constructor(public ens: EventNodeSequence<any>, pos: TimeT, length: TimeT) {
|
|
1271
|
+
super(
|
|
1272
|
+
ens.getNodesAfterOne(ens.getNodeAt(TC.toBeats(pos))),
|
|
1273
|
+
length
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
do() {
|
|
1278
|
+
super.do();
|
|
1279
|
+
const ens = this.ens;
|
|
1280
|
+
ens.updateJump(ens.head, ens.tail);
|
|
1281
|
+
}
|
|
1282
|
+
undo() {
|
|
1283
|
+
super.undo();
|
|
1284
|
+
const ens = this.ens;
|
|
1285
|
+
ens.updateJump(ens.head, ens.tail);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// 按照规矩,音符节点的时间不可变,所以不会对音符节点动手。
|
|
1290
|
+
// 依旧不用组合的NoteTimeChangeOperation的方式,因为那需要多次更新跳数组。
|
|
1291
|
+
/**
|
|
1292
|
+
* @author Zes Minkey Young
|
|
1293
|
+
*/
|
|
1294
|
+
// @ts-expect-error 我需要明确禁用掉lazy静态方法,这不会破坏类型安全。
|
|
1295
|
+
export class MultiNoteOffsetOperation extends Operation {
|
|
1296
|
+
constructor(public nnList: NNList, public notes: readonly Note[], public offset: TimeT) {
|
|
1297
|
+
super();
|
|
1298
|
+
}
|
|
1299
|
+
do() {
|
|
1300
|
+
const offset = this.offset;
|
|
1301
|
+
const notes = this.notes;
|
|
1302
|
+
const len = notes.length;
|
|
1303
|
+
const nnList = this.nnList;
|
|
1304
|
+
for (let i = 0; i < len; i++) {
|
|
1305
|
+
const note = notes[i];
|
|
1306
|
+
const startTime = TC.vadd(note.startTime, offset);
|
|
1307
|
+
note.startTime = startTime;
|
|
1308
|
+
note.endTime = TC.vadd(note.endTime, offset);
|
|
1309
|
+
note.parentNode.remove(note);
|
|
1310
|
+
nnList.getNodeOf(startTime).add(note);
|
|
1311
|
+
}
|
|
1312
|
+
nnList.jump.updateRange(nnList.head, nnList.tail);
|
|
1313
|
+
if (nnList instanceof HNList) {
|
|
1314
|
+
nnList.holdTailJump.updateRange(nnList.head, nnList.tail);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
undo() {
|
|
1318
|
+
const offset = this.offset;
|
|
1319
|
+
const notes = this.notes;
|
|
1320
|
+
const len = notes.length;
|
|
1321
|
+
const nnList = this.nnList;
|
|
1322
|
+
for (let i = 0; i < len; i++) {
|
|
1323
|
+
const note = notes[i];
|
|
1324
|
+
const startTime = TC.vsub(note.startTime, offset);
|
|
1325
|
+
note.startTime = startTime;
|
|
1326
|
+
note.endTime = TC.vsub(note.endTime, offset);
|
|
1327
|
+
note.parentNode.remove(note);
|
|
1328
|
+
nnList.getNodeOf(startTime).add(note);
|
|
1329
|
+
}
|
|
1330
|
+
nnList.jump.updateRange(nnList.head, nnList.tail);
|
|
1331
|
+
if (nnList instanceof HNList) {
|
|
1332
|
+
nnList.holdTailJump.updateRange(nnList.head, nnList.tail);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
// 禁用,因为无效
|
|
1336
|
+
private static lazy() {}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
export class NNListTimeRangeDeleteOperation extends ComplexOperation<[MultiNoteDeleteOperation, MultiNoteOffsetOperation]> {
|
|
1341
|
+
constructor(public nnList: NNList, public timeRange: TimeRange, public updatesJump: boolean = true) {
|
|
1342
|
+
const delNodes = nnList.getNodesFromOneAndRangeRight(nnList.getNodeOf(timeRange[0]), timeRange[1]);
|
|
1343
|
+
const delNotes = [];
|
|
1344
|
+
const dlen = delNodes.length;
|
|
1345
|
+
for (let i = 0; i < dlen; i++) {
|
|
1346
|
+
delNotes.push(...delNodes[i].notes);
|
|
1347
|
+
}
|
|
1348
|
+
const offsetNodes = nnList.getNodesAfterOne(delNodes[dlen - 1]);
|
|
1349
|
+
const offsetNotes = [];
|
|
1350
|
+
const olen = offsetNodes.length;
|
|
1351
|
+
for (let i = 0; i < olen; i++) {
|
|
1352
|
+
offsetNotes.push(...offsetNodes[i].notes);
|
|
1353
|
+
}
|
|
1354
|
+
super(new MultiNoteDeleteOperation(delNotes), new MultiNoteOffsetOperation(nnList, offsetNotes, TC.vsub(timeRange[0], timeRange[1])));
|
|
1355
|
+
}
|
|
1356
|
+
do() {
|
|
1357
|
+
super.do();
|
|
1358
|
+
this.nnList.clearEmptyNodes(this.updatesJump)
|
|
1359
|
+
}
|
|
1360
|
+
undo() {
|
|
1361
|
+
super.undo();
|
|
1362
|
+
this.nnList.clearEmptyNodes(this.updatesJump)
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
export class NNListAddBlankOperation extends MultiNoteOffsetOperation {
|
|
1367
|
+
updatesEditor = true;
|
|
1368
|
+
constructor(nnList: NNList, pos: TimeT, length: TimeT) {
|
|
1369
|
+
const nns = nnList.getNodesAfterOne(nnList.getNodeOf(pos));
|
|
1370
|
+
const notes = [];
|
|
1371
|
+
const len = nns.length;
|
|
1372
|
+
for (let i = 0; i < len; i++) {
|
|
1373
|
+
notes.push(...nns[i].notes);
|
|
1374
|
+
}
|
|
1375
|
+
super(nnList, notes, length);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|