DEADSOFTWARE

dbg_scale fixes; made non-sky textures always NEAREST
[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 (hasAmbient: Boolean; constref ambColor: TDFColor);
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, e_texture, 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 (hasAmbient: Boolean; constref ambColor: TDFColor);
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): e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1, 0, 0, 255, 0, B_FILTER);
437 LongWord(TEXTURE_SPECIAL_ACID1): e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1, 0, 128, 0, 0, B_FILTER);
438 LongWord(TEXTURE_SPECIAL_ACID2): e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1, 128, 0, 0, 0, B_FILTER);
439 LongWord(TEXTURE_NONE):
440 if g_Texture_Get('NOTEXTURE', NoTextureID) then
441 begin
442 e_GetTextureSize(NoTextureID, @NW, @NH);
443 e_DrawFill(NoTextureID, X, Y, Width div NW, Height div NH, 0, False, False);
444 end
445 else
446 begin
447 xx := X + (Width div 2);
448 yy := Y + (Height div 2);
449 e_DrawFillQuad(X, Y, xx, yy, 255, 0, 255, 0);
450 e_DrawFillQuad(xx, Y, X+Width-1, yy, 255, 255, 0, 0);
451 e_DrawFillQuad(X, yy, xx, Y+Height-1, 255, 255, 0, 0);
452 e_DrawFillQuad(xx, yy, X+Width-1, Y+Height-1, 255, 0, 255, 0);
453 end;
454 else
455 begin
456 if not mMovingActive then
457 e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending, hasAmbient)
458 else
459 e_DrawFillX(FTextureIDs[FCurTexture].Tex, X, Y, Width, Height, FAlpha, True, FBlending, g_dbg_scale, hasAmbient);
460 if hasAmbient then e_AmbientQuad(X, Y, Width, Height, ambColor.r, ambColor.g, ambColor.b, ambColor.a);
461 end;
462 end;
463 end;
464 end;
465 end;
467 procedure TPanel.DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
468 procedure extrude (x: Integer; y: Integer);
469 begin
470 glVertex2i(x+(x-lightX)*500, y+(y-lightY)*500);
471 //e_WriteLog(Format(' : (%d,%d)', [x+(x-lightX)*300, y+(y-lightY)*300]), MSG_WARNING);
472 end;
474 procedure drawLine (x0: Integer; y0: Integer; x1: Integer; y1: Integer);
475 begin
476 // does this side facing the light?
477 if ((x1-x0)*(lightY-y0)-(lightX-x0)*(y1-y0) >= 0) then exit;
478 //e_WriteLog(Format('lightpan: (%d,%d)-(%d,%d)', [x0, y0, x1, y1]), MSG_WARNING);
479 // this edge is facing the light, extrude and draw it
480 glVertex2i(x0, y0);
481 glVertex2i(x1, y1);
482 extrude(x1, y1);
483 extrude(x0, y0);
484 end;
486 begin
487 if radius < 4 then exit;
488 if Enabled and (FCurTexture >= 0) and (Width > 0) and (Height > 0) and (FAlpha < 255) {and
489 g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)} then
490 begin
491 if not FTextureIDs[FCurTexture].Anim then
492 begin
493 case FTextureIDs[FCurTexture].Tex of
494 LongWord(TEXTURE_SPECIAL_WATER): exit;
495 LongWord(TEXTURE_SPECIAL_ACID1): exit;
496 LongWord(TEXTURE_SPECIAL_ACID2): exit;
497 LongWord(TEXTURE_NONE): exit;
498 end;
499 end;
500 if (X+Width < lightX-radius) then exit;
501 if (Y+Height < lightY-radius) then exit;
502 if (X > lightX+radius) then exit;
503 if (Y > lightY+radius) then exit;
504 //e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending);
506 glBegin(GL_QUADS);
507 drawLine(x, y, x+width, y); // top
508 drawLine(x+width, y, x+width, y+height); // right
509 drawLine(x+width, y+height, x, y+height); // bottom
510 drawLine(x, y+height, x, y); // left
511 glEnd();
512 end;
513 end;
516 procedure TPanel.positionChanged (); inline;
517 var
518 px, py, pw, ph: Integer;
519 begin
520 if (proxyId >= 0) then
521 begin
522 mapGrid.getBodyDims(proxyId, px, py, pw, ph);
523 if (px <> x) or (py <> y) or (pw <> Width) or (ph <> Height) then
524 begin
526 e_LogWritefln('panel moved: arridx=%s; guid=%s; proxyid=%s; old:(%s,%s)-(%sx%s); new:(%s,%s)-(%sx%s)',
527 [arrIdx, mGUID, proxyId, px, py, pw, ph, x, y, width, height]);
529 g_Mark(px, py, pw, ph, MARK_WALL, false);
530 if (Width < 1) or (Height < 1) then
531 begin
532 mapGrid.proxyEnabled[proxyId] := false;
533 end
534 else
535 begin
536 mapGrid.proxyEnabled[proxyId] := Enabled;
537 if (pw <> Width) or (ph <> Height) then
538 begin
539 //writeln('panel resize!');
540 mapGrid.moveResizeBody(proxyId, X, Y, Width, Height)
541 end
542 else
543 begin
544 mapGrid.moveBody(proxyId, X, Y);
545 end;
546 g_Mark(X, Y, Width, Height, MARK_WALL);
547 end;
548 end;
549 end;
550 end;
553 var
554 monCheckList: array of TMonster = nil;
555 monCheckListUsed: Integer = 0;
557 procedure TPanel.Update();
558 var
559 ox, oy: Integer;
560 nx, ny, nw, nh: Integer;
561 ex, ey, nex, ney: Integer;
562 mpw, mph: Integer;
564 // return `true` if we should move by dx,dy
565 function tryMPlatMove (px, py, pw, ph: Integer; out dx, dy: Integer; out squash: Boolean; ontop: PBoolean=nil): Boolean;
566 var
567 u0: Single;
568 tex, tey: Integer;
569 pdx, pdy: Integer;
570 trtag: Integer;
571 szdx, szdy: Integer;
572 begin
573 squash := false;
574 tex := px;
575 tey := py;
576 pdx := mMovingSpeed.X;
577 pdy := mMovingSpeed.Y;
578 // standing on the platform?
579 if (py+ph = oy) then
580 begin
581 if (ontop <> nil) then ontop^ := true;
582 // yes, move with it; but skip steps (no need to process size change here, 'cause platform top cannot be changed with it)
583 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, (GridTagWall or GridTagDoor));
584 end
585 else
586 begin
587 if (ontop <> nil) then ontop^ := false;
588 // not standing on the platform: trace platform to see if it hits the entity
589 // first, process size change (as we cannot sweeptest both move and size change)
590 // but we don't have to check for pushing if the panel is shrinking
591 szdx := nw-mpw;
592 szdy := nh-mph;
593 if (szdx > 0) or (szdy > 0) then
594 begin
595 // ignore shrinking dimension
596 if (szdx < 0) then szdx := 0;
597 if (szdy < 0) then szdy := 0;
598 // move platform by szd* back, and check for szd* movement
599 if sweepAABB(ox-szdx, oy-szdy, nw, nh, szdx, szdy, px, py, pw, ph, @u0) then
600 begin
601 // yes, platform hits the entity, push the entity in the resizing direction
602 u0 := 1.0-u0; // how much path left?
603 szdx := trunc(szdx*u0);
604 szdy := trunc(szdy*u0);
605 if (szdx <> 0) or (szdy <> 0) then
606 begin
607 // has some path to go, trace the entity
608 trtag := (GridTagWall or GridTagDoor);
609 // if we're moving down, consider steps too
610 if (szdy > 0) then trtag := trtag or GridTagStep;
611 mapGrid.traceBox(tex, tey, px, py, pw, ph, szdx, szdy, nil, trtag);
612 end;
613 end;
614 end;
615 // second, process platform movement, using te* as entity starting point
616 if sweepAABB(ox, oy, nw, nh, pdx, pdy, tex, tey, pw, ph, @u0) then
617 begin
618 //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]);
619 // yes, platform hits the entity, push the entity in the direction of the platform
620 u0 := 1.0-u0; // how much path left?
621 pdx := trunc(pdx*u0);
622 pdy := trunc(pdy*u0);
623 //e_LogWritefln(' platsweep; uleft=%s; pd=(%s,%s)', [u0, pdx, pdy]);
624 if (pdx <> 0) or (pdy <> 0) then
625 begin
626 // has some path to go, trace the entity
627 trtag := (GridTagWall or GridTagDoor);
628 // if we're moving down, consider steps too
629 if (pdy > 0) then trtag := trtag or GridTagStep;
630 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, trtag);
631 end;
632 end;
633 end;
634 // done with entity movement, new coords are in te*
635 dx := tex-px;
636 dy := tey-py;
637 result := (dx <> 0) or (dy <> 0);
638 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
639 begin
640 // check for squashing; as entity cannot be pushed into a wall, check only collision with the platform itself
641 squash := g_Collide(tex, tey, pw, ph, nx, ny, nw, nh); // squash, if still in platform
642 end;
643 end;
645 function monCollect (mon: TMonster): Boolean;
646 begin
647 result := false; // don't stop
648 if (monCheckListUsed >= Length(monCheckList)) then SetLength(monCheckList, monCheckListUsed+128);
649 monCheckList[monCheckListUsed] := mon;
650 Inc(monCheckListUsed);
651 end;
653 var
654 cx0, cy0, cx1, cy1, cw, ch: Integer;
655 f: Integer;
656 px, py, pw, ph, pdx, pdy: Integer;
657 squash: Boolean;
658 plr: TPlayer;
659 gib: PGib;
660 cor: TCorpse;
661 mon: TMonster;
662 mpfrid: LongWord;
663 ontop: Boolean;
664 actMoveTrig: Boolean;
665 actSizeTrig: Boolean;
666 begin
667 if (not Enabled) or (Width < 1) or (Height < 1) then exit;
669 if (FCurTexture >= 0) and
670 (FTextureIDs[FCurTexture].Anim) and
671 (FTextureIDs[FCurTexture].AnTex <> nil) and
672 (FAlpha < 255) then
673 begin
674 FTextureIDs[FCurTexture].AnTex.Update();
675 FCurFrame := FTextureIDs[FCurTexture].AnTex.CurrentFrame;
676 FCurFrameCount := FTextureIDs[FCurTexture].AnTex.CurrentCounter;
677 end;
679 if not g_dbgpan_mplat_active then exit;
681 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
682 mOldMovingActive := mMovingActive;
684 if not mMovingActive then exit;
685 if mMovingSpeed.isZero and mSizeSpeed.isZero then exit;
687 //TODO: write wall size change processing
689 // moving platform?
690 begin
691 (*
692 * collect all monsters and players (aka entities) along the possible platform path
693 * if entity is standing on a platform:
694 * try to move it along the platform path, checking wall collisions
695 * if entity is NOT standing on a platform, but hit with sweeped platform aabb:
696 * try to push entity
697 * if we can't push entity all the way, squash it
698 *)
699 ox := X;
700 oy := Y;
701 mpw := Width;
702 mph := Height;
704 nw := mpw+mSizeSpeed.w;
705 nh := mph+mSizeSpeed.h;
706 nx := ox+mMovingSpeed.X;
707 ny := oy+mMovingSpeed.Y;
709 // force network updates only if some sudden change happened
710 // set the flag here, so we can sync affected monsters
711 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
712 begin
713 mNeedSend := true;
714 end
715 else if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
716 begin
717 mNeedSend := true;
718 end
719 else if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
720 begin
721 mNeedSend := true;
722 end;
724 // if pannel disappeared, we don't have to do anything
725 if (nw > 0) and (nh > 0) then
726 begin
727 // old rect
728 ex := ox+mpw-1;
729 ey := ox+mph-1;
730 // new rect
731 nex := nx+nw-1;
732 ney := ny+nh-1;
733 // full rect
734 cx0 := nmin(ox, nx);
735 cy0 := nmin(oy, ny);
736 cx1 := nmax(ex, nex);
737 cy1 := nmax(ey, ney);
738 // extrude
739 cx0 -= 1;
740 cy0 -= 1;
741 cx1 += 1;
742 cy1 += 1;
743 cw := cx1-cx0+1;
744 ch := cy1-cy0+1;
746 // process "obstacle" panels
747 if ((tag and GridTagObstacle) <> 0) then
748 begin
749 // temporarily turn off this panel, so it won't interfere with collision checks
750 mapGrid.proxyEnabled[proxyId] := false;
752 // process players
753 for f := 0 to High(gPlayers) do
754 begin
755 plr := gPlayers[f];
756 if (plr = nil) or (not plr.alive) then continue;
757 plr.getMapBox(px, py, pw, ph);
758 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
759 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
760 begin
761 // set new position
762 plr.moveBy(pdx, pdy); // this will call `positionChanged()` for us
763 end;
764 // squash player, if necessary
765 if not g_Game_IsClient and squash then plr.Damage(15000, 0, 0, 0, HIT_TRAP);
766 end;
768 // process gibs
769 for f := 0 to High(gGibs) do
770 begin
771 gib := @gGibs[f];
772 if not gib.alive then continue;
773 gib.getMapBox(px, py, pw, ph);
774 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
775 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
776 begin
777 // set new position
778 gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
779 end;
780 end;
782 // move and push corpses
783 for f := 0 to High(gCorpses) do
784 begin
785 cor := gCorpses[f];
786 if (cor = nil) then continue;
787 cor.getMapBox(px, py, pw, ph);
788 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
789 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
790 begin
791 // set new position
792 cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
793 end;
794 end;
796 // collect monsters
797 monCheckListUsed := 0;
798 g_Mons_ForEachAt(cx0, cy0, cw, ch, monCollect);
800 // process collected monsters
801 if (monCheckListUsed > 0) then
802 begin
803 mpfrid := g_Mons_getNewMPlatFrameId();
804 for f := 0 to monCheckListUsed do
805 begin
806 mon := monCheckList[f];
807 if (mon = nil) or (not mon.alive) or (mon.mplatCheckFrameId = mpfrid) then continue;
808 mon.mplatCheckFrameId := mpfrid;
809 mon.getMapBox(px, py, pw, ph);
810 //if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
811 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
812 begin
813 // set new position
814 mon.moveBy(pdx, pdy); // this will call `positionChanged()` for us
815 //???FIXME: do we really need to send monsters over the net?
816 // i don't think so, as dead reckoning should take care of 'em
817 // ok, send new monster position only if platform is going to change it's direction
818 if mNeedSend then mon.setDirty();
819 end;
820 // squash monster, if necessary
821 if not g_Game_IsClient and squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
822 end;
823 end;
825 // restore panel state
826 mapGrid.proxyEnabled[proxyId] := true;
827 end;
828 end;
830 // move panel
831 X := nx;
832 Y := ny;
833 FWidth := nw;
834 FHeight := nh;
835 positionChanged();
837 actMoveTrig := false;
838 actSizeTrig := false;
840 // `mNeedSend` was set above
842 // check "size stop"
843 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
844 begin
845 mSizeSpeed.w := 0;
846 mSizeSpeed.h := 0;
847 actSizeTrig := true;
848 if (nw < 1) or (nh < 1) then mMovingActive := false; //HACK!
849 end;
851 // reverse moving direction, if necessary
852 if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
853 begin
854 if mMoveOnce then mMovingActive := false else mMovingSpeed.X := -mMovingSpeed.X;
855 actMoveTrig := true;
856 end;
858 if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
859 begin
860 if mMoveOnce then mMovingActive := false else mMovingSpeed.Y := -mMovingSpeed.Y;
861 actMoveTrig := true;
862 end;
864 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
865 mOldMovingActive := mMovingActive;
867 if not g_Game_IsClient then
868 begin
869 if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
870 if actSizeTrig then g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
871 end;
873 // some triggers may activate this, don't delay sending
874 //TODO: when triggers will be able to control speed and size, check that here too
875 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
876 mOldMovingActive := mMovingActive;
877 end;
878 end;
881 procedure TPanel.SetFrame(Frame: Integer; Count: Byte);
883 function ClampInt(X, A, B: Integer): Integer;
884 begin
885 Result := X;
886 if X < A then Result := A else if X > B then Result := B;
887 end;
889 begin
890 if Enabled and (FCurTexture >= 0) and
891 (FTextureIDs[FCurTexture].Anim) and
892 (FTextureIDs[FCurTexture].AnTex <> nil) and
893 (Width > 0) and (Height > 0) and (FAlpha < 255) then
894 begin
895 FCurFrame := ClampInt(Frame, 0, FTextureIDs[FCurTexture].AnTex.TotalFrames);
896 FCurFrameCount := Count;
897 FTextureIDs[FCurTexture].AnTex.CurrentFrame := FCurFrame;
898 FTextureIDs[FCurTexture].AnTex.CurrentCounter := FCurFrameCount;
899 end;
900 end;
902 procedure TPanel.NextTexture(AnimLoop: Byte = 0);
903 begin
904 Assert(FCurTexture >= -1, 'FCurTexture < -1');
906 // Íåò òåêñòóð:
907 if Length(FTextureIDs) = 0 then
908 FCurTexture := -1
909 else
910 // Òîëüêî îäíà òåêñòóðà:
911 if Length(FTextureIDs) = 1 then
912 begin
913 if FCurTexture = 0 then
914 FCurTexture := -1
915 else
916 FCurTexture := 0;
917 end
918 else
919 // Áîëüøå îäíîé òåêñòóðû:
920 begin
921 // Ñëåäóþùàÿ:
922 Inc(FCurTexture);
923 // Ñëåäóþùåé íåò - âîçâðàò ê íà÷àëó:
924 if FCurTexture >= Length(FTextureIDs) then
925 FCurTexture := 0;
926 end;
928 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
929 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
930 begin
931 if (FTextureIDs[FCurTexture].AnTex = nil) then
932 begin
933 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
934 Exit;
935 end;
937 if AnimLoop = 1 then
938 FTextureIDs[FCurTexture].AnTex.Loop := True
939 else
940 if AnimLoop = 2 then
941 FTextureIDs[FCurTexture].AnTex.Loop := False;
943 FTextureIDs[FCurTexture].AnTex.Reset();
944 end;
946 LastAnimLoop := AnimLoop;
947 end;
949 procedure TPanel.SetTexture(ID: Integer; AnimLoop: Byte = 0);
950 begin
951 // Íåò òåêñòóð:
952 if Length(FTextureIDs) = 0 then
953 FCurTexture := -1
954 else
955 // Òîëüêî îäíà òåêñòóðà:
956 if Length(FTextureIDs) = 1 then
957 begin
958 if (ID = 0) or (ID = -1) then
959 FCurTexture := ID;
960 end
961 else
962 // Áîëüøå îäíîé òåêñòóðû:
963 begin
964 if (ID >= -1) and (ID <= High(FTextureIDs)) then
965 FCurTexture := ID;
966 end;
968 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
969 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
970 begin
971 if (FTextureIDs[FCurTexture].AnTex = nil) then
972 begin
973 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
974 Exit;
975 end;
977 if AnimLoop = 1 then
978 FTextureIDs[FCurTexture].AnTex.Loop := True
979 else
980 if AnimLoop = 2 then
981 FTextureIDs[FCurTexture].AnTex.Loop := False;
983 FTextureIDs[FCurTexture].AnTex.Reset();
984 end;
986 LastAnimLoop := AnimLoop;
987 end;
989 function TPanel.GetTextureID(): DWORD;
990 begin
991 Result := LongWord(TEXTURE_NONE);
993 if (FCurTexture >= 0) then
994 begin
995 if FTextureIDs[FCurTexture].Anim then
996 Result := FTextureIDs[FCurTexture].AnTex.FramesID
997 else
998 Result := FTextureIDs[FCurTexture].Tex;
999 end;
1000 end;
1002 function TPanel.GetTextureCount(): Integer;
1003 begin
1004 Result := Length(FTextureIDs);
1005 if Enabled and (FCurTexture >= 0) then
1006 if (FTextureIDs[FCurTexture].Anim) and
1007 (FTextureIDs[FCurTexture].AnTex <> nil) and
1008 (Width > 0) and (Height > 0) and (FAlpha < 255) then
1009 Result := Result + 100;
1010 end;
1013 const
1014 PAN_SAVE_VERSION = 1;
1016 procedure TPanel.SaveState (var Mem: TBinMemoryWriter);
1017 var
1018 sig: DWORD;
1019 anim: Boolean;
1020 ver: Byte;
1021 begin
1022 if (Mem = nil) then exit;
1024 // Ñèãíàòóðà ïàíåëè
1025 sig := PANEL_SIGNATURE; // 'PANL'
1026 Mem.WriteDWORD(sig);
1027 ver := PAN_SAVE_VERSION;
1028 Mem.WriteByte(ver);
1029 // Îòêðûòà/çàêðûòà, åñëè äâåðü
1030 Mem.WriteBoolean(FEnabled);
1031 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò
1032 Mem.WriteByte(FLiftType);
1033 // Íîìåð òåêóùåé òåêñòóðû
1034 Mem.WriteInt(FCurTexture);
1035 // Êîîðäû
1036 Mem.WriteInt(FX);
1037 Mem.WriteInt(FY);
1038 Mem.WriteWord(FWidth);
1039 Mem.WriteWord(FHeight);
1040 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà
1041 if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
1042 begin
1043 assert(FTextureIDs[FCurTexture].AnTex <> nil, 'TPanel.SaveState: No animation object');
1044 anim := True;
1045 end
1046 else
1047 begin
1048 anim := False;
1049 end;
1050 Mem.WriteBoolean(anim);
1051 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ
1052 if anim then FTextureIDs[FCurTexture].AnTex.SaveState(Mem);
1054 // moving platform state
1055 Mem.WriteInt(mMovingSpeed.X);
1056 Mem.WriteInt(mMovingSpeed.Y);
1057 Mem.WriteInt(mMovingStart.X);
1058 Mem.WriteInt(mMovingStart.Y);
1059 Mem.WriteInt(mMovingEnd.X);
1060 Mem.WriteInt(mMovingEnd.Y);
1062 Mem.WriteInt(mSizeSpeed.w);
1063 Mem.WriteInt(mSizeSpeed.h);
1064 Mem.WriteInt(mSizeEnd.w);
1065 Mem.WriteInt(mSizeEnd.h);
1067 Mem.WriteBoolean(mMovingActive);
1068 Mem.WriteBoolean(mMoveOnce);
1070 Mem.WriteInt(mEndPosTrig);
1071 Mem.WriteInt(mEndSizeTrig);
1072 end;
1075 procedure TPanel.LoadState (var Mem: TBinMemoryReader);
1076 var
1077 sig: DWORD;
1078 anim: Boolean;
1079 ver: Byte;
1080 begin
1081 if (Mem = nil) then exit;
1083 // Ñèãíàòóðà ïàíåëè
1084 Mem.ReadDWORD(sig);
1085 if (sig <> PANEL_SIGNATURE) then raise EBinSizeError.Create('TPanel.LoadState: wrong panel signature'); // 'PANL'
1086 Mem.ReadByte(ver);
1087 if (ver <> PAN_SAVE_VERSION) then raise EBinSizeError.Create('TPanel.LoadState: invalid panel version');
1088 // Îòêðûòà/çàêðûòà, åñëè äâåðü
1089 Mem.ReadBoolean(FEnabled);
1090 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò
1091 Mem.ReadByte(FLiftType);
1092 // Íîìåð òåêóùåé òåêñòóðû
1093 Mem.ReadInt(FCurTexture);
1094 // Êîîðäû
1095 Mem.ReadInt(FX);
1096 Mem.ReadInt(FY);
1097 Mem.ReadWord(FWidth);
1098 Mem.ReadWord(FHeight);
1099 //e_LogWritefln('panel %s(%s): old=(%s,%s); new=(%s,%s); delta=(%s,%s)', [arrIdx, proxyId, ox, oy, FX, FY, FX-ox, FY-oy]);
1100 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà
1101 Mem.ReadBoolean(anim);
1102 // Åñëè äà - çàãðóæàåì àíèìàöèþ
1103 if anim then
1104 begin
1105 Assert((FCurTexture >= 0) and
1106 (FTextureIDs[FCurTexture].Anim) and
1107 (FTextureIDs[FCurTexture].AnTex <> nil),
1108 'TPanel.LoadState: No animation object');
1109 FTextureIDs[FCurTexture].AnTex.LoadState(Mem);
1110 end;
1112 // moving platform state
1113 Mem.ReadInt(mMovingSpeed.X);
1114 Mem.ReadInt(mMovingSpeed.Y);
1115 Mem.ReadInt(mMovingStart.X);
1116 Mem.ReadInt(mMovingStart.Y);
1117 Mem.ReadInt(mMovingEnd.X);
1118 Mem.ReadInt(mMovingEnd.Y);
1120 Mem.ReadInt(mSizeSpeed.w);
1121 Mem.ReadInt(mSizeSpeed.h);
1122 Mem.ReadInt(mSizeEnd.w);
1123 Mem.ReadInt(mSizeEnd.h);
1125 Mem.ReadBoolean(mMovingActive);
1126 Mem.ReadBoolean(mMoveOnce);
1128 Mem.ReadInt(mEndPosTrig);
1129 Mem.ReadInt(mEndSizeTrig);
1131 positionChanged();
1132 //mapGrid.proxyEnabled[proxyId] := FEnabled; // done in g_map.pas
1133 end;
1135 end.