File: CmdList.pas

package info (click to toggle)
c-evo-dh 3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,540 kB
  • sloc: pascal: 57,645; xml: 243; makefile: 114; sh: 4
file content (417 lines) | stat: -rw-r--r-- 10,866 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
{$INCLUDE Switches.inc}
unit CmdList;

interface

uses
  Classes, SysUtils, Math;

const
  MaxDataSize = 1024;
  CommandDataElementSize = 4;
  CommandDataElementCountMask = $f;
  CommandDataMaxSize = CommandDataElementSize * CommandDataElementCountMask;

type
  TLogData = array [0 .. 999999999] of Byte;

  TCmdListState = record
    nLog: Integer; { used size of LogData in bytes }
    LoadPos: Integer; { position in LogData when loading a game }
    LastMovingUnit: Integer;
    MoveCode: Cardinal;
    LoadMoveCode: Cardinal;
  end;

  TCmdList = class
    constructor Create;
    destructor Destroy; override;
    procedure Get(var Command, Player, Subject: integer; var Data: pointer);
    procedure GetDataChanges(Data: pointer; DataSize: integer);
    procedure Put(Command, Player, Subject: integer; Data: pointer);
    procedure PutDataChanges(Command, Player: integer;
      OldData, NewData: pointer; DataSize: integer);
    procedure LoadFromFile(const f: TFileStream);
    procedure SaveToFile(const f: TFileStream);
    procedure AppendToFile(const f: TFileStream; const OldState: TCmdListState);
    procedure Cut;
    function Progress: integer;
  private
    LogAlloc: integer; { allocated size of LogData in bytes }
    LogData: ^TLogData;
    FState: TCmdListState;
    procedure PutData(Data: pointer; Length: integer);
    procedure CompleteMoveCode;
  public
    property State: TCmdListState read FState write FState;
  end;

  function CommandWithData(Command: Integer; DataSize: Byte): Integer;

resourcestring
  SCommandDataSizeError = 'Command data size %d out of range (0-%d).';


implementation

uses
  Protocol;

const
  LogGrow = 1 shl 18;

type
  TData = array [0 .. MaxDataSize - 1] of Cardinal;
  PData = ^TData;

function CommandWithData(Command: Integer; DataSize: Byte): Integer;
var
  DataElementCount: Byte;
begin
  if DataSize > CommandDataMaxSize then
    raise Exception.Create(Format(SCommandDataSizeError, [DataSize, CommandDataMaxSize]));
  DataElementCount := Ceil(DataSize / CommandDataElementSize);
  Result := Command or (DataElementCount and CommandDataElementCountMask);
end;

constructor TCmdList.Create;
begin
  inherited;
  FState.nLog := 0;
  LogAlloc := 0;
  LogData := nil;
  FState.LastMovingUnit := -1;
  FState.MoveCode := 0;
  FState.LoadMoveCode := 0;
end;

destructor TCmdList.Destroy;
begin
  ReallocMem(LogData, 0);
  inherited;
end;

procedure TCmdList.Get(var Command, Player, Subject: integer; var Data: pointer);
var
  DirCode: Cardinal;
  Code: Cardinal;
begin
  if FState.LoadMoveCode > 0 then
  begin
    Player := -1;
    if FState.LoadMoveCode and 1 = 1 then
    begin // FM
      DirCode := FState.LoadMoveCode shr 1 and 7;
      Subject := FState.LastMovingUnit;
      FState.LoadMoveCode := FState.LoadMoveCode shr 4;
    end
    else
    begin // M
      DirCode := FState.LoadMoveCode shr 3 and 7;
      Subject := FState.LoadMoveCode shr 6 and $FFF;
      FState.LoadMoveCode := FState.LoadMoveCode shr 18;
      FState.LastMovingUnit := Subject
    end;
    case DirCode of
      0: Command := sMoveUnit + $090;
      1: Command := sMoveUnit + $0F0;
      2: Command := sMoveUnit + $390;
      3: Command := sMoveUnit + $3F0;
      4: Command := sMoveUnit + $020;
      5: Command := sMoveUnit + $060;
      6: Command := sMoveUnit + $100;
      7: Command := sMoveUnit + $300;
    end;
    Data := nil;
  end
  else
  begin
    code := Cardinal((@LogData[FState.LoadPos])^);
    if code and 3 = 0 then
    begin // non-clientex command
      Command := code shr 2 and $3FFF + sExecute;
      Player := code shr 16 and $F;
      Subject := code shr 20 and $FFF;
      inc(FState.LoadPos, 4);
    end
    else if code and 7 = 2 then
    begin // clientex command
      Command := code shr 3 and $FFFF;
      Player := code shr 19 and $F;
      Subject := 0;
      inc(FState.LoadPos, 3);
    end
    else
    begin // move command shortcut
      if (code and 1 = 1) and (code and (7 shl 4) <> 6 shl 4) then
      begin
        FState.LoadMoveCode := code and $FF;
        inc(FState.LoadPos);
      end
      else
      begin
        FState.LoadMoveCode := code and $FFFFFF;
        inc(FState.LoadPos, 3);
      end;
      Get(Command, Player, Subject, Data);
      Exit;
    end;

    if Command and CommandDataElementCountMask = 0 then
      Data := nil
    else
    begin
      Data := @LogData[FState.LoadPos];
      inc(FState.LoadPos, Command and CommandDataElementCountMask * CommandDataElementSize);
    end;
  end;
