DEADSOFTWARE

game: disable gibs for server
[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, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {$M+}
17 unit g_panel;
19 interface
21 uses
22 SysUtils, Classes,
23 MAPDEF, g_textures, xdynrec;
25 type
26 TAddTextureArray = array of record
27 Texture: Cardinal; // Textures[Texture]
28 end;
30 ATextureID = array of record
31 Texture: Cardinal; // Textures[Texture]
32 case Anim: Boolean of
33 False: ();
34 True: (AnTex: TAnimationState);
35 end;
37 PPanel = ^TPanel;
38 TPanel = Class (TObject)
39 private
40 const
41 private
42 mGUID: Integer; // will be assigned in "g_map.pas"
43 FAlpha: Byte;
44 FBlending: Boolean;
45 FTextureIDs: ATextureID;
46 mMovingSpeed: TDFPoint;
47 mMovingStart: TDFPoint;
48 mMovingEnd: TDFPoint;
49 mMovingActive: Boolean;
50 mMoveOnce: Boolean;
52 mOldMovingActive: Boolean;
54 mSizeSpeed: TDFSize;
55 mSizeEnd: TDFSize;
57 mEndPosTrig: Integer;
58 mEndSizeTrig: Integer;
60 mNeedSend: Boolean; // for network
62 private
63 function getx1 (): Integer; inline;
64 function gety1 (): Integer; inline;
65 function getvisvalid (): Boolean; inline;
67 function getMovingSpeedX (): Integer; inline;
68 procedure setMovingSpeedX (v: Integer); inline;
69 function getMovingSpeedY (): Integer; inline;
70 procedure setMovingSpeedY (v: Integer); inline;
72 function getMovingStartX (): Integer; inline;
73 procedure setMovingStartX (v: Integer); inline;
74 function getMovingStartY (): Integer; inline;
75 procedure setMovingStartY (v: Integer); inline;
77 function getMovingEndX (): Integer; inline;
78 procedure setMovingEndX (v: Integer); inline;
79 function getMovingEndY (): Integer; inline;
80 procedure setMovingEndY (v: Integer); inline;
82 function getSizeSpeedX (): Integer; inline;
83 procedure setSizeSpeedX (v: Integer); inline;
84 function getSizeSpeedY (): Integer; inline;
85 procedure setSizeSpeedY (v: Integer); inline;
87 function getSizeEndX (): Integer; inline;
88 procedure setSizeEndX (v: Integer); inline;
89 function getSizeEndY (): Integer; inline;
90 procedure setSizeEndY (v: Integer); inline;
92 public
93 FCurTexture: Integer; // Íîìåð òåêóùåé òåêñòóðû
94 FCurFrame: Integer;
95 FCurFrameCount: Byte;
96 FX, FY: Integer;
97 FWidth, FHeight: Word;
98 FPanelType: Word;
99 FEnabled: Boolean;
100 FDoor: Boolean;
101 FLiftType: Byte;
102 FLastAnimLoop: Byte;
103 // sorry, there fields are public to allow setting 'em in g_map; this should be fixed later
104 // for now, PLEASE, don't modify 'em, or all hell will break loose
105 arrIdx: Integer; // index in one of internal arrays; sorry
106 tag: Integer; // used in coldets and such; sorry; see g_map.GridTagXXX
107 proxyId: Integer; // proxy id in map grid (DO NOT USE!)
108 mapId: AnsiString; // taken directly from map file; dunno why it is here
109 hasTexTrigger: Boolean; // HACK: true when there's a trigger than can change my texture
111 constructor Create(PanelRec: TDynRecord;
112 AddTextures: TAddTextureArray;
113 CurTex: Integer;
114 var Textures: TLevelTextureArray; aguid: Integer);
115 destructor Destroy(); override;
117 procedure Update();
118 procedure SetFrame(Frame: Integer; Count: Byte);
119 procedure NextTexture(AnimLoop: Byte = 0);
120 procedure SetTexture(ID: Integer; AnimLoop: Byte = 0);
121 function GetTextureID(): Cardinal;
122 function GetTextureCount(): Integer;
123 function CanChangeTexture(): Boolean;
125 procedure SaveState (st: TStream);
126 procedure LoadState (st: TStream);
128 procedure positionChanged (); inline;
130 function getIsGBack (): Boolean; inline; // gRenderBackgrounds
131 function getIsGStep (): Boolean; inline; // gSteps
132 function getIsGWall (): Boolean; inline; // gWalls
133 function getIsGAcid1 (): Boolean; inline; // gAcid1
134 function getIsGAcid2 (): Boolean; inline; // gAcid2
135 function getIsGWater (): Boolean; inline; // gWater
136 function getIsGFore (): Boolean; inline; // gRenderForegrounds
137 function getIsGLift (): Boolean; inline; // gLifts
138 function getIsGBlockMon (): Boolean; inline; // gBlockMon
140 // get-and-clear
141 function gncNeedSend (): Boolean; inline;
142 procedure setDirty (); inline; // why `dirty`? 'cause i may introduce property `needSend` later
144 public
145 property visvalid: Boolean read getvisvalid; // panel is "visvalid" when it's width and height are positive
147 published
148 property guid: Integer read mGUID; // will be assigned in "g_map.pas"
149 property x0: Integer read FX;
150 property y0: Integer read FY;
151 property x1: Integer read getx1; // inclusive!
152 property y1: Integer read gety1; // inclusive!
153 property x: Integer read FX write FX;
154 property y: Integer read FY write FY;
155 property width: Word read FWidth write FWidth;
156 property height: Word read FHeight write FHeight;
157 property panelType: Word read FPanelType write FPanelType;
158 property enabled: Boolean read FEnabled write FEnabled;
159 property door: Boolean read FDoor write FDoor;
160 property liftType: Byte read FLiftType write FLiftType;
161 property lastAnimLoop: Byte read FLastAnimLoop write FLastAnimLoop;
163 property movingSpeedX: Integer read getMovingSpeedX write setMovingSpeedX;
164 property movingSpeedY: Integer read getMovingSpeedY write setMovingSpeedY;
165 property movingStartX: Integer read getMovingStartX write setMovingStartX;
166 property movingStartY: Integer read getMovingStartY write setMovingStartY;
167 property movingEndX: Integer read getMovingEndX write setMovingEndX;
168 property movingEndY: Integer read getMovingEndY write setMovingEndY;
169 property movingActive: Boolean read mMovingActive write mMovingActive;
170 property moveOnce: Boolean read mMoveOnce write mMoveOnce;
172 property sizeSpeedX: Integer read getSizeSpeedX write setSizeSpeedX;
173 property sizeSpeedY: Integer read getSizeSpeedY write setSizeSpeedY;
174 property sizeEndX: Integer read getSizeEndX write setSizeEndX;
175 property sizeEndY: Integer read getSizeEndY write setSizeEndY;
177 property isGBack: Boolean read getIsGBack;
178 property isGStep: Boolean read getIsGStep;
179 property isGWall: Boolean read getIsGWall;
180 property isGAcid1: Boolean read getIsGAcid1;
181 property isGAcid2: Boolean read getIsGAcid2;
182 property isGWater: Boolean read getIsGWater;
183 property isGFore: Boolean read getIsGFore;
184 property isGLift: Boolean read getIsGLift;
185 property isGBlockMon: Boolean read getIsGBlockMon;
187 (* private state *)
188 property Alpha: Byte read FAlpha;
189 property Blending: Boolean read FBlending;
190 property TextureIDs: ATextureID read FTextureIDs;
192 public
193 property movingSpeed: TDFPoint read mMovingSpeed write mMovingSpeed;
194 property movingStart: TDFPoint read mMovingStart write mMovingStart;
195 property movingEnd: TDFPoint read mMovingEnd write mMovingEnd;
197 property sizeSpeed: TDFSize read mSizeSpeed write mSizeSpeed;
198 property sizeEnd: TDFSize read mSizeEnd write mSizeEnd;
200 property endPosTrigId: Integer read mEndPosTrig write mEndPosTrig;
201 property endSizeTrigId: Integer read mEndSizeTrig write mEndSizeTrig;
202 end;
204 TPanelArray = Array of TPanel;
206 const
207 LIFTTYPE_UP = 0;
208 LIFTTYPE_DOWN = 1;
209 LIFTTYPE_LEFT = 2;
210 LIFTTYPE_RIGHT = 3;
212 var
213 g_dbgpan_mplat_active: Boolean = {$IF DEFINED(D2F_DEBUG)}true{$ELSE}true{$ENDIF};
214 g_dbgpan_mplat_step: Boolean = false; // one step, and stop
217 implementation
219 uses
220 {$IFDEF ENABLE_GFX}
221 g_gfx,
222 {$ENDIF}
223 {$IFDEF ENABLE_GIBS}
224 g_gibs,
225 {$ENDIF}
226 g_basic, g_map, g_game, g_weapons, g_triggers,
227 g_console, g_language, g_monsters, g_player, g_grid, e_log, geom, utils, xstreams
230 const
231 PANEL_SIGNATURE = $4C4E4150; // 'PANL'
233 { T P a n e l : }
235 function FindTextureByName (const name: String): Integer;
236 var i: Integer;
237 begin
238 Result := -1;
239 if Textures <> nil then
240 begin
241 for i := 0 to High(Textures) do
242 begin
243 if Textures[i].TextureName = name then
244 begin
245 Result := i;
246 break;
247 end
248 end
249 end
250 end;
252 constructor TPanel.Create(PanelRec: TDynRecord;
253 AddTextures: TAddTextureArray;
254 CurTex: Integer;
255 var Textures: TLevelTextureArray; aguid: Integer);
256 var
257 i: Integer;
258 tnum: Integer;
259 begin
260 X := PanelRec.X;
261 Y := PanelRec.Y;
262 Width := PanelRec.Width;
263 Height := PanelRec.Height;
264 FAlpha := 0;
265 FBlending := False;
266 FCurFrame := 0;
267 FCurFrameCount := 0;
268 LastAnimLoop := 0;
270 mapId := PanelRec.id;
271 mGUID := aguid;
273 mMovingSpeed := PanelRec.moveSpeed;
274 mMovingStart := PanelRec.moveStart;
275 mMovingEnd := PanelRec.moveEnd;
276 mMovingActive := PanelRec['move_active'].value;
277 mOldMovingActive := mMovingActive;
278 mMoveOnce := PanelRec.moveOnce;
280 mSizeSpeed := PanelRec.sizeSpeed;
281 mSizeEnd := PanelRec.sizeEnd;
283 mEndPosTrig := PanelRec.endPosTrig;
284 mEndSizeTrig := PanelRec.endSizeTrig;
286 mNeedSend := false;
288 // Òèï ïàíåëè:
289 PanelType := PanelRec.PanelType;
290 Enabled := True;
291 Door := False;
292 LiftType := LIFTTYPE_UP;
293 hasTexTrigger := False;
295 case PanelType of
296 PANEL_OPENDOOR: begin Enabled := False; Door := True; end;
297 PANEL_CLOSEDOOR: Door := True;
298 PANEL_LIFTUP: LiftType := LIFTTYPE_UP; //???
299 PANEL_LIFTDOWN: LiftType := LIFTTYPE_DOWN;
300 PANEL_LIFTLEFT: LiftType := LIFTTYPE_LEFT;
301 PANEL_LIFTRIGHT: LiftType := LIFTTYPE_RIGHT;
302 end;
304 // Íåâèäèìàÿ:
305 if ByteBool(PanelRec.Flags and PANEL_FLAG_HIDE) then
306 begin
307 SetLength(FTextureIDs, 0);
308 FCurTexture := -1;
309 Exit;
310 end;
311 // Ïàíåëè, íå èñïîëüçóþùèå òåêñòóðû:
312 if ByteBool(PanelType and
313 (PANEL_LIFTUP or
314 PANEL_LIFTDOWN or
315 PANEL_LIFTLEFT or
316 PANEL_LIFTRIGHT or
317 PANEL_BLOCKMON)) then
318 begin
319 SetLength(FTextureIDs, 0);
320 FCurTexture := -1;
321 Exit;
322 end;
324 // Åñëè ýòî æèäêîñòü áåç òåêñòóðû - ñïåöòåêñòóðó:
325 if WordBool(PanelType and (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2)) and
326 (not ByteBool(PanelRec.Flags and PANEL_FLAG_WATERTEXTURES)) then
327 begin
328 SetLength(FTextureIDs, 1);
329 FTextureIDs[0].Anim := False;
330 case PanelRec.PanelType of
331 PANEL_WATER: FTextureIDs[0].Texture := FindTextureByName(TEXTURE_NAME_WATER);
332 PANEL_ACID1: FTextureIDs[0].Texture := FindTextureByName(TEXTURE_NAME_ACID1);
333 PANEL_ACID2: FTextureIDs[0].Texture := FindTextureByName(TEXTURE_NAME_ACID2);
334 end;
335 FCurTexture := 0;
336 Exit;
337 end;
339 SetLength(FTextureIDs, Length(AddTextures));
341 if CurTex < 0 then
342 FCurTexture := -1
343 else
344 if CurTex >= Length(FTextureIDs) then
345 FCurTexture := Length(FTextureIDs) - 1
346 else
347 FCurTexture := CurTex;
349 for i := 0 to Length(FTextureIDs)-1 do
350 begin
351 FTextureIDs[i].Texture := AddTextures[i].Texture;
352 FTextureIDs[i].Anim := Textures[AddTextures[i].Texture].FramesCount > 0;
353 if FTextureIDs[i].Anim then
354 begin // Àíèìèðîâàííàÿ òåêñòóðà
355 FTextureIDs[i].AnTex := TAnimationState.Create(True, Textures[AddTextures[i].Texture].Speed, Textures[AddTextures[i].Texture].FramesCount);
356 end
357 end;
359 // Òåêñòóð íåñêîëüêî - íóæíî ñîõðàíÿòü òåêóùóþ:
360 //if Length(FTextureIDs) > 1 then SaveIt := True;
362 if (PanelRec.TextureRec = nil) then tnum := -1 else tnum := PanelRec.tagInt;
363 if (tnum < 0) then tnum := Length(Textures);
365 // Åñëè íå ñïåöòåêñòóðà, òî çàäàåì ðàçìåðû:
366 if ({PanelRec.TextureNum}tnum > High(Textures)) then
367 begin
368 e_WriteLog(Format('WTF?! tnum is out of limits! (%d : %d)', [tnum, High(Textures)]), TMsgType.Warning);
369 FAlpha := 0;
370 FBlending := ByteBool(0);
371 end
372 else if not g_Map_IsSpecialTexture(Textures[{PanelRec.TextureNum}tnum].TextureName) then
373 begin
374 FAlpha := PanelRec.Alpha;
375 FBlending := ByteBool(PanelRec.Flags and PANEL_FLAG_BLENDING);
376 end;
377 end;
379 destructor TPanel.Destroy();
380 var
381 i: Integer;
382 begin
383 for i := 0 to High(FTextureIDs) do
384 if FTextureIDs[i].Anim then
385 FTextureIDs[i].AnTex.Free();
386 SetLength(FTextureIDs, 0);
388 Inherited;
389 end;
391 function TPanel.getx1 (): Integer; inline; begin result := X+Width-1; end;
392 function TPanel.gety1 (): Integer; inline; begin result := Y+Height-1; end;
393 function TPanel.getvisvalid (): Boolean; inline; begin result := (Width > 0) and (Height > 0); end;
395 function TPanel.getMovingSpeedX (): Integer; inline; begin result := mMovingSpeed.X; end;
396 procedure TPanel.setMovingSpeedX (v: Integer); inline; begin mMovingSpeed.X := v; end;
397 function TPanel.getMovingSpeedY (): Integer; inline; begin result := mMovingSpeed.Y; end;
398 procedure TPanel.setMovingSpeedY (v: Integer); inline; begin mMovingSpeed.Y := v; end;
400 function TPanel.getMovingStartX (): Integer; inline; begin result := mMovingStart.X; end;
401 procedure TPanel.setMovingStartX (v: Integer); inline; begin mMovingStart.X := v; end;
402 function TPanel.getMovingStartY (): Integer; inline; begin result := mMovingStart.Y; end;
403 procedure TPanel.setMovingStartY (v: Integer); inline; begin mMovingStart.Y := v; end;
405 function TPanel.getMovingEndX (): Integer; inline; begin result := mMovingEnd.X; end;
406 procedure TPanel.setMovingEndX (v: Integer); inline; begin mMovingEnd.X := v; end;
407 function TPanel.getMovingEndY (): Integer; inline; begin result := mMovingEnd.Y; end;
408 procedure TPanel.setMovingEndY (v: Integer); inline; begin mMovingEnd.Y := v; end;
410 function TPanel.getSizeSpeedX (): Integer; inline; begin result := mSizeSpeed.w; end;
411 procedure TPanel.setSizeSpeedX (v: Integer); inline; begin mSizeSpeed.w := v; end;
412 function TPanel.getSizeSpeedY (): Integer; inline; begin result := mSizeSpeed.h; end;
413 procedure TPanel.setSizeSpeedY (v: Integer); inline; begin mSizeSpeed.h := v; end;
415 function TPanel.getSizeEndX (): Integer; inline; begin result := mSizeEnd.w; end;
416 procedure TPanel.setSizeEndX (v: Integer); inline; begin mSizeEnd.w := v; end;
417 function TPanel.getSizeEndY (): Integer; inline; begin result := mSizeEnd.h; end;
418 procedure TPanel.setSizeEndY (v: Integer); inline; begin mSizeEnd.h := v; end;
420 function TPanel.getIsGBack (): Boolean; inline; begin result := ((tag and GridTagBack) <> 0); end;
421 function TPanel.getIsGStep (): Boolean; inline; begin result := ((tag and GridTagStep) <> 0); end;
422 function TPanel.getIsGWall (): Boolean; inline; begin result := ((tag and (GridTagWall or GridTagDoor)) <> 0); end;
423 function TPanel.getIsGAcid1 (): Boolean; inline; begin result := ((tag and GridTagAcid1) <> 0); end;
424 function TPanel.getIsGAcid2 (): Boolean; inline; begin result := ((tag and GridTagAcid2) <> 0); end;
425 function TPanel.getIsGWater (): Boolean; inline; begin result := ((tag and GridTagWater) <> 0); end;
426 function TPanel.getIsGFore (): Boolean; inline; begin result := ((tag and GridTagFore) <> 0); end;
427 function TPanel.getIsGLift (): Boolean; inline; begin result := ((tag and GridTagLift) <> 0); end;
428 function TPanel.getIsGBlockMon (): Boolean; inline; begin result := ((tag and GridTagBlockMon) <> 0); end;
430 function TPanel.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end;
431 procedure TPanel.setDirty (); inline; begin mNeedSend := true; end;
433 procedure TPanel.positionChanged (); inline;
434 var
435 px, py, pw, ph: Integer;
436 begin
437 if (proxyId >= 0) then
438 begin
439 mapGrid.getBodyDims(proxyId, px, py, pw, ph);
440 if (px <> x) or (py <> y) or (pw <> Width) or (ph <> Height) then
441 begin
443 e_LogWritefln('panel moved: arridx=%s; guid=%s; proxyid=%s; old:(%s,%s)-(%sx%s); new:(%s,%s)-(%sx%s)',
444 [arrIdx, mGUID, proxyId, px, py, pw, ph, x, y, width, height]);
446 {$IFDEF ENABLE_GFX}
447 g_Mark(px, py, pw, ph, MARK_WALL, false);
448 {$ENDIF}
449 if (Width < 1) or (Height < 1) then
450 begin
451 mapGrid.proxyEnabled[proxyId] := false;
452 end
453 else
454 begin
455 mapGrid.proxyEnabled[proxyId] := Enabled;
456 if (pw <> Width) or (ph <> Height) then
457 begin
458 //writeln('panel resize!');
459 mapGrid.moveResizeBody(proxyId, X, Y, Width, Height)
460 end
461 else
462 begin
463 mapGrid.moveBody(proxyId, X, Y);
464 end;
465 {$IFDEF ENABLE_GFX}
466 g_Mark(X, Y, Width, Height, MARK_WALL);
467 {$ENDIF}
468 end;
469 end;
470 end;
471 end;
474 var
475 monCheckList: array of TMonster = nil;
476 monCheckListUsed: Integer = 0;
478 procedure TPanel.Update();
479 var
480 ox, oy: Integer;
481 nx, ny, nw, nh: Integer;
482 ex, ey, nex, ney: Integer;
483 mpw, mph: Integer;
485 // return `true` if we should move by dx,dy
486 function tryMPlatMove (px, py, pw, ph: Integer; out dx, dy: Integer; out squash: Boolean; ontop: PBoolean=nil): Boolean;
487 var
488 u0: Single;
489 tex, tey: Integer;
490 pdx, pdy: Integer;
491 trtag: Integer;
492 szdx, szdy: Integer;
493 begin
494 squash := false;
495 tex := px;
496 tey := py;
497 pdx := mMovingSpeed.X;
498 pdy := mMovingSpeed.Y;
499 // standing on the platform?
500 if (py+ph = oy) then
501 begin
502 if (ontop <> nil) then ontop^ := true;
503 // yes, move with it; but skip steps (no need to process size change here, 'cause platform top cannot be changed with it)
504 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, (GridTagWall or GridTagDoor));
505 end
506 else
507 begin
508 if (ontop <> nil) then ontop^ := false;
509 // not standing on the platform: trace platform to see if it hits the entity
510 // first, process size change (as we cannot sweeptest both move and size change)
511 // but we don't have to check for pushing if the panel is shrinking
512 szdx := nw-mpw;
513 szdy := nh-mph;
514 if (szdx > 0) or (szdy > 0) then
515 begin
516 // ignore shrinking dimension
517 if (szdx < 0) then szdx := 0;
518 if (szdy < 0) then szdy := 0;
519 // move platform by szd* back, and check for szd* movement
520 if sweepAABB(ox-szdx, oy-szdy, nw, nh, szdx, szdy, px, py, pw, ph, @u0) then
521 begin
522 // yes, platform hits the entity, push the entity in the resizing direction
523 u0 := 1.0-u0; // how much path left?
524 szdx := trunc(szdx*u0);
525 szdy := trunc(szdy*u0);
526 if (szdx <> 0) or (szdy <> 0) then
527 begin
528 // has some path to go, trace the entity
529 trtag := (GridTagWall or GridTagDoor);
530 // if we're moving down, consider steps too
531 if (szdy > 0) then trtag := trtag or GridTagStep;
532 mapGrid.traceBox(tex, tey, px, py, pw, ph, szdx, szdy, trtag);
533 end;
534 end;
535 end;
536 // second, process platform movement, using te* as entity starting point
537 if sweepAABB(ox, oy, nw, nh, pdx, pdy, tex, tey, pw, ph, @u0) then
538 begin
539 //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]);
540 // yes, platform hits the entity, push the entity in the direction of the platform
541 u0 := 1.0-u0; // how much path left?
542 pdx := trunc(pdx*u0);
543 pdy := trunc(pdy*u0);
544 //e_LogWritefln(' platsweep; uleft=%s; pd=(%s,%s)', [u0, pdx, pdy]);
545 if (pdx <> 0) or (pdy <> 0) then
546 begin
547 // has some path to go, trace the entity
548 trtag := (GridTagWall or GridTagDoor);
549 // if we're moving down, consider steps too
550 if (pdy > 0) then trtag := trtag or GridTagStep;
551 mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, trtag);
552 end;
553 end;
554 end;
555 // done with entity movement, new coords are in te*
556 dx := tex-px;
557 dy := tey-py;
558 result := (dx <> 0) or (dy <> 0);
559 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
560 begin
561 // check for squashing; as entity cannot be pushed into a wall, check only collision with the platform itself
562 squash := g_Collide(tex, tey, pw, ph, nx, ny, nw, nh); // squash, if still in platform
563 end;
564 end;
566 function monCollect (mon: TMonster): Boolean;
567 begin
568 result := false; // don't stop
569 if (monCheckListUsed >= Length(monCheckList)) then SetLength(monCheckList, monCheckListUsed+128);
570 monCheckList[monCheckListUsed] := mon;
571 Inc(monCheckListUsed);
572 end;
574 var
575 cx0, cy0, cx1, cy1, cw, ch: Integer;
576 f: Integer;
577 px, py, pw, ph, pdx, pdy: Integer;
578 squash: Boolean;
579 plr: TPlayer;
580 {$IFDEF ENABLE_GIBS}
581 gib: PGib;
582 {$ENDIF}
583 cor: TCorpse;
584 mon: TMonster;
585 mpfrid: LongWord;
586 ontop: Boolean;
587 actMoveTrig: Boolean;
588 actSizeTrig: Boolean;
589 begin
590 if (not Enabled) or (Width < 1) or (Height < 1) then exit;
592 if (FCurTexture >= 0) and
593 (FTextureIDs[FCurTexture].Anim) and
594 (FTextureIDs[FCurTexture].AnTex <> nil) and
595 (FAlpha < 255) then
596 begin
597 FTextureIDs[FCurTexture].AnTex.Update();
598 FCurFrame := FTextureIDs[FCurTexture].AnTex.CurrentFrame;
599 FCurFrameCount := FTextureIDs[FCurTexture].AnTex.CurrentCounter;
600 end;
602 if not g_dbgpan_mplat_active then exit;
604 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
605 mOldMovingActive := mMovingActive;
607 if not mMovingActive then exit;
608 if mMovingSpeed.isZero and mSizeSpeed.isZero then exit;
610 //TODO: write wall size change processing
612 // moving platform?
613 begin
614 (*
615 * collect all monsters and players (aka entities) along the possible platform path
616 * if entity is standing on a platform:
617 * try to move it along the platform path, checking wall collisions
618 * if entity is NOT standing on a platform, but hit with sweeped platform aabb:
619 * try to push entity
620 * if we can't push entity all the way, squash it
621 *)
622 ox := X;
623 oy := Y;
624 mpw := Width;
625 mph := Height;
627 nw := mpw+mSizeSpeed.w;
628 nh := mph+mSizeSpeed.h;
629 nx := ox+mMovingSpeed.X;
630 ny := oy+mMovingSpeed.Y;
632 // force network updates only if some sudden change happened
633 // set the flag here, so we can sync affected monsters
634 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
635 begin
636 mNeedSend := true;
637 end
638 else if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
639 begin
640 mNeedSend := true;
641 end
642 else if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
643 begin
644 mNeedSend := true;
645 end;
647 // if pannel disappeared, we don't have to do anything
648 if (nw > 0) and (nh > 0) then
649 begin
650 // old rect
651 ex := ox+mpw-1;
652 ey := ox+mph-1;
653 // new rect
654 nex := nx+nw-1;
655 ney := ny+nh-1;
656 // full rect
657 cx0 := nmin(ox, nx);
658 cy0 := nmin(oy, ny);
659 cx1 := nmax(ex, nex);
660 cy1 := nmax(ey, ney);
661 // extrude
662 cx0 -= 1;
663 cy0 -= 1;
664 cx1 += 1;
665 cy1 += 1;
666 cw := cx1-cx0+1;
667 ch := cy1-cy0+1;
669 // process "obstacle" panels
670 if ((tag and GridTagObstacle) <> 0) then
671 begin
672 // temporarily turn off this panel, so it won't interfere with collision checks
673 mapGrid.proxyEnabled[proxyId] := false;
675 // process players
676 for f := 0 to High(gPlayers) do
677 begin
678 plr := gPlayers[f];
679 if (plr = nil) or (not plr.alive) then continue;
680 plr.getMapBox(px, py, pw, ph);
681 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
682 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
683 begin
684 // set new position
685 plr.moveBy(pdx, pdy); // this will call `positionChanged()` for us
686 end;
687 // squash player, if necessary
688 if not g_Game_IsClient and squash then plr.Damage(15000, 0, 0, 0, HIT_TRAP);
689 end;
691 {$IFDEF ENABLE_GIBS}
692 // process gibs
693 for f := 0 to High(gGibs) do
694 begin
695 gib := @gGibs[f];
696 if not gib.alive then continue;
697 gib.getMapBox(px, py, pw, ph);
698 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
699 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
700 begin
701 // set new position
702 gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
703 end;
704 end;
705 {$ENDIF}
707 // move and push corpses
708 for f := 0 to High(gCorpses) do
709 begin
710 cor := gCorpses[f];
711 if (cor = nil) then continue;
712 cor.getMapBox(px, py, pw, ph);
713 if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
714 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
715 begin
716 // set new position
717 cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
718 end;
719 end;
721 // collect monsters
722 monCheckListUsed := 0;
723 g_Mons_ForEachAt(cx0, cy0, cw, ch, monCollect);
725 // process collected monsters
726 if (monCheckListUsed > 0) then
727 begin
728 mpfrid := g_Mons_getNewMPlatFrameId();
729 for f := 0 to monCheckListUsed do
730 begin
731 mon := monCheckList[f];
732 if (mon = nil) or (not mon.alive) or (mon.mplatCheckFrameId = mpfrid) then continue;
733 mon.mplatCheckFrameId := mpfrid;
734 mon.getMapBox(px, py, pw, ph);
735 //if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
736 if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
737 begin
738 // set new position
739 mon.moveBy(pdx, pdy); // this will call `positionChanged()` for us
740 //???FIXME: do we really need to send monsters over the net?
741 // i don't think so, as dead reckoning should take care of 'em
742 // ok, send new monster position only if platform is going to change it's direction
743 if mNeedSend then mon.setDirty();
744 end;
745 // squash monster, if necessary
746 if not g_Game_IsClient and squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
747 end;
748 end;
750 // restore panel state
751 mapGrid.proxyEnabled[proxyId] := true;
752 end;
753 end;
755 // move panel
756 X := nx;
757 Y := ny;
758 FWidth := nw;
759 FHeight := nh;
760 positionChanged();
762 actMoveTrig := false;
763 actSizeTrig := false;
765 // `mNeedSend` was set above
767 // check "size stop"
768 if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
769 begin
770 mSizeSpeed.w := 0;
771 mSizeSpeed.h := 0;
772 actSizeTrig := true;
773 if (nw < 1) or (nh < 1) then mMovingActive := false; //HACK!
774 end;
776 // reverse moving direction, if necessary
777 if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
778 begin
779 if mMoveOnce then mMovingActive := false else mMovingSpeed.X := -mMovingSpeed.X;
780 actMoveTrig := true;
781 end;
783 if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
784 begin
785 if mMoveOnce then mMovingActive := false else mMovingSpeed.Y := -mMovingSpeed.Y;
786 actMoveTrig := true;
787 end;
789 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
790 mOldMovingActive := mMovingActive;
792 if not g_Game_IsClient then
793 begin
794 if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
795 if actSizeTrig then g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
796 end;
798 // some triggers may activate this, don't delay sending
799 //TODO: when triggers will be able to control speed and size, check that here too
800 if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
801 mOldMovingActive := mMovingActive;
802 end;
803 end;
806 procedure TPanel.SetFrame(Frame: Integer; Count: Byte);
808 function ClampInt(X, A, B: Integer): Integer;
809 begin
810 Result := X;
811 if X < A then Result := A else if X > B then Result := B;
812 end;
814 begin
815 if Enabled and (FCurTexture >= 0) and
816 (FTextureIDs[FCurTexture].Anim) and
817 (FTextureIDs[FCurTexture].AnTex <> nil) and
818 (Width > 0) and (Height > 0) and (FAlpha < 255) then
819 begin
820 FCurFrame := ClampInt(Frame, 0, FTextureIDs[FCurTexture].AnTex.TotalFrames - 1);
821 FCurFrameCount := Count;
822 FTextureIDs[FCurTexture].AnTex.CurrentFrame := FCurFrame;
823 FTextureIDs[FCurTexture].AnTex.CurrentCounter := FCurFrameCount;
824 end;
825 end;
827 procedure TPanel.NextTexture(AnimLoop: Byte = 0);
828 begin
829 Assert(FCurTexture >= -1, 'FCurTexture < -1');
831 // Íåò òåêñòóð:
832 if Length(FTextureIDs) = 0 then
833 FCurTexture := -1
834 else
835 // Òîëüêî îäíà òåêñòóðà:
836 if Length(FTextureIDs) = 1 then
837 begin
838 if FCurTexture = 0 then
839 FCurTexture := -1
840 else
841 FCurTexture := 0;
842 end
843 else
844 // Áîëüøå îäíîé òåêñòóðû:
845 begin
846 // Ñëåäóþùàÿ:
847 Inc(FCurTexture);
848 // Ñëåäóþùåé íåò - âîçâðàò ê íà÷àëó:
849 if FCurTexture >= Length(FTextureIDs) then
850 FCurTexture := 0;
851 end;
853 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
854 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
855 begin
856 if (FTextureIDs[FCurTexture].AnTex = nil) then
857 begin
858 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
859 Exit;
860 end;
862 if AnimLoop = 1 then
863 FTextureIDs[FCurTexture].AnTex.Loop := True
864 else
865 if AnimLoop = 2 then
866 FTextureIDs[FCurTexture].AnTex.Loop := False;
868 FTextureIDs[FCurTexture].AnTex.Reset();
869 end;
871 LastAnimLoop := AnimLoop;
872 end;
874 procedure TPanel.SetTexture(ID: Integer; AnimLoop: Byte = 0);
875 begin
876 if (ID >= -1) and (ID < Length(FTextureIDs)) then
877 FCurTexture := ID;
879 // Ïåðåêëþ÷èëèñü íà âèäèìóþ àíèì. òåêñòóðó:
880 if (FCurTexture >= 0) and FTextureIDs[FCurTexture].Anim then
881 begin
882 if (FTextureIDs[FCurTexture].AnTex = nil) then
883 begin
884 g_FatalError(_lc[I_GAME_ERROR_SWITCH_TEXTURE]);
885 Exit;
886 end;
888 if AnimLoop = 1 then
889 FTextureIDs[FCurTexture].AnTex.Loop := True
890 else
891 if AnimLoop = 2 then
892 FTextureIDs[FCurTexture].AnTex.Loop := False;
894 FTextureIDs[FCurTexture].AnTex.Reset();
895 end;
897 LastAnimLoop := AnimLoop;
898 end;
900 function TPanel.GetTextureID(): DWORD;
901 var Texture: Integer;
902 begin
903 Result := LongWord(TEXTURE_NONE);
904 if (FCurTexture >= 0) then
905 begin
906 Texture := FTextureIDs[FCurTexture].Texture;
907 case Textures[Texture].TextureName of
908 TEXTURE_NAME_WATER: Result := DWORD(TEXTURE_SPECIAL_WATER);
909 TEXTURE_NAME_ACID1: Result := DWORD(TEXTURE_SPECIAL_ACID1);
910 TEXTURE_NAME_ACID2: Result := DWORD(TEXTURE_SPECIAL_ACID2);
911 end
912 end
913 end;
915 function TPanel.GetTextureCount(): Integer;
916 begin
917 Result := Length(FTextureIDs);
918 if Enabled and (FCurTexture >= 0) then
919 if (FTextureIDs[FCurTexture].Anim) and
920 (FTextureIDs[FCurTexture].AnTex <> nil) and
921 (Width > 0) and (Height > 0) and (FAlpha < 255) then
922 Result := Result + 100;
923 end;
925 function TPanel.CanChangeTexture(): Boolean;
926 begin
927 Result := (GetTextureCount() > 1) or hasTexTrigger;
928 end;
930 const
931 PAN_SAVE_VERSION = 1;
933 procedure TPanel.SaveState (st: TStream);
934 var
935 anim: Boolean;
936 begin
937 if (st = nil) then exit;
939 // Ñèãíàòóðà ïàíåëè
940 utils.writeSign(st, 'PANL');
941 utils.writeInt(st, Byte(PAN_SAVE_VERSION));
942 // Îòêðûòà/çàêðûòà, åñëè äâåðü
943 utils.writeBool(st, FEnabled);
944 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò
945 utils.writeInt(st, Byte(FLiftType));
946 // Íîìåð òåêóùåé òåêñòóðû
947 utils.writeInt(st, Integer(FCurTexture));
948 // Êîîðäèíàòû è ðàçìåð
949 utils.writeInt(st, Integer(FX));
950 utils.writeInt(st, Integer(FY));
951 utils.writeInt(st, Word(FWidth));
952 utils.writeInt(st, Word(FHeight));
953 // Àíèìèðîâàíà ëè òåêóùàÿ òåêñòóðà
954 if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
955 begin
956 assert(FTextureIDs[FCurTexture].AnTex <> nil, 'TPanel.SaveState: No animation object');
957 anim := true;
958 end
959 else
960 begin
961 anim := false;
962 end;
963 utils.writeBool(st, anim);
964 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ
965 if anim then FTextureIDs[FCurTexture].AnTex.SaveState(st, FAlpha, FBlending);
967 // moving platform state
968 utils.writeInt(st, Integer(mMovingSpeed.X));
969 utils.writeInt(st, Integer(mMovingSpeed.Y));
970 utils.writeInt(st, Integer(mMovingStart.X));
971 utils.writeInt(st, Integer(mMovingStart.Y));
972 utils.writeInt(st, Integer(mMovingEnd.X));
973 utils.writeInt(st, Integer(mMovingEnd.Y));
975 utils.writeInt(st, Integer(mSizeSpeed.w));
976 utils.writeInt(st, Integer(mSizeSpeed.h));
977 utils.writeInt(st, Integer(mSizeEnd.w));
978 utils.writeInt(st, Integer(mSizeEnd.h));
980 utils.writeBool(st, mMovingActive);
981 utils.writeBool(st, mMoveOnce);
983 utils.writeInt(st, Integer(mEndPosTrig));
984 utils.writeInt(st, Integer(mEndSizeTrig));
985 end;
988 procedure TPanel.LoadState (st: TStream);
989 begin
990 if (st = nil) then exit;
992 // Ñèãíàòóðà ïàíåëè
993 if not utils.checkSign(st, 'PANL') then raise XStreamError.create('wrong panel signature');
994 if (utils.readByte(st) <> PAN_SAVE_VERSION) then raise XStreamError.create('wrong panel version');
995 // Îòêðûòà/çàêðûòà, åñëè äâåðü
996 FEnabled := utils.readBool(st);
997 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò
998 FLiftType := utils.readByte(st);
999 // Íîìåð òåêóùåé òåêñòóðû
1000 FCurTexture := utils.readLongInt(st);
1001 // Êîîðäèíàòû è ðàçìåð
1002 FX := utils.readLongInt(st);
1003 FY := utils.readLongInt(st);
1004 FWidth := utils.readWord(st);
1005 FHeight := utils.readWord(st);
1006 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà
1007 if utils.readBool(st) then
1008 begin
1009 // Åñëè äà - çàãðóæàåì àíèìàöèþ
1010 Assert((FCurTexture >= 0) and
1011 (FTextureIDs[FCurTexture].Anim) and
1012 (FTextureIDs[FCurTexture].AnTex <> nil),
1013 'TPanel.LoadState: No animation object');
1014 FTextureIDs[FCurTexture].AnTex.LoadState(st, FAlpha, FBlending);
1015 end;
1017 // moving platform state
1018 mMovingSpeed.X := utils.readLongInt(st);
1019 mMovingSpeed.Y := utils.readLongInt(st);
1020 mMovingStart.X := utils.readLongInt(st);
1021 mMovingStart.Y := utils.readLongInt(st);
1022 mMovingEnd.X := utils.readLongInt(st);
1023 mMovingEnd.Y := utils.readLongInt(st);
1025 mSizeSpeed.w := utils.readLongInt(st);
1026 mSizeSpeed.h := utils.readLongInt(st);
1027 mSizeEnd.w := utils.readLongInt(st);
1028 mSizeEnd.h := utils.readLongInt(st);
1030 mMovingActive := utils.readBool(st);
1031 mMoveOnce := utils.readBool(st);
1033 mEndPosTrig := utils.readLongInt(st);
1034 mEndSizeTrig := utils.readLongInt(st);
1036 positionChanged();
1037 //mapGrid.proxyEnabled[proxyId] := FEnabled; // done in g_map.pas
1038 end;
1041 end.