DEADSOFTWARE

center player when the game is scaled (lighting is not working correctly yet, tho)
[d2df-sdl.git] / src / game / g_panel.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 {$M+}
18 unit g_panel;
20 interface
22 uses
23 MAPDEF, BinEditor, g_textures, xdynrec;
25 type
26 TAddTextureArray = Array of
27 record
28 Texture: Cardinal;
29 Anim: Boolean;
30 end;
32 TPanel = Class (TObject)
33 private
34 const
35 private
36 mGUID: Integer; // will be assigned in "g_map.pas"
37 FTextureWidth: Word;
38 FTextureHeight: Word;
39 FAlpha: Byte;
40 FBlending: Boolean;
41 FTextureIDs: Array of
42 record
43 case Anim: Boolean of
44 False: (Tex: Cardinal);
45 True: (AnTex: TAnimation);
46 end;
48 mMovingSpeed: TDFPoint;
49 mMovingStart: TDFPoint;
50 mMovingEnd: TDFPoint;
51 mMovingActive: Boolean;
52 mMoveOnce: Boolean;
54 mOldMovingActive: Boolean;
56 mSizeSpeed: TDFSize;
57 mSizeEnd: TDFSize;
59 mEndPosTrig: Integer;
60 mEndSizeTrig: Integer;
62 mNeedSend: Boolean; // for network
64 private
65 function getx1 (): Integer; inline;
66 function gety1 (): Integer; inline;
67 function getvisvalid (): Boolean; inline;
69 function getMovingSpeedX (): Integer; inline;
70 procedure setMovingSpeedX (v: Integer); inline;
71 function getMovingSpeedY (): Integer; inline;
72 procedure setMovingSpeedY (v: Integer); inline;
74 function getMovingStartX (): Integer; inline;
75 procedure setMovingStartX (v: Integer); inline;
76 function getMovingStartY (): Integer; inline;
77 procedure setMovingStartY (v: Integer); inline;
79 function getMovingEndX (): Integer; inline;
80 procedure setMovingEndX (v: Integer); inline;
81 function getMovingEndY (): Integer; inline;
82 procedure setMovingEndY (v: Integer); inline;
84 function getSizeSpeedX (): Integer; inline;
85 procedure setSizeSpeedX (v: Integer); inline;
86 function getSizeSpeedY (): Integer; inline;
87 procedure setSizeSpeedY (v: Integer); inline;
89 function getSizeEndX (): Integer; inline;
90 procedure setSizeEndX (v: Integer); inline;
91 function getSizeEndY (): Integer; inline;
92 procedure setSizeEndY (v: Integer); inline;
94 public
95 FCurTexture: Integer; // Íîìåð òåêóùåé òåêñòóðû
96 FCurFrame: Integer;
97 FCurFrameCount: Byte;
98 FX, FY: Integer;
99 FWidth, FHeight: Word;
100 FPanelType: Word;
101 FEnabled: Boolean;
102 FDoor: Boolean;
103 FLiftType: Byte;
104 FLastAnimLoop: Byte;
105 // sorry, there fields are public to allow setting 'em in g_map; this should be fixed later
106 // for now, PLEASE, don't modify 'em, or all hell will break loose
107 arrIdx: Integer; // index in one of internal arrays; sorry
108 tag: Integer; // used in coldets and such; sorry; see g_map.GridTagXXX
109 proxyId: Integer; // proxy id in map grid (DO NOT USE!)
110 mapId: AnsiString; // taken directly from map file; dunno why it is here
112 constructor Create(PanelRec: TDynRecord;
113 AddTextures: TAddTextureArray;
114 CurTex: Integer;
115 var Textures: TLevelTextureArray; aguid: Integer);
116 destructor Destroy(); override;
118 procedure Draw();
119 procedure DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
120 procedure Update();
121 procedure SetFrame(Frame: Integer; Count: Byte);
122 procedure NextTexture(AnimLoop: Byte = 0);
123 procedure SetTexture(ID: Integer; AnimLoop: Byte = 0);
124 function GetTextureID(): Cardinal;
125 function GetTextureCount(): Integer;
127 procedure SaveState(var Mem: TBinMemoryWriter);
128 procedure LoadState(var Mem: TBinMemoryReader);
130 procedure positionChanged (); inline;
132 function getIsGBack (): Boolean; inline; // gRenderBackgrounds
133 function getIsGStep (): Boolean; inline; // gSteps
134 function getIsGWall (): Boolean; inline; // gWalls
135 function getIsGAcid1 (): Boolean; inline; // gAcid1
136 function getIsGAcid2 (): Boolean; inline; // gAcid2
137 function getIsGWater (): Boolean; inline; // gWater
138 function getIsGFore (): Boolean; inline; // gRenderForegrounds
139 function getIsGLift (): Boolean; inline; // gLifts
140 function getIsGBlockMon (): Boolean; inline; // gBlockMon
142 // get-and-clear
143 function gncNeedSend (): Boolean; inline;
144 procedure setDirty (); inline; // why `dirty`? 'cause i may introduce property `needSend` later
146 public
147 property visvalid: Boolean read getvisvalid; // panel is "visvalid" when it's width and height are positive
149 published
150 property guid: Integer read mGUID; // will be assigned in "g_map.pas"
151 property x0: Integer read FX;
152 property y0: Integer read FY;
153 property x1: Integer read getx1; // inclusive!
154 property y1: Integer read gety1; // inclusive!
155 property x: Integer read FX write FX;
156 property y: Integer read FY write FY;
157 property width: Word read FWidth write FWidth;
158 property height: Word read FHeight write FHeight;
159 property panelType: Word read FPanelType write FPanelType;
160 property enabled: Boolean read FEnabled write FEnabled; // Ñîõðàíÿòü ïðè SaveState?
161 property door: Boolean read FDoor write FDoor; // Ñîõðàíÿòü ïðè SaveState?
162 property liftType: Byte read FLiftType write FLiftType; // Ñîõðàíÿòü ïðè SaveState?
163 property lastAnimLoop: Byte read FLastAnimLoop write FLastAnimLoop; // Ñîõðàíÿòü ïðè SaveState?
165 property movingSpeedX: Integer read getMovingSpeedX write setMovingSpeedX;
166 property movingSpeedY: Integer read getMovingSpeedY write setMovingSpeedY;
167 property movingStartX: Integer read getMovingStartX write setMovingStartX;
168 property movingStartY: Integer read getMovingStartY write setMovingStartY;
169 property movingEndX: Integer read getMovingEndX write setMovingEndX;
170 property movingEndY: Integer read getMovingEndY write setMovingEndY;
171 property movingActive: Boolean read mMovingActive write mMovingActive;
172 property moveOnce: Boolean read mMoveOnce write mMoveOnce;
174 property sizeSpeedX: Integer read getSizeSpeedX write setSizeSpeedX;
175 property sizeSpeedY: Integer read getSizeSpeedY write setSizeSpeedY;
176 property sizeEndX: Integer read getSizeEndX write setSizeEndX;
177 property sizeEndY: Integer read getSizeEndY write setSizeEndY;
179 property isGBack: Boolean read getIsGBack;
180 property isGStep: Boolean read getIsGStep;
181 property isGWall: Boolean read getIsGWall;
182 property isGAcid1: Boolean read getIsGAcid1;
183 property isGAcid2: Boolean read getIsGAcid2;
184 property isGWater: Boolean read getIsGWater;
185 property isGFore: Boolean read getIsGFore;
186 property isGLift: Boolean read getIsGLift;
187 property isGBlockMon: Boolean read getIsGBlockMon;
189 public
190 property movingSpeed: TDFPoint read mMovingSpeed write mMovingSpeed;
191 property movingStart: TDFPoint read mMovingStart write mMovingStart;
192 property movingEnd: TDFPoint read mMovingEnd write mMovingEnd;
194 property sizeSpeed: TDFSize read mSizeSpeed write mSizeSpeed;
195 property sizeEnd: TDFSize read mSizeEnd write mSizeEnd;
197 property endPosTrigId: Integer read mEndPosTrig write mEndPosTrig;
198 property endSizeTrigId: Integer read mEndSizeTrig write mEndSizeTrig;
199 end;
201 TPanelArray = Array of TPanel;
203 var
204 g_dbgpan_mplat_active: Boolean = {$IF DEFINED(D2F_DEBUG)}true{$ELSE}true{$ENDIF};
205 g_dbgpan_mplat_step: Boolean = false; // one step, and stop
208 implementation
210 uses
211 SysUtils, g_basic, g_map, g_game, g_gfx, e_graphics, g_weapons, g_triggers,
212 g_console, g_language, g_monsters, g_player, g_grid, e_log, GL, utils;
214 const
215 PANEL_SIGNATURE = $4C4E4150; // 'PANL'
217 { T P a n e l : }
219 constructor TPanel.Create(PanelRec: TDynRecord;
220 AddTextures: TAddTextureArray;
221 CurTex: Integer;
222 var Textures: TLevelTextureArray; aguid: Integer);
223 var
224 i: Integer;
225 begin
226 X := PanelRec.X;
227 Y := PanelRec.Y;
228 Width := PanelRec.Width;
229 Height := PanelRec.Height;
230 FAlpha := 0;
231 FBlending := False;
232 FCurFrame := 0;
233 FCurFrameCount := 0;
234 LastAnimLoop := 0;
236 mapId := PanelRec.id;
237 mGUID := aguid;
239 mMovingSpeed := PanelRec.moveSpeed;
240 mMovingStart := PanelRec.moveStart;
241 mMovingEnd := PanelRec.moveEnd;
242 mMovingActive := PanelRec['move_active'].value;
243 mOldMovingActive := mMovingActive;
244 mMoveOnce := PanelRec.moveOnce;
246 mSizeSpeed := PanelRec.sizeSpeed;
247 mSizeEnd := PanelRec.sizeEnd;
249 mEndPosTrig := PanelRec.endPosTrig;
250 mEndSizeTrig := PanelRec.endSizeTrig;
252 mNeedSend := false;
254 // Òèï ïàíåëè:
255 PanelType := PanelRec.PanelType;
256 Enabled := True;
257 Door := False;
258 LiftType := 0;
260 case PanelType of
261 PANEL_OPENDOOR: begin Enabled := False; Door := True; end;
262 PANEL_CLOSEDOOR: Door := True;
263 PANEL_LIFTUP: LiftType := 0; //???
264 PANEL_LIFTDOWN: LiftType := 1;
265 PANEL_LIFTLEFT: LiftType := 2;
266 PANEL_LIFTRIGHT: LiftType := 3;
267 end;
269 // Íåâèäèìàÿ:
270 if ByteBool(PanelRec.Flags and PANEL_FLAG_HIDE) then
271 begin
272 SetLength(FTextureIDs, 0);
273 FCurTexture := -1;
274 Exit;
275 end;
276 // Ïàíåëè, íå èñïîëüçóþùèå òåêñòóðû:
277 if ByteBool(PanelType and
278 (PANEL_LIFTUP or
279 PANEL_LIFTDOWN or
280 PANEL_LIFTLEFT or
281 PANEL_LIFTRIGHT or
282 PANEL_BLOCKMON)) then
283 begin
284 SetLength(FTextureIDs, 0);
285 FCurTexture := -1;
286 Exit;
287 end;
289 // Åñëè ýòî æèäêîñòü áåç òåêñòóðû - ñïåöòåêñòóðó:
290 if WordBool(PanelType and (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2)) and
291 (not ByteBool(PanelRec.Flags and PANEL_FLAG_WATERTEXTURES)) then
292 begin
293 SetLength(FTextureIDs, 1);
294 FTextureIDs[0].Anim := False;
296 case PanelRec.PanelType of
297 PANEL_WATER:
298 FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_WATER);
299 PANEL_ACID1:
300 FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_ACID1);
301 PANEL_ACID2:
302 FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_ACID2);
303 end;
305 FCurTexture := 0;
306 Exit;
307 end;
309 SetLength(FTextureIDs, Length(AddTextures));
311 if CurTex < 0 then
312 FCurTexture := -1
313 else
314 if CurTex >= Length(FTextureIDs) then
315 FCurTexture := Length(FTextureIDs) - 1
316 else
317 FCurTexture := CurTex;
319 for i := 0 to Length(FTextureIDs)-1 do
320 begin
321 FTextureIDs[i].Anim := AddTextures[i].Anim;
322 if FTextureIDs[i].Anim then
323 begin // Àíèìèðîâàííàÿ òåêñòóðà
324 FTextureIDs[i].AnTex :=
325 TAnimation.Create(Textures[AddTextures[i].Texture].FramesID,
326 True, Textures[AddTextures[i].Texture].Speed);
327 FTextureIDs[i].AnTex.Blending := ByteBool(PanelRec.Flags and PANEL_FLAG_BLENDING);
328 FTextureIDs[i].AnTex.Alpha := PanelRec.Alpha;
329 end
330 else
331 begin // Îáû÷íàÿ òåêñòóðà
332 FTextureIDs[i].Tex := Textures[AddTextures[i].Texture].TextureID;
333 end;
334 end;
336 // Òåêñòóð íåñêîëüêî - íóæíî ñîõðàíÿòü òåêóùóþ:
337 //if Length(FTextureIDs) > 1 then SaveIt := True;
339 // Åñëè íå ñïåöòåêñòóðà, òî çàäàåì ðàçìåðû:
340 if PanelRec.TextureNum > High(Textures) then
341 begin
342 e_WriteLog(Format('WTF?! PanelRec.TextureNum is out of limits! (%d : %d)', [PanelRec.TextureNum, High(Textures)]), MSG_FATALERROR);
343 FTextureWidth := 2;
344 FTextureHeight := 2;
345 FAlpha := 0;
346 FBlending := ByteBool(0);
347 end
348 else if not g_Map_IsSpecialTexture(Textures[PanelRec.TextureNum].TextureName) then
349 begin
350 FTextureWidth := Textures[PanelRec.TextureNum].Width;
351 FTextureHeight := Textures[PanelRec.TextureNum].Height;
352 FAlpha := PanelRec.Alpha;
353 FBlending := ByteBool(PanelRec.Flags and PANEL_FLAG_BLENDING);
354 end;
355 end;
357 destructor TPanel.Destroy();
358 var
359 i: Integer;
360 begin
361 for i := 0 to High(FTextureIDs) do
362 if FTextureIDs[i].Anim then
363 FTextureIDs[i].AnTex.Free();
364 SetLength(FTextureIDs, 0);
366 Inherited;
367 end;
369 function TPanel.getx1 (): Integer; inline; begin result := X+Width-1; end;
370 function TPanel.gety1 (): Integer; inline; begin result := Y+Height-1; end;
371 function TPanel.getvisvalid (): Boolean; inline; begin result := (Width > 0) and (Height > 0); end;
373 function TPanel.getMovingSpeedX (): Integer; inline; begin result := mMovingSpeed.X; end;
374 procedure TPanel.setMovingSpeedX (v: Integer); inline; begin mMovingSpeed.X := v; end;
375 function TPanel.getMovingSpeedY (): Integer; inline; begin result := mMovingSpeed.Y; end;
376 procedure TPanel.setMovingSpeedY (v: Integer); inline; begin mMovingSpeed.Y := v; end;
378 function TPanel.getMovingStartX (): Integer; inline; begin result := mMovingStart.X; end;
379 procedure TPanel.setMovingStartX (v: Integer); inline; begin mMovingStart.X := v; end;
380 function TPanel.getMovingStartY (): Integer; inline; begin result := mMovingStart.Y; end;
381 procedure TPanel.setMovingStartY (v: Integer); inline; begin mMovingStart.Y := v; end;
383 function TPanel.getMovingEndX (): Integer; inline; begin result := mMovingEnd.X; end;
384 procedure TPanel.setMovingEndX (v: Integer); inline; begin mMovingEnd.X := v; end;
385 function TPanel.getMovingEndY (): Integer; inline; begin result := mMovingEnd.Y; end;
386 procedure TPanel.setMovingEndY (v: Integer); inline; begin mMovingEnd.Y := v; end;
388 function TPanel.getSizeSpeedX (): Integer; inline; begin result := mSizeSpeed.w; end;
389 procedure TPanel.setSizeSpeedX (v: Integer); inline; begin mSizeSpeed.w := v; end;
390 function TPanel.getSizeSpeedY (): Integer; inline; begin result := mSizeSpeed.h; end;
391 procedure TPanel.setSizeSpeedY (v: Integer); inline; begin mSizeSpeed.h := v; end;
393 function TPanel.getSizeEndX (): Integer; inline; begin result := mSizeEnd.w; end;
394 procedure TPanel.setSizeEndX (v: Integer); inline; begin mSizeEnd.w := v; end;
395 function TPanel.getSizeEndY (): Integer; inline; begin result := mSizeEnd.h; end;
396 procedure TPanel.setSizeEndY (v: Integer); inline; begin mSizeEnd.h := v; end;
398 function TPanel.getIsGBack (): Boolean; inline; begin result := ((tag and GridTagBack) <> 0); end;
399 function TPanel.getIsGStep (): Boolean; inline; begin result := ((tag and GridTagStep) <> 0); end;
400 function TPanel.getIsGWall (): Boolean; inline; begin result := ((tag and (GridTagWall or GridTagDoor)) <> 0); end;
401 function TPanel.getIsGAcid1 (): Boolean; inline; begin result := ((tag and GridTagAcid1) <> 0); end;
402 function TPanel.getIsGAcid2 (): Boolean; inline; begin result := ((tag and GridTagAcid2) <> 0); end;
403 function TPanel.getIsGWater (): Boolean; inline; begin result := ((tag and GridTagWater) <> 0); end;
404 function TPanel.getIsGFore (): Boolean; inline; begin result := ((tag and GridTagFore) <> 0); end;
405 function TPanel.getIsGLift (): Boolean; inline; begin result := ((tag and GridTagLift) <> 0); end;
406 function TPanel.getIsGBlockMon (): Boolean; inline; begin result := ((tag and GridTagBlockMon) <> 0); end;
408 function TPanel.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end;
409 procedure TPanel.setDirty (); inline; begin mNeedSend := true; end;
412 procedure TPanel.Draw ();
413 var
414 xx, yy: Integer;
415 NoTextureID: DWORD;
416 NW, NH: Word;
417 begin
418 if {Enabled and} (FCurTexture >= 0) and
419 (Width > 0) and (Height > 0) and (FAlpha < 255) {and
420 g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)} then
421 begin
422 if FTextureIDs[FCurTexture].Anim then
423 begin // Àíèìèðîâàííàÿ òåêñòóðà
424 if FTextureIDs[FCurTexture].AnTex = nil then
425 Exit;
427 for xx := 0 to (Width div FTextureWidth)-1 do
428 for yy := 0 to (Height div FTextureHeight)-1 do
429 FTextureIDs[FCurTexture].AnTex.Draw(
430 X + xx*FTextureWidth,
431 Y + yy*FTextureHeight, M_NONE);
432 end
433 else
434 begin // Îáû÷íàÿ òåêñòóðà
435 case FTextureIDs[FCurTexture].Tex of
436 LongWord(TEXTURE_SPECIAL_WATER):
437 e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1,
438 0, 0, 255, 0, B_FILTER);
439 LongWord(TEXTURE_SPECIAL_ACID1):
440 e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1,
441 0, 128, 0, 0, B_FILTER);
442 LongWord(TEXTURE_SPECIAL_ACID2):
443 e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1,
444 128, 0, 0, 0, B_FILTER);
445 LongWord(TEXTURE_NONE):
446 if g_Texture_Get('NOTEXTURE', NoTextureID) then
447 begin
448 e_GetTextureSize(NoTextureID, @NW, @NH);
449 e_DrawFill(NoTextureID, X, Y, Width div NW, Height div NH,
450 0, False, False);
451 end else
452 begin
453 xx := X + (Width div 2);
454 yy := Y + (Height div 2);
455 e_DrawFillQuad(X, Y, xx, yy,
456 255, 0, 255, 0);
457 e_DrawFillQuad(xx, Y, X+Width-1, yy,
458 255, 255, 0, 0);
459 e_DrawFillQuad(X, yy, xx, Y+Height-1,
460 255, 255, 0, 0);
461 e_DrawFillQuad(xx, yy, X+Width-1, Y+Height-1,
462 255, 0, 255, 0);
463 end;
465 else
466 if not mMovingActive then
467 e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending)
468 else
469 e_DrawFillX(FTextureIDs[FCurTexture].Tex, X, Y, Width, Height, FAlpha, True, FBlending, g_dbg_scale);
470 end;
471 end;
472 end;
473 end;
475 procedure TPanel.DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
476 procedure extrude (x: Integer; y: Integer);
477 begin
478 glVertex2i(x+(x-lightX)*500, y+(y-lightY)*500);
479 //e_WriteLog(Format(' : (%d,%d)', [x+(x-lightX)*300, y+(y-lightY)*300]), MSG_WARNING);
480 end;
482 procedure drawLine (x0: Integer; y0: Integer; x1: Integer; y1: Integer);
483 begin
484 // does this side facing the light?
485 if ((x1-x0)*(lightY-y0)-(lightX-x0)*(y1-y0) >= 0) then exit;
486 //e_WriteLog(Format('lightpan: (%d,%d)-(%d,%d)', [x0, y0, x1, y1]), MSG_WARNING);
487 // this edge is facing the light, extrude and draw it
488 glVertex2i(x0, y0);
489 glVertex2i(x1, y1);
490 extrude(x1, y1);
491 extrude(x0, y0);
492 end;
494 begin
495 if radius < 4 then exit;
496 if Enabled and (FCurTexture >= 0) and (Width > 0) and (Height > 0) and (FAlpha < 255) {and
497 g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)} then
498 begin
499 if not FTextureIDs[FCurTexture].Anim then
500 begin
501 case FTextureIDs[FCurTexture].Tex of
502 LongWord(TEXTURE_SPECIAL_WATER): exit;
503 LongWord(TEXTURE_SPECIAL_ACID1): exit;
504 LongWord(TEXTURE_SPECIAL_ACID2): exit;
505 LongWord(TEXTURE_NONE): exit;
506 end;
507 end;
508 if (X+Width < lightX-radius) then exit;
509 if (Y+Height < lightY-radius) then exit;
510 if (X > lightX+radius) then exit;
511 if (Y > lightY+radius) then exit;
512 //e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending);
514 glBegin(GL_QUADS);
515 drawLine(x, y, x+width, y); // top
516 drawLine(x+width, y, x+width, y+height); // right
517 drawLine(x+width, y+height, x, y+height); // bottom
518 drawLine(x, y+height, x, y); // left
519 glEnd();
520 end;
521 end;
524 procedure TPanel.positionChanged (); inline;
525 var
526 px, py, pw, ph: Integer;
527 begin
528 if (proxyId >= 0) then
529 begin
530 mapGrid.getBodyDims(proxyId, px, py, pw, ph);
531 if (px <> x) or (py <> y) or (pw <> Width) or (ph <> Height) then
532 begin
534 e_LogWritefln('panel moved: arridx=%s; guid=%s; proxyid=%s; old:(%s,%s)-(%sx%s); new:(%s,%s)-(%sx%s)',
535 [arrIdx, mGUID, proxyId, px, py, pw, ph, x, y, width, height]);
537 g_Mark(px, py, pw, ph, MARK_WALL, false);
538 if (Width < 1) or (Height < 1) then
539 begin
540 mapGrid.proxyEnabled[proxyId] := false;
541 end
542 else
543 begin
544 mapGrid.proxyEnabled[proxyId] := Enabled;
545 if (pw <> Width) or (ph <> Height) then
546 begin
547 //writeln('panel resize!');
548 mapGrid.moveResizeBody(proxyId, X, Y, Width, Height)
549 end
550 else
551 begin
552 mapGrid.moveBody(proxyId, X, Y);
553 end;
554 g_Mark(X, Y, Width, Height, MARK_WALL);
555 end;
556 end;
557 end;
558 end;
561 var
562 monCheckList: array of TMonster = nil;
563 monCheckListUsed: Integer = 0;
565 procedure TPanel.Update();
566 var
567 ox, oy: Integer;
568 nx, ny, nw, nh: Integer;
569 ex, ey, nex, ney: Integer;
570 mpw, mph: Integer;
572 // return `true` if we should move by dx,dy
573 function tryMPlatMove (px, py, pw, ph: Integer; out dx, dy: Integer; out squash: Boolean; ontop: PBoolean=nil): Boolean;
574 var
575 u0: Single;
576 tex, tey: Integer;
577 pdx, pdy: Integer;
578 trtag: Integer;
579 szdx, szdy: Integer;
580 begin
581 squash := false;
582 tex := px;
583 tey := py;
584 pdx := mMovingSpeed.X;
585 pdy := mMovingSpeed.Y;
586 // standing on the platform?
587 if (py+ph = oy) then
588 begin
589 if (ontop <> nil) then ontop^ := true;
590 // yes, move with it; but skip steps (no need to process size change here, 'cause platform top cannot be changed with it)
591 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, (GridTagWall or GridTagDoor));
592 end
593 else
594 begin
595 if (ontop <> nil) then ontop^ := false;
596 // not standing on the platform: trace platform to see if it hits the entity
597 // first, process size change (as we cannot sweeptest both move and size change)
598 // but we don't have to check for pushing if the panel is shrinking
599 szdx := nw-mpw;
600 szdy := nh-mph;
601 if (szdx > 0) or (szdy > 0) then
602 begin
603 // ignore shrinking dimension
604 if (szdx < 0) then szdx := 0;
605 if (szdy < 0) then szdy := 0;
606 // move platform by szd* back, and check for szd* movement
607 if sweepAABB(ox-szdx, oy-szdy, nw, nh, szdx, szdy, px, py, pw, ph, @u0) then
608 begin
609 // yes, platform hits the entity, push the entity in the resizing direction
610 u0 := 1.0-u0; // how much path left?
611 szdx := trunc(szdx*u0);
612 szdy := trunc(szdy*u0);
613 if (szdx <> 0) or (szdy <> 0) then
614 begin
615 // has some path to go, trace the entity
616 trtag := (GridTagWall or GridTagDoor);
617 // if we're moving down, consider steps too
618 if (szdy > 0) then trtag := trtag or GridTagStep;
619 mapGrid.traceBox(tex, tey, px, py, pw, ph, szdx, szdy, nil, trtag);
620 end;
621 end;
622 end;
623 // second, process platform movement, using te* as entity starting point
624 if sweepAABB(ox, oy, nw, nh, pdx, pdy, tex, tey, pw, ph, @u0) then
625 begin
626 //e_LogWritefln('T: platsweep; u0=%s; u1=%s; hedge=%s; sweepAABB(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', [u0, u1, hedge, ox, oy, mpw, mph, pdx, pdy, px-1, py-1, pw+2, ph+2]);
627 // yes, platform hits the entity, push the entity in the direction of the platform
628 u0 := 1.0-u0; // how much path left?
629 pdx := trunc(pdx*u0);
630 pdy := trunc(pdy*u0);
631 //e_LogWritefln(' platsweep; uleft=%s; pd=(%s,%s)', [u0, pdx, pdy]);
632 if (pdx <> 0) or (pdy <> 0) then
633 begin
634 // has some path to go, trace the entity
635 trtag := (GridTagWall or GridTagDoor);
636 // if we're moving down, consider steps too
637 if (pdy > 0) then trtag := trtag or GridTagStep;
638 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, trtag);
639 end;
640 end;
641 end;
642 // done with entity movement, new coords are in te*
643 dx := tex-px;
644 dy := tey-py;
645 result := (dx <> 0) or (dy <> 0);
646 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
647 begin
648 // check for squashing; as entity cannot be pushed into a wall, check only collision with the platform itself
649 squash := g_Collide(tex, tey, pw, ph, nx, ny, nw, nh); // squash, if still in platform
650 end;
651 end;
653 function monCollect (mon: TMonster): Boolean;
654 begin
655 result := false; // don't stop
656 if (monCheckListUsed >= Length(monCheckList)) then SetLength(monCheckList, monCheckListUsed+128);
657 monCheckList[monCheckListUsed] := mon;
658 Inc(monCheckListUsed);
659 end;
661 var
662 cx0, cy0, cx1, cy1, cw, ch: Integer;
663 f: Integer;
664 px, py, pw, ph, pdx, pdy: Integer;
665 squash: Boolean;
666 plr: TPlayer;
667 gib: PGib;
668 cor: TCorpse;
669 mon: TMonster;
670 mpfrid: LongWord;
671 ontop: Boolean;
672 actMoveTrig: Boolean;
673 actSizeTrig: Boolean;
674 begin
675 if (not Enabled) or (Width < 1) or (Height < 1) then exit;
677 if (FCurTexture >= 0) and
678 (FTextureIDs[FCurTexture].Anim) and
679 (FTextureIDs[FCurTexture].AnTex <> nil) and
680 (FAlpha < 255) then
681 begin
682 FTextureIDs[FCurTexture].AnTex.Update();
683 FCurFrame := FTextureIDs[FCurTexture].AnTex.CurrentFrame;
684 FCurFrameCount := FTextureIDs[FCurTexture].AnTex.CurrentCounter;
685 end;
687 if not g_dbgpan_mplat_active then exit;
689 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
690 mOldMovingActive := mMovingActive;
692 if not mMovingActive then exit;
693 if mMovingSpeed.isZero and mSizeSpeed.isZero then exit;
695 //TODO: write wall size change processing
697 // moving platform?
698 begin
699 (*
700 * collect all monsters and players (aka entities) along the possible platform path
701 * if entity is standing on a platform:
702 * try to move it along the platform path, checking wall collisions
703 * if entity is NOT standing on a platform, but hit with sweeped platform aabb:
704 * try to push entity
705 * if we can't push entity all the way, squash it
706 *)
707 ox := X;
708 oy := Y;
709 mpw := Width;
710 mph := Height;
712 nw := mpw+mSizeSpeed.w;
713 nh := mph+mSizeSpeed.h;
714 nx := ox+mMovingSpeed.X;
715 ny := oy+mMovingSpeed.Y;
717 // force network updates only if some sudden change happened
718 // set the flag here, so we can sync affected monsters
719 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
720 begin
721 mNeedSend := true;
722 end
723 else if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
724 begin
725 mNeedSend := true;
726 end
727 else if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
728 begin
729 mNeedSend := true;
730 end;
732 // if pannel disappeared, we don't have to do anything
733 if (nw > 0) and (nh > 0) then
734 begin
735 // old rect
736 ex := ox+mpw-1;
737 ey := ox+mph-1;
738 // new rect
739 nex := nx+nw-1;
740 ney := ny+nh-1;
741 // full rect
742 cx0 := nmin(ox, nx);
743 cy0 := nmin(oy, ny);
744 cx1 := nmax(ex, nex);
745 cy1 := nmax(ey, ney);
746 // extrude
747 cx0 -= 1;
748 cy0 -= 1;
749 cx1 += 1;
750 cy1 += 1;
751 cw := cx1-cx0+1;
752 ch := cy1-cy0+1;
754 // process "obstacle" panels
755 if ((tag and GridTagObstacle) <> 0) then
756 begin
757 // temporarily turn off this panel, so it won't interfere with collision checks
758 mapGrid.proxyEnabled[proxyId] := false;
760 // process players
761 for f := 0 to High(gPlayers) do
762 begin
763 plr := gPlayers[f];
764 if (plr = nil) or (not plr.alive) then continue;
765 plr.getMapBox(px, py, pw, ph);
766 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
767 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
768 begin
769 // set new position
770 plr.moveBy(pdx, pdy); // this will call `positionChanged()` for us
771 end;
772 // squash player, if necessary
773 if not g_Game_IsClient and squash then plr.Damage(15000, 0, 0, 0, HIT_TRAP);
774 end;
776 // process gibs
777 for f := 0 to High(gGibs) do
778 begin
779 gib := @gGibs[f];
780 if not gib.alive then continue;
781 gib.getMapBox(px, py, pw, ph);
782 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
783 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
784 begin
785 // set new position
786 gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
787 end;
788 end;
790 // move and push corpses
791 for f := 0 to High(gCorpses) do
792 begin
793 cor := gCorpses[f];
794 if (cor = nil) then continue;
795 cor.getMapBox(px, py, pw, ph);
796 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
797 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
798 begin
799 // set new position
800 cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
801 end;
802 end;
804 // collect monsters
805 monCheckListUsed := 0;
806 g_Mons_ForEachAt(cx0, cy0, cw, ch, monCollect);
808 // process collected monsters
809 if (monCheckListUsed > 0) then
810 begin
811 mpfrid := g_Mons_getNewMPlatFrameId();
812 for f := 0 to monCheckListUsed do
813 begin
814 mon := monCheckList[f];
815 if (mon = nil) or (not mon.alive) or (mon.mplatCheckFrameId = mpfrid) then continue;
816 mon.mplatCheckFrameId := mpfrid;
817 mon.getMapBox(px, py, pw, ph);
818 //if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
819 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
820 begin
821 // set new position
822 mon.moveBy(pdx, pdy); // this will call `positionChanged()` for us
823 //???FIXME: do we really need to send monsters over the net?
824 // i don't think so, as dead reckoning should take care of 'em
825 // ok, send new monster position only if platform is going to change it's direction
826 if mNeedSend then mon.setDirty();
827 end;
828 // squash monster, if necessary
829 if not g_Game_IsClient and squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
830 end;
831 end;
833 // restore panel state
834 mapGrid.proxyEnabled[proxyId] := true;
835 end;
836 end;
838 // move panel
839 X := nx;
840 Y := ny;
841 FWidth := nw;
842 FHeight := nh;
843 positionChanged();
845 actMoveTrig := false;
846 actSizeTrig := false;
848 // `mNeedSend` was set above
850 // check "size stop"
851 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
852 begin
853 mSizeSpeed.w := 0;
854 mSizeSpeed.h := 0;
855 actSizeTrig := true;
856 if (nw < 1) or (nh < 1) then mMovingActive := false; //HACK!
857 end;
859 // reverse moving direction, if necessary
860 if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
861 begin
862 if mMoveOnce then mMovingActive := false else mMovingSpeed.X := -mMovingSpeed.X;
863 actMoveTrig := true;
864 end;
866 if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
867 begin
868 if mMoveOnce then mMovingActive := false else mMovingSpeed.Y := -mMovingSpeed.Y;
869 actMoveTrig := true;
870 end;
872 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
873 mOldMovingActive := mMovingActive;
875 if not g_Game_IsClient then
876 begin
877 if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
878 if actSizeTrig then g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
879 end;
881 // some triggers may activate this, don't delay sending
882 //TODO: when triggers will be able to control speed and size, check that here too
883 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
884 mOldMovingActive := mMovingActive;
885 end;
886 end;
889 procedure TPanel.SetFrame(Frame: Integer; Count: Byte);
891 function ClampInt(X, A, B: Integer): Integer;
892 begin
893 Result := X;
894 if X < A then Result := A else if X > B then Result := B;
895 end;
897 begin
898 if Enabled and (FCurTexture >= 0) and
899 (FTextureIDs[FCurTexture].Anim) and
900 (FTextureIDs[FCurTexture].AnTex <> nil) and
901 (Width > 0) and (Height > 0) and (FAlpha < 255) then
902 begin
903 FCurFrame := ClampInt(Frame, 0, FTextureIDs[FCurTexture].AnTex.TotalFrames);
904 FCurFrameCount := Count;
905 FTextureIDs[FCurTexture].AnTex.CurrentFrame := FCurFrame;
906 FTextureIDs[FCurTexture].AnTex.CurrentCounter := FCurFrameCount;
907 end;
908 end;
910 procedure TPanel.NextTexture(AnimLoop: Byte = 0);
911 begin
912 Assert(FCurTexture >= -1, 'FCurTexture < -1');
914 // Íåò òåêñòóð:
915 if Length(FTextureIDs) = 0 then
916 FCurTexture := -1
917 else
918 // Òîëüêî îäíà òåêñòóðà:
919 if Length(FTextureIDs) = 1 then
920 begin
921 if FCurTexture = 0 then
922 FCurTexture := -1
923 else
924 FCurTexture := 0;
925 end
926 else
927 // Áîëüøå îäíîé òåêñòóðû:
928 begin
929 // Ñëåäóþùàÿ:
930 Inc(FCurTexture);
931 // Ñëåäóþùåé íåò - âîçâðàò ê íà÷àëó:
932 if FCurTexture >= Length(FTextureIDs) then
933 FCurTexture := 0;
934 end;
936 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
937 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
938 begin
939 if (FTextureIDs[FCurTexture].AnTex = nil) then
940 begin
941 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
942 Exit;
943 end;
945 if AnimLoop = 1 then
946 FTextureIDs[FCurTexture].AnTex.Loop := True
947 else
948 if AnimLoop = 2 then
949 FTextureIDs[FCurTexture].AnTex.Loop := False;
951 FTextureIDs[FCurTexture].AnTex.Reset();
952 end;
954 LastAnimLoop := AnimLoop;
955 end;
957 procedure TPanel.SetTexture(ID: Integer; AnimLoop: Byte = 0);
958 begin
959 // Íåò òåêñòóð:
960 if Length(FTextureIDs) = 0 then
961 FCurTexture := -1
962 else
963 // Òîëüêî îäíà òåêñòóðà:
964 if Length(FTextureIDs) = 1 then
965 begin
966 if (ID = 0) or (ID = -1) then
967 FCurTexture := ID;
968 end
969 else
970 // Áîëüøå îäíîé òåêñòóðû:
971 begin
972 if (ID >= -1) and (ID <= High(FTextureIDs)) then
973 FCurTexture := ID;
974 end;
976 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
977 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
978 begin
979 if (FTextureIDs[FCurTexture].AnTex = nil) then
980 begin
981 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
982 Exit;
983 end;
985 if AnimLoop = 1 then
986 FTextureIDs[FCurTexture].AnTex.Loop := True
987 else
988 if AnimLoop = 2 then
989 FTextureIDs[FCurTexture].AnTex.Loop := False;
991 FTextureIDs[FCurTexture].AnTex.Reset();
992 end;
994 LastAnimLoop := AnimLoop;
995 end;
997 function TPanel.GetTextureID(): DWORD;
998 begin
999 Result := LongWord(TEXTURE_NONE);
1001 if (FCurTexture >= 0) then
1002 begin
1003 if FTextureIDs[FCurTexture].Anim then
1004 Result := FTextureIDs[FCurTexture].AnTex.FramesID
1005 else
1006 Result := FTextureIDs[FCurTexture].Tex;
1007 end;
1008 end;
1010 function TPanel.GetTextureCount(): Integer;
1011 begin
1012 Result := Length(FTextureIDs);
1013 if Enabled and (FCurTexture >= 0) then
1014 if (FTextureIDs[FCurTexture].Anim) and
1015 (FTextureIDs[FCurTexture].AnTex <> nil) and
1016 (Width > 0) and (Height > 0) and (FAlpha < 255) then
1017 Result := Result + 100;
1018 end;
1020 const
1021 PAN_SAVE_VERSION = 1;
1023 procedure TPanel.SaveState(Var Mem: TBinMemoryWriter);
1024 var
1025 sig: DWORD;
1026 anim: Boolean;
1027 ver: Byte;
1028 begin
1029 if (Mem = nil) then exit;
1031 // Ñèãíàòóðà ïàíåëè:
1032 sig := PANEL_SIGNATURE; // 'PANL'
1033 Mem.WriteDWORD(sig);
1034 ver := PAN_SAVE_VERSION;
1035 Mem.WriteByte(ver);
1036 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
1037 Mem.WriteBoolean(FEnabled);
1038 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
1039 Mem.WriteByte(FLiftType);
1040 // Íîìåð òåêóùåé òåêñòóðû:
1041 Mem.WriteInt(FCurTexture);
1042 // Êîîðäû
1043 Mem.WriteInt(FX);
1044 Mem.WriteInt(FY);
1045 Mem.WriteWord(FWidth);
1046 Mem.WriteWord(FHeight);
1047 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà:
1048 if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
1049 begin
1050 Assert(FTextureIDs[FCurTexture].AnTex <> nil,
1051 'TPanel.SaveState: No animation object');
1052 anim := True;
1053 end
1054 else
1055 anim := False;
1056 Mem.WriteBoolean(anim);
1057 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ:
1058 if anim then
1059 FTextureIDs[FCurTexture].AnTex.SaveState(Mem);
1061 // moving platform state
1062 Mem.WriteInt(mMovingSpeed.X);
1063 Mem.WriteInt(mMovingSpeed.Y);
1064 Mem.WriteInt(mMovingStart.X);
1065 Mem.WriteInt(mMovingStart.Y);
1066 Mem.WriteInt(mMovingEnd.X);
1067 Mem.WriteInt(mMovingEnd.Y);
1069 Mem.WriteInt(mSizeSpeed.w);
1070 Mem.WriteInt(mSizeSpeed.h);
1071 Mem.WriteInt(mSizeEnd.w);
1072 Mem.WriteInt(mSizeEnd.h);
1073 Mem.WriteBoolean(mMovingActive);
1074 Mem.WriteBoolean(mMoveOnce);
1076 Mem.WriteInt(mEndPosTrig);
1077 Mem.WriteInt(mEndSizeTrig);
1078 end;
1080 procedure TPanel.LoadState(var Mem: TBinMemoryReader);
1081 var
1082 sig: DWORD;
1083 anim: Boolean;
1084 ver: Byte;
1085 //ox, oy: Integer;
1086 begin
1087 if (Mem = nil) then exit;
1089 // Ñèãíàòóðà ïàíåëè:
1090 Mem.ReadDWORD(sig);
1091 if (sig <> PANEL_SIGNATURE) then raise EBinSizeError.Create('TPanel.LoadState: wrong panel signature'); // 'PANL'
1092 Mem.ReadByte(ver);
1093 if (ver <> PAN_SAVE_VERSION) then raise EBinSizeError.Create('TPanel.LoadState: invalid panel version');
1094 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
1095 Mem.ReadBoolean(FEnabled);
1096 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
1097 Mem.ReadByte(FLiftType);
1098 // Íîìåð òåêóùåé òåêñòóðû:
1099 Mem.ReadInt(FCurTexture);
1100 // Êîîðäû
1101 //ox := FX;
1102 //oy := FY;
1103 Mem.ReadInt(FX);
1104 Mem.ReadInt(FY);
1105 Mem.ReadWord(FWidth);
1106 Mem.ReadWord(FHeight);
1107 //e_LogWritefln('panel %s(%s): old=(%s,%s); new=(%s,%s); delta=(%s,%s)', [arrIdx, proxyId, ox, oy, FX, FY, FX-ox, FY-oy]);
1108 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà:
1109 Mem.ReadBoolean(anim);
1110 // Åñëè äà - çàãðóæàåì àíèìàöèþ:
1111 if anim then
1112 begin
1113 Assert((FCurTexture >= 0) and
1114 (FTextureIDs[FCurTexture].Anim) and
1115 (FTextureIDs[FCurTexture].AnTex <> nil),
1116 'TPanel.LoadState: No animation object');
1117 FTextureIDs[FCurTexture].AnTex.LoadState(Mem);
1118 end;
1120 // moving platform state
1121 Mem.ReadInt(mMovingSpeed.X);
1122 Mem.ReadInt(mMovingSpeed.Y);
1123 Mem.ReadInt(mMovingStart.X);
1124 Mem.ReadInt(mMovingStart.Y);
1125 Mem.ReadInt(mMovingEnd.X);
1126 Mem.ReadInt(mMovingEnd.Y);
1127 Mem.ReadInt(mSizeSpeed.w);
1128 Mem.ReadInt(mSizeSpeed.h);
1129 Mem.ReadInt(mSizeEnd.w);
1130 Mem.ReadInt(mSizeEnd.h);
1131 Mem.ReadBoolean(mMovingActive);
1132 Mem.ReadBoolean(mMoveOnce);
1134 Mem.ReadInt(mEndPosTrig);
1135 Mem.ReadInt(mEndSizeTrig);
1137 positionChanged();
1138 //mapGrid.proxyEnabled[proxyId] := FEnabled; // done in g_map.pas
1139 end;
1141 end.