end;

procedure TCmdList.GetDataChanges(Data: pointer; DataSize: integer);
var
  b0, b1: integer;
  Map0, Map1: Cardinal;
begin
  Map0 := Cardinal((@LogData[FState.LoadPos])^);
  inc(FState.LoadPos, 4);
  b0 := 0;
  while Map0 > 0 do begin
    if Map0 and 1 <> 0 then begin
      Map1 := Cardinal((@LogData[FState.LoadPos])^);
      inc(FState.LoadPos, 4);
      for b1 := 0 to 31 do
        if 1 shl b1 and Map1 <> 0 then begin
          if b0 * 32 + b1 < DataSize then
            PData(Data)[b0 * 32 + b1] := Cardinal((@LogData[FState.LoadPos])^);
          inc(FState.LoadPos, 4);
        end;
    end;
    inc(b0);
    Map0 := Map0 shr 1;
  end;
end;

procedure TCmdList.Put(Command, Player, Subject: integer; Data: pointer);
var
  DirCode, code: Cardinal;
begin
  if Command and $FC00 = sMoveUnit then
  begin // move command shortcut
    case Command of
      sMoveUnit + $090: DirCode := 0;
      sMoveUnit + $0F0: DirCode := 1;
      sMoveUnit + $390: DirCode := 2;
      sMoveUnit + $3F0: DirCode := 3;
      sMoveUnit + $020: DirCode := 4;
      sMoveUnit + $060: DirCode := 5;
      sMoveUnit + $100: DirCode := 6;
      sMoveUnit + $300: DirCode := 7;

      otherwise DirCode := 8; //arbitrary fallback
    end;

    if Subject = FState.LastMovingUnit then
      code := 1 + DirCode shl 1
    else
      code := 6 + DirCode shl 3 + Cardinal(Subject) shl 6;
    if FState.MoveCode = 0 then
      FState.MoveCode := code
    else if FState.MoveCode and 1 = 1 then
    begin // FM + this
      FState.MoveCode := FState.MoveCode + code shl 4;
      if code and 1 = 1 then
        PutData(@FState.MoveCode, 1) // FM + FM
      else
        PutData(@FState.MoveCode, 3); // FM + M
      FState.MoveCode := 0;
    end
    else if code and 1 = 1 then
    begin // M + FM
      FState.MoveCode := FState.MoveCode + code shl 18;
      PutData(@FState.MoveCode, 3);
      FState.MoveCode := 0;
    end
    else // M + M
    begin
      PutData(@FState.MoveCode, 3);
      FState.MoveCode := code;
    end;
    FState.LastMovingUnit := Subject;
  end
  else
  begin
    CompleteMoveCode;
    if Command >= cClientEx then
    begin
      code := 2 + Command shl 3 + Player shl 19;
      PutData(@code, 3);
    end
    else
    begin
      code := Cardinal(Command - sExecute) shl 2 + Cardinal(Player) shl 16 +
        Cardinal(Subject) shl 20;
      PutData(@code, 4);
    end;
  end;
  if Command and CommandDataElementCountMask <> 0 then
    PutData(Data, Command and CommandDataElementCountMask * CommandDataElementSize);
end;

procedure TCmdList.PutDataChanges(Command, Player: integer;
  OldData, NewData: pointer; DataSize: integer);
var
  MapPos, LogPos, b0, b1, RowEnd: integer;
  Map0, Map1, code: Cardinal;
