DEADSOFTWARE

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