source: trunk/CmdList.pas

Last change on this file was 457, checked in by chronos, 5 months ago
  • Modified: Use enumeration type in Help unit for better readability.
  • Fixed: Corruption external help image referenced with incorrect file name case.
  • Fixed: Chinese translation converted to UTF-8.
  • Modified: Code cleanup.
File size: 11.0 KB
Line 
1{$INCLUDE Switches.inc}
2unit CmdList;
3
4interface
5
6uses
7 Classes, SysUtils, Math;
8
9const
10 MaxDataSize = 1024;
11 CommandDataElementSize = 4;
12 CommandDataElementCountMask = $F;
13 CommandDataMaxSize = CommandDataElementSize * CommandDataElementCountMask;
14
15type
16 TLogData = array [0 .. 999999999] of Byte;
17
18 TCmdListState = record
19 nLog: Integer; { used size of LogData in bytes }
20 LoadPos: Integer; { position in LogData when loading a game }
21 LastMovingUnit: Integer;
22 MoveCode: Cardinal;
23 LoadMoveCode: Cardinal;
24 end;
25
26 TCmdList = class
27 constructor Create;
28 destructor Destroy; override;
29 procedure Get(var Command, Player, Subject: Integer; var Data: Pointer);
30 procedure GetDataChanges(Data: Pointer; DataSize: Integer);
31 procedure Put(Command, Player, Subject: Integer; Data: Pointer);
32 procedure PutDataChanges(Command, Player: Integer;
33 OldData, NewData: Pointer; DataSize: Integer);
34 procedure LoadFromFile(const F: TFileStream);
35 procedure SaveToFile(const F: TFileStream);
36 procedure AppendToFile(const F: TFileStream; const OldState: TCmdListState);
37 procedure Cut;
38 function Progress: Integer;
39 private
40 LogAlloc: Integer; { allocated size of LogData in bytes }
41 LogData: ^TLogData;
42 FState: TCmdListState;
43 procedure PutData(Data: Pointer; Length: Integer);
44 procedure CompleteMoveCode;
45 public
46 property State: TCmdListState read FState write FState;
47 end;
48
49 function CommandWithData(Command: Integer; DataSize: Byte): Integer;
50
51resourcestring
52 SCommandDataSizeError = 'Command data size %d out of range (0-%d).';
53
54
55implementation
56
57uses
58 Protocol;
59
60const
61 LogGrow = 1 shl 18;
62
63type
64 TData = array [0..MaxDataSize - 1] of Cardinal;
65 PData = ^TData;
66
67function CommandWithData(Command: Integer; DataSize: Byte): Integer;
68var
69 DataElementCount: Byte;
70begin
71 if DataSize > CommandDataMaxSize then
72 raise Exception.Create(Format(SCommandDataSizeError, [DataSize, CommandDataMaxSize]));
73 DataElementCount := Ceil(DataSize / CommandDataElementSize);
74 Result := Command or (DataElementCount and CommandDataElementCountMask);
75end;
76
77constructor TCmdList.Create;
78begin
79 inherited;
80 FState.nLog := 0;
81 LogAlloc := 0;
82 LogData := nil;
83 FState.LastMovingUnit := -1;
84 FState.MoveCode := 0;
85 FState.LoadMoveCode := 0;
86end;
87
88destructor TCmdList.Destroy;
89begin
90 ReallocMem(LogData, 0);
91 inherited;
92end;
93
94procedure TCmdList.Get(var Command, Player, Subject: Integer; var Data: Pointer);
95var
96 DirCode: Cardinal;
97 Code: Cardinal;
98begin
99 if FState.LoadMoveCode > 0 then
100 begin
101 Player := -1;
102 if FState.LoadMoveCode and 1 = 1 then
103 begin // FM
104 DirCode := FState.LoadMoveCode shr 1 and 7;
105 Subject := FState.LastMovingUnit;
106 FState.LoadMoveCode := FState.LoadMoveCode shr 4;
107 end
108 else
109 begin // M
110 DirCode := FState.LoadMoveCode shr 3 and 7;
111 Subject := FState.LoadMoveCode shr 6 and $FFF;
112 FState.LoadMoveCode := FState.LoadMoveCode shr 18;
113 FState.LastMovingUnit := Subject
114 end;
115 case DirCode of
116 0: Command := sMoveUnit + $090;
117 1: Command := sMoveUnit + $0F0;
118 2: Command := sMoveUnit + $390;
119 3: Command := sMoveUnit + $3F0;
120 4: Command := sMoveUnit + $020;
121 5: Command := sMoveUnit + $060;
122 6: Command := sMoveUnit + $100;
123 7: Command := sMoveUnit + $300;
124 end;
125 Data := nil;
126 end
127 else
128 begin
129 Code := Cardinal((@LogData[FState.LoadPos])^);
130 if Code and 3 = 0 then
131 begin // non-clientex command
132 Command := Code shr 2 and $3FFF + sExecute;
133 Player := Code shr 16 and $F;
134 Subject := Code shr 20 and $FFF;
135 Inc(FState.LoadPos, 4);
136 end
137 else if Code and 7 = 2 then
138 begin // clientex command
139 Command := Code shr 3 and $FFFF;
140 Player := Code shr 19 and $F;
141 Subject := 0;
142 Inc(FState.LoadPos, 3);
143 end
144 else
145 begin // move command shortcut
146 if (Code and 1 = 1) and (Code and (7 shl 4) <> 6 shl 4) then
147 begin
148 FState.LoadMoveCode := Code and $FF;
149 Inc(FState.LoadPos);
150 end
151 else
152 begin
153 FState.LoadMoveCode := Code and $FFFFFF;
154 Inc(FState.LoadPos, 3);
155 end;
156 Get(Command, Player, Subject, Data);
157 Exit;
158 end;
159
160 if Command and CommandDataElementCountMask = 0 then
161 Data := nil
162 else
163 begin
164 Data := @LogData[FState.LoadPos];
165 Inc(FState.LoadPos, Command and CommandDataElementCountMask * CommandDataElementSize);
166 end;
167 end;
168end;
169
170procedure TCmdList.GetDataChanges(Data: Pointer; DataSize: Integer);
171var
172 b0, b1: Integer;
173 Map0, Map1: Cardinal;
174begin
175 Map0 := Cardinal((@LogData[FState.LoadPos])^);
176 Inc(FState.LoadPos, 4);
177 b0 := 0;
178 while Map0 > 0 do begin
179 if Map0 and 1 <> 0 then begin
180 Map1 := Cardinal((@LogData[FState.LoadPos])^);
181 Inc(FState.LoadPos, 4);
182 for b1 := 0 to 31 do
183 if 1 shl b1 and Map1 <> 0 then begin
184 if b0 * 32 + b1 < DataSize then
185 PData(Data)[b0 * 32 + b1] := Cardinal((@LogData[FState.LoadPos])^);
186 Inc(FState.LoadPos, 4);
187 end;
188 end;
189 Inc(b0);
190 Map0 := Map0 shr 1;
191 end;
192end;
193
194procedure TCmdList.Put(Command, Player, Subject: Integer; Data: Pointer);
195var
196 DirCode, Code: Cardinal;
197begin
198 if Command and $FC00 = sMoveUnit then
199 begin // move command shortcut
200 case Command of
201 sMoveUnit + $090: DirCode := 0;
202 sMoveUnit + $0F0: DirCode := 1;
203 sMoveUnit + $390: DirCode := 2;
204 sMoveUnit + $3F0: DirCode := 3;
205 sMoveUnit + $020: DirCode := 4;
206 sMoveUnit + $060: DirCode := 5;
207 sMoveUnit + $100: DirCode := 6;
208 sMoveUnit + $300: DirCode := 7;
209 end;
210 if Subject = FState.LastMovingUnit then
211 Code := 1 + DirCode shl 1
212 else
213 Code := 6 + DirCode shl 3 + Cardinal(Subject) shl 6;
214 if FState.MoveCode = 0 then
215 FState.MoveCode := Code
216 else if FState.MoveCode and 1 = 1 then
217 begin // FM + this
218 FState.MoveCode := FState.MoveCode + Code shl 4;
219 if Code and 1 = 1 then
220 PutData(@FState.MoveCode, 1) // FM + FM
221 else
222 PutData(@FState.MoveCode, 3); // FM + M
223 FState.MoveCode := 0;
224 end
225 else if Code and 1 = 1 then
226 begin // M + FM
227 FState.MoveCode := FState.MoveCode + Code shl 18;
228 PutData(@FState.MoveCode, 3);
229 FState.MoveCode := 0;
230 end
231 else // M + M
232 begin
233 PutData(@FState.MoveCode, 3);
234 FState.MoveCode := Code;
235 end;
236 FState.LastMovingUnit := Subject;
237 end
238 else
239 begin
240 CompleteMoveCode;
241 if Command >= cClientEx then
242 begin
243 Code := 2 + Command shl 3 + Player shl 19;
244 PutData(@Code, 3);
245 end
246 else
247 begin
248 Code := Cardinal(Command - sExecute) shl 2 + Cardinal(Player) shl 16 +
249 Cardinal(Subject) shl 20;
250 PutData(@Code, 4);
251 end;
252 end;
253 if Command and CommandDataElementCountMask <> 0 then
254 PutData(Data, Command and CommandDataElementCountMask * CommandDataElementSize);
255end;
256
257procedure TCmdList.PutDataChanges(Command, Player: Integer;
258 OldData, NewData: Pointer; DataSize: Integer);
259var
260 MapPos, LogPos, b0, b1, RowEnd: Integer;
261 Map0, Map1, Code: Cardinal;
262begin
263 if DataSize <= 0 then
264 Exit;
265 if DataSize > MaxDataSize then
266 DataSize := MaxDataSize;
267 CompleteMoveCode;
268 MapPos := FState.nLog + 8;
269 LogPos := MapPos + 4;
270 Map0 := 0;
271 for b0 := 0 to (DataSize - 1) div 32 do
272 begin
273 if LogPos + 4 * 32 > LogAlloc then
274 begin
275 Inc(LogAlloc, LogGrow);
276 ReallocMem(LogData, LogAlloc);
277 end;
278 Map0 := Map0 shr 1;
279 Map1 := 0;
280 RowEnd := DataSize - 1;
281 if RowEnd > b0 * 32 + 31 then
282 RowEnd := b0 * 32 + 31;
283 for b1 := b0 * 32 to RowEnd do
284 begin
285 Map1 := Map1 shr 1;
286 if PData(NewData)[b1] <> PData(OldData)[b1] then
287 begin
288 Cardinal((@LogData[LogPos])^) := PData(NewData)[b1];
289 Inc(LogPos, 4);
290 Inc(Map1, $80000000);
291 end;
292 end;
293 if Map1 > 0 then
294 begin
295 Map1 := Map1 shr (b0 * 32 + 31 - RowEnd);
296 Cardinal((@LogData[MapPos])^) := Map1;
297 MapPos := LogPos;
298 Inc(LogPos, 4);
299 Inc(Map0, $80000000);
300 end;
301 end;
302 if Map0 = 0 then
303 Exit; // no changes
304
305 Map0 := Map0 shr (31 - (DataSize - 1) div 32);
306 Cardinal((@LogData[FState.nLog + 4])^) := Map0;
307 Code := Cardinal(Command - sExecute) shl 2 + Cardinal(Player) shl 16;
308 Cardinal((@LogData[FState.nLog])^) := Code;
309 FState.nLog := MapPos;
310end;
311
312procedure TCmdList.PutData(Data: Pointer; Length: Integer);
313begin
314 if FState.nLog + Length > LogAlloc then
315 begin
316 Inc(LogAlloc, LogGrow);
317 ReallocMem(LogData, LogAlloc);
318 end;
319 Move(Data^, LogData[FState.nLog], Length);
320 Inc(FState.nLog, Length);
321end;
322
323procedure TCmdList.CompleteMoveCode;
324begin
325 if FState.MoveCode > 0 then
326 begin
327 if FState.MoveCode and 1 = 1 then
328 PutData(@FState.MoveCode, 1) // Single FM
329 else
330 PutData(@FState.MoveCode, 3); // Single M
331 FState.MoveCode := 0;
332 end;
333end;
334
335procedure TCmdList.LoadFromFile(const F: TFileStream);
336begin
337 F.Read(FState.nLog, 4);
338 LogData := nil;
339 LogAlloc := ((FState.nLog + 2) div LogGrow + 1) * LogGrow;
340 ReallocMem(LogData, LogAlloc);
341 F.Read(LogData^, FState.nLog);
342 FState.LoadPos := 0;
343end;
344
345procedure TCmdList.SaveToFile(const F: TFileStream);
346begin
347 CompleteMoveCode;
348 F.Write(FState.nLog, 4);
349 F.Write(LogData^, FState.nLog);
350end;
351
352procedure TCmdList.AppendToFile(const F: TFileStream;
353 const OldState: TCmdListState);
354begin
355 CompleteMoveCode;
356 F.Write(FState.nLog, 4);
357 F.Position := F.Position + OldState.nLog;
358 F.Write(LogData[OldState.nLog], FState.nLog - OldState.nLog);
359end;
360
361procedure TCmdList.Cut;
362begin
363 FState.nLog := FState.LoadPos;
364end;
365
366function TCmdList.Progress: Integer;
367begin
368 if (FState.LoadPos = FState.nLog) and (FState.LoadMoveCode = 0) then
369 Result := 1000 // loading complete
370 else if FState.nLog > 1 shl 20 then
371 Result := (FState.LoadPos shr 8) * 999 div (FState.nLog shr 8)
372 else
373 Result := FState.LoadPos * 999 div FState.nLog;
374end;
375
376{ Format Specification:
377
378 Non-ClientEx-Command:
379 Byte3 Byte2 Byte1 Byte0
380 ssssssss sssspppp cccccccc cccccc00
381 (C = Command-sExecute, P = Player, S = Subject)
382
383 ClientEx-Command:
384 Byte2 Byte1 Byte0
385 0ppppccc cccccccc ccccc010
386 (C = Command, P = Player)
387
388 Single Move:
389 Byte2 Byte1 Byte0
390 000000ss ssssssss ssaaa110
391 (A = Direction, S = Subject)
392
393 Move + Follow Move:
394 Byte2 Byte1 Byte0
395 00bbb1ss ssssssss ssaaa110
396 (A = Direction 1, S = Subject 1, B = Direction 2)
397
398 Follow Move + Move:
399 Byte2 Byte1 Byte0
400 00ssssss ssssssbb b110aaa1
401 (A = Direction 1, B = Direction 2, S = Subject 2)
402
403 Single Follow Move:
404 Byte0
405 0000aaa1
406 (A = Direction)
407
408 Double Follow Move:
409 Byte0
410 bbb1aaa1
411 (A = Direction 1, B = Direction 2)
412}
413
414end.
Note: See TracBrowser for help on using the repository browser.