DEADSOFTWARE

bye-bye, bineditor, we won't miss you
[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 SysUtils, Classes,
24 MAPDEF, g_textures, xdynrec;
26 type
27 TAddTextureArray = Array of
28 record
29 Texture: Cardinal;
30 Anim: Boolean;
31 end;
33 TPanel = Class (TObject)
34 private
35 const
36 private
37 mGUID: Integer; // will be assigned in "g_map.pas"
38 FTextureWidth: Word;
39 FTextureHeight: Word;
40 FAlpha: Byte;
41 FBlending: Boolean;
42 FTextureIDs: Array of
43 record
44 case Anim: Boolean of
45 False: (Tex: Cardinal);
46 True: (AnTex: TAnimation);
47 end;
49 mMovingSpeed: TDFPoint;
50 mMovingStart: TDFPoint;
51 mMovingEnd: TDFPoint;
52 mMovingActive: Boolean;
53 mMoveOnce: Boolean;
55 mOldMovingActive: Boolean;
57 mSizeSpeed: TDFSize;
58 mSizeEnd: TDFSize;
60 mEndPosTrig: Integer;
61 mEndSizeTrig: Integer;
63 mNeedSend: Boolean; // for network
65 private
66 function getx1 (): Integer; inline;
67 function gety1 (): Integer; inline;
68 function getvisvalid (): Boolean; inline;
70 function getMovingSpeedX (): Integer; inline;
71 procedure setMovingSpeedX (v: Integer); inline;
72 function getMovingSpeedY (): Integer; inline;
73 procedure setMovingSpeedY (v: Integer); inline;
75 function getMovingStartX (): Integer; inline;
76 procedure setMovingStartX (v: Integer); inline;
77 function getMovingStartY (): Integer; inline;
78 procedure setMovingStartY (v: Integer); inline;
80 function getMovingEndX (): Integer; inline;
81 procedure setMovingEndX (v: Integer); inline;
82 function getMovingEndY (): Integer; inline;
83 procedure setMovingEndY (v: Integer); inline;
85 function getSizeSpeedX (): Integer; inline;
86 procedure setSizeSpeedX (v: Integer); inline;
87 function getSizeSpeedY (): Integer; inline;
88 procedure setSizeSpeedY (v: Integer); inline;
90 function getSizeEndX (): Integer; inline;
91 procedure setSizeEndX (v: Integer); inline;
92 function getSizeEndY (): Integer; inline;
93 procedure setSizeEndY (v: Integer); inline;
95 public
96 FCurTexture: Integer; // Íîìåð òåêóùåé òåêñòóðû
97 FCurFrame: Integer;
98 FCurFrameCount: Byte;
99 FX, FY: Integer;
100 FWidth, FHeight: Word;
101 FPanelType: Word;
102 FEnabled: Boolean;
103 FDoor: Boolean;
104 FLiftType: Byte;
105 FLastAnimLoop: Byte;
106 // sorry, there fields are public to allow setting 'em in g_map; this should be fixed later
107 // for now, PLEASE, don't modify 'em, or all hell will break loose
108 arrIdx: Integer; // index in one of internal arrays; sorry
109 tag: Integer; // used in coldets and such; sorry; see g_map.GridTagXXX
110 proxyId: Integer; // proxy id in map grid (DO NOT USE!)
111 mapId: AnsiString; // taken directly from map file; dunno why it is here
113 constructor Create(PanelRec: TDynRecord;
114 AddTextures: TAddTextureArray;
115 CurTex: Integer;
116 var Textures: TLevelTextureArray; aguid: Integer);
117 destructor Destroy(); override;
119 procedure Draw (hasAmbient: Boolean; constref ambColor: TDFColor);
120 procedure DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
121 procedure Update();
122 procedure SetFrame(Frame: Integer; Count: Byte);
123 procedure NextTexture(AnimLoop: Byte = 0);
124 procedure SetTexture(ID: Integer; AnimLoop: Byte = 0);
125 function GetTextureID(): Cardinal;
126 function GetTextureCount(): Integer;
128 procedure SaveState (st: TStream);
129 procedure LoadState (st: TStream);
131 procedure positionChanged (); inline;
133 function getIsGBack (): Boolean; inline; // gRenderBackgrounds
134 function getIsGStep (): Boolean; inline; // gSteps
135 function getIsGWall (): Boolean; inline; // gWalls
136 function getIsGAcid1 (): Boolean; inline; // gAcid1
137 function getIsGAcid2 (): Boolean; inline; // gAcid2
138 function getIsGWater (): Boolean; inline; // gWater
139 function getIsGFore (): Boolean; inline; // gRenderForegrounds
140 function getIsGLift (): Boolean; inline; // gLifts
141 function getIsGBlockMon (): Boolean; inline; // gBlockMon
143 // get-and-clear
144 function gncNeedSend (): Boolean; inline;
145 procedure setDirty (); inline; // why `dirty`? 'cause i may introduce property `needSend` later
147 public
148 property visvalid: Boolean read getvisvalid; // panel is "visvalid" when it's width and height are positive
150 published
151 property guid: Integer read mGUID; // will be assigned in "g_map.pas"
152 property x0: Integer read FX;
153 property y0: Integer read FY;
154 property x1: Integer read getx1; // inclusive!
155 property y1: Integer read gety1; // inclusive!
156 property x: Integer read FX write FX;
157 property y: Integer read FY write FY;
158 property width: Word read FWidth write FWidth;
159 property height: Word read FHeight write FHeight;
160 property panelType: Word read FPanelType write FPanelType;
161 property enabled: Boolean read FEnabled write FEnabled;
162 property door: Boolean read FDoor write FDoor;
163 property liftType: Byte read FLiftType write FLiftType;
164 property lastAnimLoop: Byte read FLastAnimLoop write FLastAnimLoop;
166 property movingSpeedX: Integer read getMovingSpeedX write setMovingSpeedX;
167 property movingSpeedY: Integer read getMovingSpeedY write setMovingSpeedY;
168 property movingStartX: Integer read getMovingStartX write setMovingStartX;
169 property movingStartY: Integer read getMovingStartY write setMovingStartY;
170 property movingEndX: Integer read getMovingEndX write setMovingEndX;
171 property movingEndY: Integer read getMovingEndY write setMovingEndY;
172 property movingActive: Boolean read mMovingActive write mMovingActive;
173 property moveOnce: Boolean read mMoveOnce write mMoveOnce;
175 property sizeSpeedX: Integer read getSizeSpeedX write setSizeSpeedX;
176 property sizeSpeedY: Integer read getSizeSpeedY write setSizeSpeedY;
177 property sizeEndX: Integer read getSizeEndX write setSizeEndX;
178 property sizeEndY: Integer read getSizeEndY write setSizeEndY;
180 property isGBack: Boolean read getIsGBack;
181 property isGStep: Boolean read getIsGStep;
182 property isGWall: Boolean read getIsGWall;
183 property isGAcid1: Boolean read getIsGAcid1;
184 property isGAcid2: Boolean read getIsGAcid2;
185 property isGWater: Boolean read getIsGWater;
186 property isGFore: Boolean read getIsGFore;
187 property isGLift: Boolean read getIsGLift;
188 property isGBlockMon: Boolean read getIsGBlockMon;
190 public
191 property movingSpeed: TDFPoint read mMovingSpeed write mMovingSpeed;
192 property movingStart: TDFPoint read mMovingStart write mMovingStart;
193 property movingEnd: TDFPoint read mMovingEnd write mMovingEnd;
195 property sizeSpeed: TDFSize read mSizeSpeed write mSizeSpeed;
196 property sizeEnd: TDFSize read mSizeEnd write mSizeEnd;
198 property endPosTrigId: Integer read mEndPosTrig write mEndPosTrig;
199 property endSizeTrigId: Integer read mEndSizeTrig write mEndSizeTrig;
200 end;
202 TPanelArray = Array of TPanel;
204 var
205 g_dbgpan_mplat_active: Boolean = {$IF DEFINED(D2F_DEBUG)}true{$ELSE}true{$ENDIF};
206 g_dbgpan_mplat_step: Boolean = false; // one step, and stop
209 implementation
211 uses
212 e_texture, g_basic, g_map, g_game, g_gfx, e_graphics, g_weapons, g_triggers,
213 g_console, g_language, g_monsters, g_player, g_grid, e_log, GL, utils, xstreams;
215 const
216 PANEL_SIGNATURE = $4C4E4150; // 'PANL'
218 { T P a n e l : }
220 constructor TPanel.Create(PanelRec: TDynRecord;
221 AddTextures: TAddTextureArray;
222 CurTex: Integer;
223 var Textures: TLevelTextureArray; aguid: Integer);
224 var
225 i: Integer;
226 begin
227 X := PanelRec.X;
228 Y := PanelRec.Y;
229 Width := PanelRec.Width;
230 Height := PanelRec.Height;
231 FAlpha := 0;
232 FBlending := False;
233 FCurFrame := 0;
234 FCurFrameCount := 0;
235 LastAnimLoop := 0;
237 mapId := PanelRec.id;
238 mGUID := aguid;
240 mMovingSpeed := PanelRec.moveSpeed;
241 mMovingStart := PanelRec.moveStart;
242 mMovingEnd := PanelRec.moveEnd;
243 mMovingActive := PanelRec['move_active'].value;
244 mOldMovingActive := mMovingActive;
245 mMoveOnce := PanelRec.moveOnce;
247 mSizeSpeed := PanelRec.sizeSpeed;
248 mSizeEnd := PanelRec.sizeEnd;
250 mEndPosTrig := PanelRec.endPosTrig;
251 mEndSizeTrig := PanelRec.endSizeTrig;
253 mNeedSend := false;
255 // Òèï ïàíåëè:
256 PanelType := PanelRec.PanelType;
257 Enabled := True;
258 Door := False;
259 LiftType := 0;
261 case PanelType of
262 PANEL_OPENDOOR: begin Enabled := False; Door := True; end;
263 PANEL_CLOSEDOOR: Door := True;
264 PANEL_LIFTUP: LiftType := 0; //???
265 PANEL_LIFTDOWN: LiftType := 1;
266 PANEL_LIFTLEFT: LiftType := 2;
267 PANEL_LIFTRIGHT: LiftType := 3;
268 end;
270 // Íåâèäèìàÿ:
271 if ByteBool(PanelRec.Flags and PANEL_FLAG_HIDE) then
272 begin
273 SetLength(FTextureIDs, 0);
274 FCurTexture := -1;
275 Exit;
276 end;
277 // Ïàíåëè, íå èñïîëüçóþùèå òåêñòóðû:
278 if ByteBool(PanelType and
279 (PANEL_LIFTUP or
280 PANEL_LIFTDOWN or
281 PANEL_LIFTLEFT or
282 PANEL_LIFTRIGHT or
283 PANEL_BLOCKMON)) then
284 begin
285 SetLength(FTextureIDs, 0);
286 FCurTexture := -1;
287 Exit;
288 end;
290 // Åñëè ýòî æèäêîñòü áåç òåêñòóðû - ñïåöòåêñòóðó:
291 if WordBool(PanelType and (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2)) and
292 (not ByteBool(PanelRec.Flags and PANEL_FLAG_WATERTEXTURES)) then
293 begin
294 SetLength(FTextureIDs, 1);
295 FTextureIDs[0].Anim := False;
297 case PanelRec.PanelType of
298 PANEL_WATER:
299 FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_WATER);
300 PANEL_ACID1:
301 FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_ACID1);
302 PANEL_ACID2:
303 FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_ACID2);
304 end;
306 FCurTexture := 0;
307 Exit;
308 end;
310 SetLength(FTextureIDs, Length(AddTextures));
312 if CurTex < 0 then
313 FCurTexture := -1
314 else
315 if CurTex >= Length(FTextureIDs) then
316 FCurTexture := Length(FTextureIDs) - 1
317 else
318 FCurTexture := CurTex;
320 for i := 0 to Length(FTextureIDs)-1 do
321 begin
322 FTextureIDs[i].Anim := AddTextures[i].Anim;
323 if FTextureIDs[i].Anim then
324 begin // Àíèìèðîâàííàÿ òåêñòóðà
325 FTextureIDs[i].AnTex :=
326 TAnimation.Create(Textures[AddTextures[i].Texture].FramesID,
327 True, Textures[AddTextures[i].Texture].Speed);
328 FTextureIDs[i].AnTex.Blending := ByteBool(PanelRec.Flags and PANEL_FLAG_BLENDING);
329 FTextureIDs[i].AnTex.Alpha := PanelRec.Alpha;
330 end
331 else
332 begin // Îáû÷íàÿ òåêñòóðà
333 FTextureIDs[i].Tex := Textures[AddTextures[i].Texture].TextureID;
334 end;
335 end;
337 // Òåêñòóð íåñêîëüêî - íóæíî ñîõðàíÿòü òåêóùóþ:
338 //if Length(FTextureIDs) > 1 then SaveIt := True;
340 // Åñëè íå ñïåöòåêñòóðà, òî çàäàåì ðàçìåðû:
341 if PanelRec.TextureNum > High(Textures) then
342 begin
343 e_WriteLog(Format('WTF?! PanelRec.TextureNum is out of limits! (%d : %d)', [PanelRec.TextureNum, High(Textures)]), MSG_FATALERROR);
344 FTextureWidth := 2;
345 FTextureHeight := 2;
346 FAlpha := 0;
347 FBlending := ByteBool(0);
348 end
349 else if not g_Map_IsSpecialTexture(Textures[PanelRec.TextureNum].TextureName) then
350 begin
351 FTextureWidth := Textures[PanelRec.TextureNum].Width;
352 FTextureHeight := Textures[PanelRec.TextureNum].Height;
353 FAlpha := PanelRec.Alpha;
354 FBlending := ByteBool(PanelRec.Flags and PANEL_FLAG_BLENDING);
355 end;
356 end;
358 destructor TPanel.Destroy();
359 var
360 i: Integer;
361 begin
362 for i := 0 to High(FTextureIDs) do
363 if FTextureIDs[i].Anim then
364 FTextureIDs[i].AnTex.Free();
365 SetLength(FTextureIDs, 0);
367 Inherited;
368 end;
370 function TPanel.getx1 (): Integer; inline; begin result := X+Width-1; end;
371 function TPanel.gety1 (): Integer; inline; begin result := Y+Height-1; end;
372 function TPanel.getvisvalid (): Boolean; inline; begin result := (Width > 0) and (Height > 0); end;
374 function TPanel.getMovingSpeedX (): Integer; inline; begin result := mMovingSpeed.X; end;
375 procedure TPanel.setMovingSpeedX (v: Integer); inline; begin mMovingSpeed.X := v; end;
376 function TPanel.getMovingSpeedY (): Integer; inline; begin result := mMovingSpeed.Y; end;
377 procedure TPanel.setMovingSpeedY (v: Integer); inline; begin mMovingSpeed.Y := v; end;
379 function TPanel.getMovingStartX (): Integer; inline; begin result := mMovingStart.X; end;
380 procedure TPanel.setMovingStartX (v: Integer); inline; begin mMovingStart.X := v; end;
381 function TPanel.getMovingStartY (): Integer; inline; begin result := mMovingStart.Y; end;
382 procedure TPanel.setMovingStartY (v: Integer); inline; begin mMovingStart.Y := v; end;
384 function TPanel.getMovingEndX (): Integer; inline; begin result := mMovingEnd.X; end;
385 procedure TPanel.setMovingEndX (v: Integer); inline; begin mMovingEnd.X := v; end;
386 function TPanel.getMovingEndY (): Integer; inline; begin result := mMovingEnd.Y; end;
387 procedure TPanel.setMovingEndY (v: Integer); inline; begin mMovingEnd.Y := v; end;
389 function TPanel.getSizeSpeedX (): Integer; inline; begin result := mSizeSpeed.w; end;
390 procedure TPanel.setSizeSpeedX (v: Integer); inline; begin mSizeSpeed.w := v; end;
391 function TPanel.getSizeSpeedY (): Integer; inline; begin result := mSizeSpeed.h; end;
392 procedure TPanel.setSizeSpeedY (v: Integer); inline; begin mSizeSpeed.h := v; end;
394 function TPanel.getSizeEndX (): Integer; inline; begin result := mSizeEnd.w; end;
395 procedure TPanel.setSizeEndX (v: Integer); inline; begin mSizeEnd.w := v; end;
396 function TPanel.getSizeEndY (): Integer; inline; begin result := mSizeEnd.h; end;
397 procedure TPanel.setSizeEndY (v: Integer); inline; begin mSizeEnd.h := v; end;
399 function TPanel.getIsGBack (): Boolean; inline; begin result := ((tag and GridTagBack) <> 0); end;
400 function TPanel.getIsGStep (): Boolean; inline; begin result := ((tag and GridTagStep) <> 0); end;
401 function TPanel.getIsGWall (): Boolean; inline; begin result := ((tag and (GridTagWall or GridTagDoor)) <> 0); end;
402 function TPanel.getIsGAcid1 (): Boolean; inline; begin result := ((tag and GridTagAcid1) <> 0); end;
403 function TPanel.getIsGAcid2 (): Boolean; inline; begin result := ((tag and GridTagAcid2) <> 0); end;
404 function TPanel.getIsGWater (): Boolean; inline; begin result := ((tag and GridTagWater) <> 0); end;
405 function TPanel.getIsGFore (): Boolean; inline; begin result := ((tag and GridTagFore) <> 0); end;
406 function TPanel.getIsGLift (): Boolean; inline; begin result := ((tag and GridTagLift) <> 0); end;
407 function TPanel.getIsGBlockMon (): Boolean; inline; begin result := ((tag and GridTagBlockMon) <> 0); end;
409 function TPanel.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end;
410 procedure TPanel.setDirty (); inline; begin mNeedSend := true; end;
413 procedure TPanel.Draw (hasAmbient: Boolean; constref ambColor: TDFColor);
414 var
415 xx, yy: Integer;
416 NoTextureID: DWORD;
417 NW, NH: Word;
418 begin
419 if {Enabled and} (FCurTexture >= 0) and
420 (Width > 0) and (Height > 0) and (FAlpha < 255) {and
421 g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)} then
422 begin
423 if FTextureIDs[FCurTexture].Anim then
424 begin // Àíèìèðîâàííàÿ òåêñòóðà
425 if FTextureIDs[FCurTexture].AnTex = nil then
426 Exit;
428 for xx := 0 to (Width div FTextureWidth)-1 do
429 for yy := 0 to (Height div FTextureHeight)-1 do
430 FTextureIDs[FCurTexture].AnTex.Draw(
431 X + xx*FTextureWidth,
432 Y + yy*FTextureHeight, M_NONE);
433 end
434 else
435 begin // Îáû÷íàÿ òåêñòóðà
436 case FTextureIDs[FCurTexture].Tex of
437 LongWord(TEXTURE_SPECIAL_WATER): e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1, 0, 0, 255, 0, B_FILTER);
438 LongWord(TEXTURE_SPECIAL_ACID1): e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1, 0, 128, 0, 0, B_FILTER);
439 LongWord(TEXTURE_SPECIAL_ACID2): e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1, 128, 0, 0, 0, B_FILTER);
440 LongWord(TEXTURE_NONE):
441 if g_Texture_Get('NOTEXTURE', NoTextureID) then
442 begin
443 e_GetTextureSize(NoTextureID, @NW, @NH);
444 e_DrawFill(NoTextureID, X, Y, Width div NW, Height div NH, 0, False, False);
445 end
446 else
447 begin
448 xx := X + (Width div 2);
449 yy := Y + (Height div 2);
450 e_DrawFillQuad(X, Y, xx, yy, 255, 0, 255, 0);
451 e_DrawFillQuad(xx, Y, X+Width-1, yy, 255, 255, 0, 0);
452 e_DrawFillQuad(X, yy, xx, Y+Height-1, 255, 255, 0, 0);
453 e_DrawFillQuad(xx, yy, X+Width-1, Y+Height-1, 255, 0, 255, 0);
454 end;
455 else
456 begin
457 if not mMovingActive then
458 e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending, hasAmbient)
459 else
460 e_DrawFillX(FTextureIDs[FCurTexture].Tex, X, Y, Width, Height, FAlpha, True, FBlending, g_dbg_scale, hasAmbient);
461 if hasAmbient then e_AmbientQuad(X, Y, Width, Height, ambColor.r, ambColor.g, ambColor.b, ambColor.a);
462 end;
463 end;
464 end;
465 end;
466 end;
468 procedure TPanel.DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
469 procedure extrude (x: Integer; y: Integer);
470 begin
471 glVertex2i(x+(x-lightX)*500, y+(y-lightY)*500);
472 //e_WriteLog(Format(' : (%d,%d)', [x+(x-lightX)*300, y+(y-lightY)*300]), MSG_WARNING);
473 end;
475 procedure drawLine (x0: Integer; y0: Integer; x1: Integer; y1: Integer);
476 begin
477 // does this side facing the light?
478 if ((x1-x0)*(lightY-y0)-(lightX-x0)*(y1-y0) >= 0) then exit;
479 //e_WriteLog(Format('lightpan: (%d,%d)-(%d,%d)', [x0, y0, x1, y1]), MSG_WARNING);
480 // this edge is facing the light, extrude and draw it
481 glVertex2i(x0, y0);
482 glVertex2i(x1, y1);
483 extrude(x1, y1);
484 extrude(x0, y0);
485 end;
487 begin
488 if radius < 4 then exit;
489 if Enabled and (FCurTexture >= 0) and (Width > 0) and (Height > 0) and (FAlpha < 255) {and
490 g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)} then
491 begin
492 if not FTextureIDs[FCurTexture].Anim then
493 begin
494 case FTextureIDs[FCurTexture].Tex of
495 LongWord(TEXTURE_SPECIAL_WATER): exit;
496 LongWord(TEXTURE_SPECIAL_ACID1): exit;
497 LongWord(TEXTURE_SPECIAL_ACID2): exit;
498 LongWord(TEXTURE_NONE): exit;
499 end;
500 end;
501 if (X+Width < lightX-radius) then exit;
502 if (Y+Height < lightY-radius) then exit;
503 if (X > lightX+radius) then exit;
504 if (Y > lightY+radius) then exit;
505 //e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending);
507 glBegin(GL_QUADS);
508 drawLine(x, y, x+width, y); // top
509 drawLine(x+width, y, x+width, y+height); // right
510 drawLine(x+width, y+height, x, y+height); // bottom
511 drawLine(x, y+height, x, y); // left
512 glEnd();
513 end;
514 end;
517 procedure TPanel.positionChanged (); inline;
518 var
519 px, py, pw, ph: Integer;
520 begin
521 if (proxyId >= 0) then
522 begin
523 mapGrid.getBodyDims(proxyId, px, py, pw, ph);
524 if (px <> x) or (py <> y) or (pw <> Width) or (ph <> Height) then
525 begin
527 e_LogWritefln('panel moved: arridx=%s; guid=%s; proxyid=%s; old:(%s,%s)-(%sx%s); new:(%s,%s)-(%sx%s)',
528 [arrIdx, mGUID, proxyId, px, py, pw, ph, x, y, width, height]);
530 g_Mark(px, py, pw, ph, MARK_WALL, false);
531 if (Width < 1) or (Height < 1) then
532 begin
533 mapGrid.proxyEnabled[proxyId] := false;
534 end
535 else
536 begin
537 mapGrid.proxyEnabled[proxyId] := Enabled;
538 if (pw <> Width) or (ph <> Height) then
539 begin
540 //writeln('panel resize!');
541 mapGrid.moveResizeBody(proxyId, X, Y, Width, Height)
542 end
543 else
544 begin
545 mapGrid.moveBody(proxyId, X, Y);
546 end;
547 g_Mark(X, Y, Width, Height, MARK_WALL);
548 end;
549 end;
550 end;
551 end;
554 var
555 monCheckList: array of TMonster = nil;
556 monCheckListUsed: Integer = 0;
558 procedure TPanel.Update();
559 var
560 ox, oy: Integer;
561 nx, ny, nw, nh: Integer;
562 ex, ey, nex, ney: Integer;
563 mpw, mph: Integer;
565 // return `true` if we should move by dx,dy
566 function tryMPlatMove (px, py, pw, ph: Integer; out dx, dy: Integer; out squash: Boolean; ontop: PBoolean=nil): Boolean;
567 var
568 u0: Single;
569 tex, tey: Integer;
570 pdx, pdy: Integer;
571 trtag: Integer;
572 szdx, szdy: Integer;
573 begin
574 squash := false;
575 tex := px;
576 tey := py;
577 pdx := mMovingSpeed.X;
578 pdy := mMovingSpeed.Y;
579 // standing on the platform?
580 if (py+ph = oy) then
581 begin
582 if (ontop <> nil) then ontop^ := true;
583 // yes, move with it; but skip steps (no need to process size change here, 'cause platform top cannot be changed with it)
584 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, (GridTagWall or GridTagDoor));
585 end
586 else
587 begin
588 if (ontop <> nil) then ontop^ := false;
589 // not standing on the platform: trace platform to see if it hits the entity
590 // first, process size change (as we cannot sweeptest both move and size change)
591 // but we don't have to check for pushing if the panel is shrinking
592 szdx := nw-mpw;
593 szdy := nh-mph;
594 if (szdx > 0) or (szdy > 0) then
595 begin
596 // ignore shrinking dimension
597 if (szdx < 0) then szdx := 0;
598 if (szdy < 0) then szdy := 0;
599 // move platform by szd* back, and check for szd* movement
600 if sweepAABB(ox-szdx, oy-szdy, nw, nh, szdx, szdy, px, py, pw, ph, @u0) then
601 begin
602 // yes, platform hits the entity, push the entity in the resizing direction
603 u0 := 1.0-u0; // how much path left?
604 szdx := trunc(szdx*u0);
605 szdy := trunc(szdy*u0);
606 if (szdx <> 0) or (szdy <> 0) then
607 begin
608 // has some path to go, trace the entity
609 trtag := (GridTagWall or GridTagDoor);
610 // if we're moving down, consider steps too
611 if (szdy > 0) then trtag := trtag or GridTagStep;
612 mapGrid.traceBox(tex, tey, px, py, pw, ph, szdx, szdy, nil, trtag);
613 end;
614 end;
615 end;
616 // second, process platform movement, using te* as entity starting point
617 if sweepAABB(ox, oy, nw, nh, pdx, pdy, tex, tey, pw, ph, @u0) then
618 begin
619 //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]);
620 // yes, platform hits the entity, push the entity in the direction of the platform
621 u0 := 1.0-u0; // how much path left?
622 pdx := trunc(pdx*u0);
623 pdy := trunc(pdy*u0);
624 //e_LogWritefln(' platsweep; uleft=%s; pd=(%s,%s)', [u0, pdx, pdy]);
625 if (pdx <> 0) or (pdy <> 0) then
626 begin
627 // has some path to go, trace the entity
628 trtag := (GridTagWall or GridTagDoor);
629 // if we're moving down, consider steps too
630 if (pdy > 0) then trtag := trtag or GridTagStep;
631 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, trtag);
632 end;
633 end;
634 end;
635 // done with entity movement, new coords are in te*
636 dx := tex-px;
637 dy := tey-py;
638 result := (dx <> 0) or (dy <> 0);
639 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
640 begin
641 // check for squashing; as entity cannot be pushed into a wall, check only collision with the platform itself
642 squash := g_Collide(tex, tey, pw, ph, nx, ny, nw, nh); // squash, if still in platform
643 end;
644 end;
646 function monCollect (mon: TMonster): Boolean;
647 begin
648 result := false; // don't stop
649 if (monCheckListUsed >= Length(monCheckList)) then SetLength(monCheckList, monCheckListUsed+128);
650 monCheckList[monCheckListUsed] := mon;
651 Inc(monCheckListUsed);
652 end;
654 var
655 cx0, cy0, cx1, cy1, cw, ch: Integer;
656 f: Integer;
657 px, py, pw, ph, pdx, pdy: Integer;
658 squash: Boolean;
659 plr: TPlayer;
660 gib: PGib;
661 cor: TCorpse;
662 mon: TMonster;
663 mpfrid: LongWord;
664 ontop: Boolean;
665 actMoveTrig: Boolean;
666 actSizeTrig: Boolean;
667 begin
668 if (not Enabled) or (Width < 1) or (Height < 1) then exit;
670 if (FCurTexture >= 0) and
671 (FTextureIDs[FCurTexture].Anim) and
672 (FTextureIDs[FCurTexture].AnTex <> nil) and
673 (FAlpha < 255) then
674 begin
675 FTextureIDs[FCurTexture].AnTex.Update();
676 FCurFrame := FTextureIDs[FCurTexture].AnTex.CurrentFrame;
677 FCurFrameCount := FTextureIDs[FCurTexture].AnTex.CurrentCounter;
678 end;
680 if not g_dbgpan_mplat_active then exit;
682 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
683 mOldMovingActive := mMovingActive;
685 if not mMovingActive then exit;
686 if mMovingSpeed.isZero and mSizeSpeed.isZero then exit;
688 //TODO: write wall size change processing
690 // moving platform?
691 begin
692 (*
693 * collect all monsters and players (aka entities) along the possible platform path
694 * if entity is standing on a platform:
695 * try to move it along the platform path, checking wall collisions
696 * if entity is NOT standing on a platform, but hit with sweeped platform aabb:
697 * try to push entity
698 * if we can't push entity all the way, squash it
699 *)
700 ox := X;
701 oy := Y;
702 mpw := Width;
703 mph := Height;
705 nw := mpw+mSizeSpeed.w;
706 nh := mph+mSizeSpeed.h;
707 nx := ox+mMovingSpeed.X;
708 ny := oy+mMovingSpeed.Y;
710 // force network updates only if some sudden change happened
711 // set the flag here, so we can sync affected monsters
712 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
713 begin
714 mNeedSend := true;
715 end
716 else if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
717 begin
718 mNeedSend := true;
719 end
720 else if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
721 begin
722 mNeedSend := true;
723 end;
725 // if pannel disappeared, we don't have to do anything
726 if (nw > 0) and (nh > 0) then
727 begin
728 // old rect
729 ex := ox+mpw-1;
730 ey := ox+mph-1;
731 // new rect
732 nex := nx+nw-1;
733 ney := ny+nh-1;
734 // full rect
735 cx0 := nmin(ox, nx);
736 cy0 := nmin(oy, ny);
737 cx1 := nmax(ex, nex);
738 cy1 := nmax(ey, ney);
739 // extrude
740 cx0 -= 1;
741 cy0 -= 1;
742 cx1 += 1;
743 cy1 += 1;
744 cw := cx1-cx0+1;
745 ch := cy1-cy0+1;
747 // process "obstacle" panels
748 if ((tag and GridTagObstacle) <> 0) then
749 begin
750 // temporarily turn off this panel, so it won't interfere with collision checks
751 mapGrid.proxyEnabled[proxyId] := false;
753 // process players
754 for f := 0 to High(gPlayers) do
755 begin
756 plr := gPlayers[f];
757 if (plr = nil) or (not plr.alive) then continue;
758 plr.getMapBox(px, py, pw, ph);
759 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
760 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
761 begin
762 // set new position
763 plr.moveBy(pdx, pdy); // this will call `positionChanged()` for us
764 end;
765 // squash player, if necessary
766 if not g_Game_IsClient and squash then plr.Damage(15000, 0, 0, 0, HIT_TRAP);
767 end;
769 // process gibs
770 for f := 0 to High(gGibs) do
771 begin
772 gib := @gGibs[f];
773 if not gib.alive then continue;
774 gib.getMapBox(px, py, pw, ph);
775 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
776 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
777 begin
778 // set new position
779 gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
780 end;
781 end;
783 // move and push corpses
784 for f := 0 to High(gCorpses) do
785 begin
786 cor := gCorpses[f];
787 if (cor = nil) then continue;
788 cor.getMapBox(px, py, pw, ph);
789 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
790 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
791 begin
792 // set new position
793 cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
794 end;
795 end;
797 // collect monsters
798 monCheckListUsed := 0;
799 g_Mons_ForEachAt(cx0, cy0, cw, ch, monCollect);
801 // process collected monsters
802 if (monCheckListUsed > 0) then
803 begin
804 mpfrid := g_Mons_getNewMPlatFrameId();
805 for f := 0 to monCheckListUsed do
806 begin
807 mon := monCheckList[f];
808 if (mon = nil) or (not mon.alive) or (mon.mplatCheckFrameId = mpfrid) then continue;
809 mon.mplatCheckFrameId := mpfrid;
810 mon.getMapBox(px, py, pw, ph);
811 //if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
812 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
813 begin
814 // set new position
815 mon.moveBy(pdx, pdy); // this will call `positionChanged()` for us
816 //???FIXME: do we really need to send monsters over the net?
817 // i don't think so, as dead reckoning should take care of 'em
818 // ok, send new monster position only if platform is going to change it's direction
819 if mNeedSend then mon.setDirty();
820 end;
821 // squash monster, if necessary
822 if not g_Game_IsClient and squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
823 end;
824 end;
826 // restore panel state
827 mapGrid.proxyEnabled[proxyId] := true;
828 end;
829 end;
831 // move panel
832 X := nx;
833 Y := ny;
834 FWidth := nw;
835 FHeight := nh;
836 positionChanged();
838 actMoveTrig := false;
839 actSizeTrig := false;
841 // `mNeedSend` was set above
843 // check "size stop"
844 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
845 begin
846 mSizeSpeed.w := 0;
847 mSizeSpeed.h := 0;
848 actSizeTrig := true;
849 if (nw < 1) or (nh < 1) then mMovingActive := false; //HACK!
850 end;
852 // reverse moving direction, if necessary
853 if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
854 begin
855 if mMoveOnce then mMovingActive := false else mMovingSpeed.X := -mMovingSpeed.X;
856 actMoveTrig := true;
857 end;
859 if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
860 begin
861 if mMoveOnce then mMovingActive := false else mMovingSpeed.Y := -mMovingSpeed.Y;
862 actMoveTrig := true;
863 end;
865 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
866 mOldMovingActive := mMovingActive;
868 if not g_Game_IsClient then
869 begin
870 if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
871 if actSizeTrig then g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
872 end;
874 // some triggers may activate this, don't delay sending
875 //TODO: when triggers will be able to control speed and size, check that here too
876 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
877 mOldMovingActive := mMovingActive;
878 end;
879 end;
882 procedure TPanel.SetFrame(Frame: Integer; Count: Byte);
884 function ClampInt(X, A, B: Integer): Integer;
885 begin
886 Result := X;
887 if X < A then Result := A else if X > B then Result := B;
888 end;
890 begin
891 if Enabled and (FCurTexture >= 0) and
892 (FTextureIDs[FCurTexture].Anim) and
893 (FTextureIDs[FCurTexture].AnTex <> nil) and
894 (Width > 0) and (Height > 0) and (FAlpha < 255) then
895 begin
896 FCurFrame := ClampInt(Frame, 0, FTextureIDs[FCurTexture].AnTex.TotalFrames);
897 FCurFrameCount := Count;
898 FTextureIDs[FCurTexture].AnTex.CurrentFrame := FCurFrame;
899 FTextureIDs[FCurTexture].AnTex.CurrentCounter := FCurFrameCount;
900 end;
901 end;
903 procedure TPanel.NextTexture(AnimLoop: Byte = 0);
904 begin
905 Assert(FCurTexture >= -1, 'FCurTexture < -1');
907 // Íåò òåêñòóð:
908 if Length(FTextureIDs) = 0 then
909 FCurTexture := -1
910 else
911 // Òîëüêî îäíà òåêñòóðà:
912 if Length(FTextureIDs) = 1 then
913 begin
914 if FCurTexture = 0 then
915 FCurTexture := -1
916 else
917 FCurTexture := 0;
918 end
919 else
920 // Áîëüøå îäíîé òåêñòóðû:
921 begin
922 // Ñëåäóþùàÿ:
923 Inc(FCurTexture);
924 // Ñëåäóþùåé íåò - âîçâðàò ê íà÷àëó:
925 if FCurTexture >= Length(FTextureIDs) then
926 FCurTexture := 0;
927 end;
929 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
930 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
931 begin
932 if (FTextureIDs[FCurTexture].AnTex = nil) then
933 begin
934 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
935 Exit;
936 end;
938 if AnimLoop = 1 then
939 FTextureIDs[FCurTexture].AnTex.Loop := True
940 else
941 if AnimLoop = 2 then
942 FTextureIDs[FCurTexture].AnTex.Loop := False;
944 FTextureIDs[FCurTexture].AnTex.Reset();
945 end;
947 LastAnimLoop := AnimLoop;
948 end;
950 procedure TPanel.SetTexture(ID: Integer; AnimLoop: Byte = 0);
951 begin
952 // Íåò òåêñòóð:
953 if Length(FTextureIDs) = 0 then
954 FCurTexture := -1
955 else
956 // Òîëüêî îäíà òåêñòóðà:
957 if Length(FTextureIDs) = 1 then
958 begin
959 if (ID = 0) or (ID = -1) then
960 FCurTexture := ID;
961 end
962 else
963 // Áîëüøå îäíîé òåêñòóðû:
964 begin
965 if (ID >= -1) and (ID <= High(FTextureIDs)) then
966 FCurTexture := ID;
967 end;
969 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
970 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
971 begin
972 if (FTextureIDs[FCurTexture].AnTex = nil) then
973 begin
974 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
975 Exit;
976 end;
978 if AnimLoop = 1 then
979 FTextureIDs[FCurTexture].AnTex.Loop := True
980 else
981 if AnimLoop = 2 then
982 FTextureIDs[FCurTexture].AnTex.Loop := False;
984 FTextureIDs[FCurTexture].AnTex.Reset();
985 end;
987 LastAnimLoop := AnimLoop;
988 end;
990 function TPanel.GetTextureID(): DWORD;
991 begin
992 Result := LongWord(TEXTURE_NONE);
994 if (FCurTexture >= 0) then
995 begin
996 if FTextureIDs[FCurTexture].Anim then
997 Result := FTextureIDs[FCurTexture].AnTex.FramesID
998 else
999 Result := FTextureIDs[FCurTexture].Tex;
1000 end;
1001 end;
1003 function TPanel.GetTextureCount(): Integer;
1004 begin
1005 Result := Length(FTextureIDs);
1006 if Enabled and (FCurTexture >= 0) then
1007 if (FTextureIDs[FCurTexture].Anim) and
1008 (FTextureIDs[FCurTexture].AnTex <> nil) and
1009 (Width > 0) and (Height > 0) and (FAlpha < 255) then
1010 Result := Result + 100;
1011 end;
1014 const
1015 PAN_SAVE_VERSION = 1;
1017 procedure TPanel.SaveState (st: TStream);
1018 var
1019 anim: Boolean;
1020 begin
1021 if (st = nil) then exit;
1023 // Ñèãíàòóðà ïàíåëè
1024 utils.writeSign(st, 'PANL');
1025 utils.writeInt(st, Byte(PAN_SAVE_VERSION));
1026 // Îòêðûòà/çàêðûòà, åñëè äâåðü
1027 utils.writeBool(st, FEnabled);
1028 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò
1029 utils.writeInt(st, Byte(FLiftType));
1030 // Íîìåð òåêóùåé òåêñòóðû
1031 utils.writeInt(st, Integer(FCurTexture));
1032 // Êîîðäèíàòû è ðàçìåð
1033 utils.writeInt(st, Integer(FX));
1034 utils.writeInt(st, Integer(FY));
1035 utils.writeInt(st, Word(FWidth));
1036 utils.writeInt(st, Word(FHeight));
1037 // Àíèìèðîâàíà ëè òåêóùàÿ òåêñòóðà
1038 if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
1039 begin
1040 assert(FTextureIDs[FCurTexture].AnTex <> nil, 'TPanel.SaveState: No animation object');
1041 anim := true;
1042 end
1043 else
1044 begin
1045 anim := false;
1046 end;
1047 utils.writeBool(st, anim);
1048 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ
1049 if anim then FTextureIDs[FCurTexture].AnTex.SaveState(st);
1051 // moving platform state
1052 utils.writeInt(st, Integer(mMovingSpeed.X));
1053 utils.writeInt(st, Integer(mMovingSpeed.Y));
1054 utils.writeInt(st, Integer(mMovingStart.X));
1055 utils.writeInt(st, Integer(mMovingStart.Y));
1056 utils.writeInt(st, Integer(mMovingEnd.X));
1057 utils.writeInt(st, Integer(mMovingEnd.Y));
1059 utils.writeInt(st, Integer(mSizeSpeed.w));
1060 utils.writeInt(st, Integer(mSizeSpeed.h));
1061 utils.writeInt(st, Integer(mSizeEnd.w));
1062 utils.writeInt(st, Integer(mSizeEnd.h));
1064 utils.writeBool(st, mMovingActive);
1065 utils.writeBool(st, mMoveOnce);
1067 utils.writeInt(st, Integer(mEndPosTrig));
1068 utils.writeInt(st, Integer(mEndSizeTrig));
1069 end;
1072 procedure TPanel.LoadState (st: TStream);
1073 begin
1074 if (st = nil) then exit;
1076 // Ñèãíàòóðà ïàíåëè
1077 if not utils.checkSign(st, 'PANL') then raise XStreamError.create('wrong panel signature');
1078 if (utils.readByte(st) <> PAN_SAVE_VERSION) then raise XStreamError.create('wrong panel version');
1079 // Îòêðûòà/çàêðûòà, åñëè äâåðü
1080 FEnabled := utils.readBool(st);
1081 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò
1082 FLiftType := utils.readByte(st);
1083 // Íîìåð òåêóùåé òåêñòóðû
1084 FCurTexture := utils.readLongInt(st);
1085 // Êîîðäèíàòû è ðàçìåð
1086 FX := utils.readLongInt(st);
1087 FY := utils.readLongInt(st);
1088 FWidth := utils.readWord(st);
1089 FHeight := utils.readWord(st);
1090 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà
1091 if utils.readBool(st) then
1092 begin
1093 // Åñëè äà - çàãðóæàåì àíèìàöèþ
1094 Assert((FCurTexture >= 0) and
1095 (FTextureIDs[FCurTexture].Anim) and
1096 (FTextureIDs[FCurTexture].AnTex <> nil),
1097 'TPanel.LoadState: No animation object');
1098 FTextureIDs[FCurTexture].AnTex.LoadState(st);
1099 end;
1101 // moving platform state
1102 mMovingSpeed.X := utils.readLongInt(st);
1103 mMovingSpeed.Y := utils.readLongInt(st);
1104 mMovingStart.X := utils.readLongInt(st);
1105 mMovingStart.Y := utils.readLongInt(st);
1106 mMovingEnd.X := utils.readLongInt(st);
1107 mMovingEnd.Y := utils.readLongInt(st);
1109 mSizeSpeed.w := utils.readLongInt(st);
1110 mSizeSpeed.h := utils.readLongInt(st);
1111 mSizeEnd.w := utils.readLongInt(st);
1112 mSizeEnd.h := utils.readLongInt(st);
1114 mMovingActive := utils.readBool(st);
1115 mMoveOnce := utils.readBool(st);
1117 mEndPosTrig := utils.readLongInt(st);
1118 mEndSizeTrig := utils.readLongInt(st);
1120 positionChanged();
1121 //mapGrid.proxyEnabled[proxyId] := FEnabled; // done in g_map.pas
1122 end;
1125 end.