begin
  if DataSize <= 0 then
    exit;
  if DataSize > MaxDataSize then
    DataSize := MaxDataSize;
  CompleteMoveCode;
  MapPos := FState.nLog + 8;
  LogPos := MapPos + 4;
  Map0 := 0;
  for b0 := 0 to (DataSize - 1) div 32 do
  begin
    if LogPos + 4 * 32 > LogAlloc then
    begin
      inc(LogAlloc, LogGrow);
      ReallocMem(LogData, LogAlloc);
    end;
    Map0 := Map0 shr 1;
    Map1 := 0;
    RowEnd := DataSize - 1;
    if RowEnd > b0 * 32 + 31 then
      RowEnd := b0 * 32 + 31;
    for b1 := b0 * 32 to RowEnd do
    begin
      Map1 := Map1 shr 1;
      if PData(NewData)[b1] <> PData(OldData)[b1] then
      begin
        Cardinal((@LogData[LogPos])^) := PData(NewData)[b1];
        inc(LogPos, 4);
        inc(Map1, $80000000);
      end;
    end;
    if Map1 > 0 then
    begin
      Map1 := Map1 shr (b0 * 32 + 31 - RowEnd);
      Cardinal((@LogData[MapPos])^) := Map1;
      MapPos := LogPos;
      inc(LogPos, 4);
      inc(Map0, $80000000);
    end;
  end;
  if Map0 = 0 then
    exit; // no changes

  Map0 := Map0 shr (31 - (DataSize - 1) div 32);
  Cardinal((@LogData[FState.nLog + 4])^) := Map0;
  code := Cardinal(Command - sExecute) shl 2 + Cardinal(Player) shl 16;
  Cardinal((@LogData[FState.nLog])^) := code;
  FState.nLog := MapPos;
end;

procedure TCmdList.PutData(Data: pointer; Length: integer);
begin
  if FState.nLog + Length > LogAlloc then
  begin
    inc(LogAlloc, LogGrow);
    ReallocMem(LogData, LogAlloc);
  end;
  move(Data^, LogData[FState.nLog], Length);
  inc(FState.nLog, Length);
end;

procedure TCmdList.CompleteMoveCode;
begin
  if FState.MoveCode > 0 then
  begin
    if FState.MoveCode and 1 = 1 then
      PutData(@FState.MoveCode, 1) // Single FM
    else
      PutData(@FState.MoveCode, 3); // Single M
    FState.MoveCode := 0;
  end;
end;

procedure TCmdList.LoadFromFile(const f: TFileStream);
begin
  f.read(FState.nLog, 4);
  LogData := nil;
  LogAlloc := ((FState.nLog + 2) div LogGrow + 1) * LogGrow;
  ReallocMem(LogData, LogAlloc);
  f.read(LogData^, FState.nLog);
  FState.LoadPos := 0;
end;

procedure TCmdList.SaveToFile(const f: TFileStream);
begin
  CompleteMoveCode;
  f.write(FState.nLog, 4);
  f.write(LogData^, FState.nLog);
end;

procedure TCmdList.AppendToFile(const f: TFileStream;
  const OldState: TCmdListState);
begin
  CompleteMoveCode;
  f.write(FState.nLog, 4);
  f.Position := f.Position + OldState.nLog;
  f.write(LogData[OldState.nLog], FState.nLog - OldState.nLog);
end;

procedure TCmdList.Cut;
begin
  FState.nLog := FState.LoadPos;
end;

function TCmdList.Progress: integer;
begin
  if (FState.LoadPos = FState.nLog) and (FState.LoadMoveCode = 0) then
    result := 1000 // loading complete
  else if FState.nLog > 1 shl 20 then
    result := (FState.LoadPos shr 8) * 999 div (FState.nLog shr 8)
  else
    result := FState.LoadPos * 999 div FState.nLog;
end;

{ Format Specification:

  Non-ClientEx-Command:
  Byte3    Byte2    Byte1    Byte0
  ssssssss sssspppp cccccccc cccccc00
  (c = Command-sExecute, p = Player, s = Subject)

  ClientEx-Command:
  Byte2    Byte1    Byte0
  0ppppccc cccccccc ccccc010
  (c = Command, p = Player)

  Single Move:
  Byte2    Byte1    Byte0
  000000ss ssssssss ssaaa110
  (a = Direction, s = Subject)

  Move + Follow Move:
  Byte2    Byte1    Byte0
  00bbb1ss ssssssss ssaaa110
  (a = Direction 1, s = Subject 1, b = Direction 2)

  Follow Move + Move:
  Byte2    Byte1    Byte0
  00ssssss ssssssbb b110aaa1
  (a = Direction 1, b = Direction 2, s = Subject 2)

  Single Follow Move:
  Byte0
  0000aaa1
  (a = Direction)

  Double Follow Move:
  Byte0
  bbb1aaa1
  (a = Direction 1, b = Direction 2)
}

end.