diff --git a/Client/CentrED.lpi b/Client/CentrED.lpi index 41850a2..133db83 100644 --- a/Client/CentrED.lpi +++ b/Client/CentrED.lpi @@ -325,7 +325,7 @@ - + @@ -577,6 +577,10 @@ + + + + diff --git a/Client/UActions.pas b/Client/UActions.pas new file mode 100644 index 0000000..f380877 --- /dev/null +++ b/Client/UActions.pas @@ -0,0 +1,465 @@ +(* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at + * http://www.opensource.org/licenses/cddl1.php. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at + * http://www.opensource.org/licenses/cddl1.php. If applicable, + * add the following below this CDDL HEADER, with the fields enclosed + * by brackets "[]" replaced with your own identifying * information: + * Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * + * Portions Copyright 2022 Andreas Schneider + *) + +unit UActions; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, fgl, UWorldItem, UStatics, UMap, UPacket, ULandscape; + +type + + TPacketList = specialize TFPGObjectList; + TIdList = specialize TFPGList; + + { TBaseEditAction } + + TBaseEditAction = class(TObject) + private + FLandscape: TLandscape; + FMapTiles: TMapCellList; + FStaticTiles: TStaticItemList; + public + constructor Create(ALandscape: TLandscape); + destructor Destroy; override; + public + property MapTiles: TMapCellList read FMapTiles; + property StaticTiles: TStaticItemList read FStaticTiles; + + procedure StartSelection(ATile: TWorldItem); virtual; abstract; + procedure AddSelection(ATile: TWorldItem); virtual; abstract; + function IsHighlighted(ATile: TWorldItem): Boolean; virtual; abstract; + procedure Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, AReverseActions: TPacketList); virtual; + end; + + { TRectangleEditAction } + + TRectangleEditAction = class(TBaseEditAction) + private + FFirstTile: TWorldItem; + FLastTile: TWorldItem; + FRect: TRect; + public + constructor Create(ALandscape: TLandscape); + procedure StartSelection(ATile: TWorldItem); override; + procedure AddSelection(ATile: TWorldItem); override; + function IsHighlighted(ATile: TWorldItem): Boolean; override; + private + procedure UpdateArea; virtual; + function IsEditableTile(AWorldItem: TWorldItem): Boolean; inline; + function IsEditableStaticTile(AWorldItem: TWorldItem): Boolean; inline; + end; + + { TDrawAction } + + TDrawAction = class(TRectangleEditAction) + private + FTileIds: TIdList; + FForceZ: Boolean; + FForceZValue: ShortInt; + FRandomZ: Boolean; + FRandomZValue: Byte; + FHue: Word; + public + property TileIds: TIdList read FTileIds; + property ForceZ: Boolean read FForceZ write FForceZ; + property ForceZValue: ShortInt read FForceZValue write FForceZValue; + property RandomZ: Boolean read FRandomZ write FRandomZ; + property RandomZValue: Byte read FRandomZValue write FRandomZValue; + + constructor Create(ALandscape: TLandscape); + + function IsHighlighted(ATile: TWorldItem): Boolean; override; + procedure Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, + AReverseActions: TPacketList); override; + private + procedure UpdateArea; override; + end; + + { TMoveAction } + + TMoveAction = class(TRectangleEditAction) + private + FOffsetX: Integer; + FOffsetY: Integer; + public + property OffsetX: Integer read FOffsetX write FOffsetX; + property OffsetY: Integer read FOffsetY write FOffsetY; + + procedure Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, + AReverseActions: TPacketList); override; + end; + + { TElevateAction } + + TElevateAction = class(TRectangleEditAction) + public type + TMode = (emSet, emRaise, emLower); + private + FMode: TMode; + FZ: ShortInt; + FRandomZ: Boolean; + FRandomZValue: Byte; + public + property Mode: TMode read FMode write FMode; + property Z: ShortInt read FZ write FZ; + + procedure Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, + AReverseActions: TPacketList); override; + end; + + { TDeleteAction } + + TDeleteAction = class(TRectangleEditAction) + public + procedure Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, + AReverseActions: TPacketList); override; + end; + + { THueAction } + + THueAction = class(TRectangleEditAction) + private + FHue: Word; + public + property Hue: Word read FHue write FHue; + + procedure Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, + AReverseActions: TPacketList); override; + end; + +implementation + +uses + Math, UGameResources, UPackets; + +{ TBaseEditAction } + +constructor TBaseEditAction.Create(ALandscape: TLandscape); +begin + FLandscape := ALandscape; + FMapTiles := TMapCellList.Create; + FStaticTiles := TStaticItemList.Create; +end; + +destructor TBaseEditAction.Destroy; +begin + FMapTiles.Free; + FStaticTiles.Free; + inherited Destroy; +end; + +procedure TBaseEditAction.Execute(AScreenBuffer: TScreenBuffer; out + AForwardActions, AReverseActions: TPacketList); +begin + AForwardActions := TPacketList.Create; + AReverseActions := TPacketList.Create; +end; + +{ TRectangleEditAction } + +constructor TRectangleEditAction.Create(ALandscape: TLandscape); +begin + inherited Create(ALandscape); + FRect.Left := -1; + FRect.Top := -1; + FRect.Bottom := -1; + FRect.Right := -1; +end; + +procedure TRectangleEditAction.StartSelection(ATile: TWorldItem); +begin + FFirstTile := ATile; + FLastTile := ATile; + UpdateArea; +end; + +procedure TRectangleEditAction.AddSelection(ATile: TWorldItem); +begin + FLastTile := ATile; + UpdateArea; +end; + +function TRectangleEditAction.IsHighlighted(ATile: TWorldItem): Boolean; +begin + Result := ((FFirstTile = FLastTile) and (FFirsttile = ATile)) or + FRect.Contains(TPoint.Create(ATile.X, ATile.Y)); +end; + +procedure TRectangleEditAction.UpdateArea; +begin + if FFirstTile = FLastTile then + begin + FRect.Left := -1; + FRect.Top := -1; + FRect.Bottom := -1; + FRect.Right := -1; + end else + begin + FRect.Left := Min(FFirstTile.X, FLastTile.X); + FRect.Top := Min(FFirstTile.Y, FLastTile.Y); + FRect.Right := Max(FFirstTile.X, FLastTile.X); + FRect.Bottom := Max(FFirstTile.Y, FLastTile.Y); + end; +end; + +function TRectangleEditAction.IsEditableTile(AWorldItem: TWorldItem): Boolean; +begin + if not AWorldItem.CanBeEdited then + Exit(False); + + if FFirstTile = FLastTile then + begin + if AWorldItem <> FFirstTile then + Exit(False); + end else + begin + if not FRect.Contains(TPoint.Create(AWorldItem.X, AWorldItem.Y)) then + Exit(False); + end; + + Result := True; +end; + +function TRectangleEditAction.IsEditableStaticTile(AWorldItem: TWorldItem): Boolean; +begin + if not IsEditableTile(AWorldItem) then + Exit(False); + + if not (AWorldItem is TStaticItem) then + Exit(False); + + Result := True; +end; + +{ TDrawAction } + +constructor TDrawAction.Create(ALandscape: TLandscape); +begin + inherited Create(ALandscape); + FTileIds := TIdList.Create; +end; + +function TDrawAction.IsHighlighted(ATile: TWorldItem): Boolean; +begin + // Since we add new tiles, we don't need to highlight any existing + // tiles. + Result := False; +end; + +procedure TDrawAction.Execute(AScreenBuffer: TScreenBuffer; out + AForwardActions, AReverseActions: TPacketList); +var + staticTile: TStaticItem; + mapTile, previousMapTile: TMapCell; +begin + inherited Execute(AScreenBuffer, AForwardActions, AReverseActions); + + if FTileIds.Count < 1 then + Exit; + + for mapTile in FMapTiles do + begin + previousMapTile := FLandscape.MapCell[mapTile.X, mapTile.Y]; + AForwardActions.Add(TDrawMapPacket.Create(mapTile.X, mapTile.Y, mapTile.Z, mapTile.TileID)); + AReverseActions.Add(TDrawMapPacket.Create(previousMapTile.X, previousMapTile.Y, previousMapTile.Z, previousMapTile.TileID)); + end; + + for staticTile in FStaticTiles do + begin + AForwardActions.Add(TInsertStaticPacket.Create(staticTile)); + AReverseActions.Add(TDeleteStaticPacket.Create(staticTile)); + end; +end; + +procedure TDrawAction.UpdateArea; +var + targetArea: TRect; + tileX, tileY: Word; + tileId: Word; + mapCell: TMapCell; + staticItem: TStaticItem; + worldItem: TWorldItem; +begin + inherited UpdateArea; + + FMapTiles.Clear; + FStaticTiles.Clear; + + // TODO Only update relevant section of the tile lists. + + if FFirstTile = nil then + Exit; + + targetArea.Left := Min(FFirstTile.X, FLastTile.X); + targetArea.Top := Min(FFirstTile.Y, FLastTile.Y); + targetArea.Right := Max(FFirstTile.X, FLastTile.X); + targetArea.Bottom := Max(FFirstTile.Y, FLastTile.Y); + + for tileX := targetArea.Left to targetArea.Right do + for tileY := targetArea.Top to targetArea.Bottom do + begin + tileId := FTileIds[Random(FTileIds.Count)]; + if tileId < $4000 then + begin + // Map Tile + mapCell := FLandscape.MapCell[tileX, tileY].Clone; + mapCell.TileID := tileId; + worldItem := mapCell; + FMapTiles.Add(mapCell); + end else + begin + // Static Tile + staticItem := TStaticItem.Create(nil, nil, 0, 0); + staticItem.TileID := tileId - $4000; + staticItem.Hue := FHue; + if not FForceZ then + begin + staticItem.Z := FFirstTile.Z; + if FFirstTile is TStaticItem then + staticItem.Z := staticItem.Z + ResMan.Tiledata.StaticTiles[FFirstTile.TileID].Height; + end; + worldItem := staticItem; + FStaticTiles.Add(staticItem); + end; + + worldItem.X := tileX; + worldItem.Y := tileY; + + if FForceZ then + worldItem.Z := FForceZValue; + if FRandomZ then + worldItem.Z := worldItem.Z + Random(FRandomZValue); + end; +end; + +{ TDeleteAction } + +procedure TDeleteAction.Execute(AScreenBuffer: TScreenBuffer; out + AForwardActions, AReverseActions: TPacketList); +var + item: TWorldItem; +begin + inherited Execute(AScreenBuffer, AForwardActions, AReverseActions); + + for item in AScreenBuffer do + begin + if not IsEditableStaticTile(item) then + Continue; + + AForwardActions.Add(TDeleteStaticPacket.Create(TStaticItem(item))); + AReverseActions.Add(TInsertStaticPacket.Create(TStaticItem(item))); + end; +end; + +{ TMoveAction } + +procedure TMoveAction.Execute(AScreenBuffer: TScreenBuffer; out + AForwardActions, AReverseActions: TPacketList); +var + newX, newY: Word; + item: TWorldItem; +begin + inherited Execute(AScreenBuffer, AForwardActions, AReverseActions); + + for item in AScreenBuffer do + begin + if not IsEditableStaticTile(item) then + Continue; + + newX := EnsureRange(item.X + FOffsetX, 0, FLandscape.CellWidth - 1); + newY := EnsureRange(item.Y + FOffsetY, 0, FLandscape.CellHeight - 1); + + AForwardActions.Add(TMoveStaticPacket.Create(TStaticItem(item), newX, newY)); + AReverseActions.Add(TMoveStaticPacket.Create(TStaticItem(item), item.X, item.Y)); + end; +end; + +{ TElevateAction } + +procedure TElevateAction.Execute(AScreenBuffer: TScreenBuffer; out + AForwardActions, AReverseActions: TPacketList); +var + item: TWorldItem; + newZ: Integer; +begin + inherited Execute(AScreenBuffer, AForwardActions, AReverseActions); + + for item in AScreenBuffer do + begin + if not IsEditableTile(item) then + Continue; + + newZ := FZ; + if FRandomZ then + newZ := newZ + Random(FRandomZValue); + + case FMode of + emRaise: newZ := item.Z + FZ; + emLower: newZ := item.Z - FZ; + end; + + newZ := EnsureRange(newZ, -128, 127); + + if item is TMapCell then + begin + AForwardActions.Add(TDrawMapPacket.Create(item.X, item.Y, newZ, item.TileID)); + AReverseActions.Add(TDrawMapPacket.Create(item.X, item.Y, item.Z, item.TileID)); + end else if item is TStaticItem then + begin + AForwardActions.Add(TElevateStaticPacket.Create(TStaticItem(item), newZ)); + AReverseActions.Add(TElevateStaticPacket.Create(TStaticItem(item), item.Z)); + end; + end; +end; + +{ THueAction } + +procedure THueAction.Execute(AScreenBuffer: TScreenBuffer; out AForwardActions, + AReverseActions: TPacketList); +var + item: TWorldItem; +begin + inherited Execute(AScreenBuffer, AForwardActions, AReverseActions); + + for item in AScreenBuffer do + begin + if not IsEditableStaticTile(item) then + Continue; + + if TStaticItem(item).Hue = FHue then + Continue; + + AForwardActions.Add(THueStaticPacket.Create(TStaticItem(item), FHue)); + AReverseActions.Add(THueStaticPacket.Create(TStaticItem(item), TStaticItem(item).Hue)); + end; +end; + +end. + diff --git a/Client/ULandscape.pas b/Client/ULandscape.pas index 00313b1..d35d4de 100644 --- a/Client/ULandscape.pas +++ b/Client/ULandscape.pas @@ -299,6 +299,18 @@ type { Events } procedure OnTileRemoved(ATile: TMulBlock); end; + + { TScreenBufferItemEnumerator } + + TScreenBufferItemEnumerator = object + private + FScreenBuffer: TScreenBuffer; + FCurrentBlock: PBlockInfo; + FCurrentItem: TWorldItem; + public + function MoveNext: Boolean; + property Current: TWorldItem read FCurrentItem; + end; TStaticInfo = packed record X: Word; @@ -308,6 +320,8 @@ type Hue: Word; end; +operator enumerator(AScreenBuffer: TScreenBuffer): TScreenBufferItemEnumerator; + implementation uses @@ -325,6 +339,21 @@ begin GLVector[2] := AVector.data[2]; end; +operator enumerator(AScreenBuffer: TScreenBuffer): TScreenBufferItemEnumerator; +begin + Result.FScreenBuffer := AScreenBuffer; + Result.MoveNext; +end; + +{ TScreenBufferItemEnumerator } + +function TScreenBufferItemEnumerator.MoveNext: Boolean; +begin + Result := FScreenBuffer.Iterate(FCurrentBlock); + if not Result then + FCurrentItem := nil; +end; + { TLandTextureManager } constructor TLandTextureManager.Create; diff --git a/Client/UPackets.pas b/Client/UPackets.pas index 8d4a078..2c0d1d9 100644 --- a/Client/UPackets.pas +++ b/Client/UPackets.pas @@ -82,8 +82,9 @@ type { TInsertStaticPacket } - TInsertStaticPacket = class(TPacket) - constructor Create(AX, AY: Word; AZ: ShortInt; ATileID: Word; AHue: Word); + TInsertStaticPacket = class(TStaticPacket) + constructor Create(AX, AY: Word; AZ: ShortInt; ATileID: Word; AHue: Word); overload; + constructor Create(AStaticItem: TStaticItem); overload; end; { TDeleteStaticPacket } @@ -250,6 +251,12 @@ begin FStream.WriteWord(AHue); end; +constructor TInsertStaticPacket.Create(AStaticItem: TStaticItem); +begin + inherited Create($07, 10); + WriteStaticItem(AStaticItem); +end; + { TDeleteStaticPacket } constructor TDeleteStaticPacket.Create(AStaticItem: TStaticItem); diff --git a/Client/UfrmMain.lfm b/Client/UfrmMain.lfm index 1bef31e..2ac5abe 100644 --- a/Client/UfrmMain.lfm +++ b/Client/UfrmMain.lfm @@ -1,11 +1,11 @@ object frmMain: TfrmMain - Left = 283 + Left = 87 Height = 961 - Top = 179 + Top = 70 Width = 1180 ActiveControl = oglGameWindow Caption = 'UO CentrED' - ClientHeight = 928 + ClientHeight = 961 ClientWidth = 1180 Constraints.MinHeight = 781 Constraints.MinWidth = 1172 @@ -19,12 +19,11 @@ object frmMain: TfrmMain Position = poScreenCenter SessionProperties = 'acFlat.Checked;acNoDraw.Checked;Height;Left;mnuFlatShowHeight.Checked;mnuSecurityQuestion.Checked;mnuShowAnimations.Checked;spTileList.Top;tbStatics.Down;tbTerrain.Down;Top;Width;WindowState;mnuWhiteBackground.Checked' ShowInTaskBar = stAlways - LCLVersion = '2.2.3.0' WindowState = wsMaximized object pnlBottom: TPanel Left = 0 Height = 49 - Top = 879 + Top = 912 Width = 1180 Align = alBottom BevelOuter = bvNone @@ -119,7 +118,7 @@ object frmMain: TfrmMain end object pcLeft: TPageControl Left = 0 - Height = 841 + Height = 874 Top = 38 Width = 350 ActivePage = tsTiles @@ -128,7 +127,7 @@ object frmMain: TfrmMain TabOrder = 1 object tsTiles: TTabSheet Caption = 'Tiles' - ClientHeight = 803 + ClientHeight = 836 ClientWidth = 340 object lblFilter: TLabel AnchorSideLeft.Control = cbTerrain @@ -153,7 +152,7 @@ object frmMain: TfrmMain AnchorSideRight.Side = asrBottom AnchorSideBottom.Control = spTileList Left = 6 - Height = 460 + Height = 493 Hint = '-' Top = 74 Width = 328 @@ -214,7 +213,7 @@ object frmMain: TfrmMain AnchorSideBottom.Side = asrBottom Left = 0 Height = 261 - Top = 542 + Top = 575 Width = 340 Anchors = [akTop, akLeft, akRight, akBottom] Caption = 'Random pool' @@ -553,7 +552,7 @@ object frmMain: TfrmMain Cursor = crVSplit Left = 0 Height = 8 - Top = 534 + Top = 567 Width = 340 Align = alNone Anchors = [akLeft, akRight, akBottom] @@ -567,7 +566,7 @@ object frmMain: TfrmMain Left = 172 Height = 38 Hint = 'Append S or T to restrict the search to Statics or Terrain.' - Top = 484 + Top = 517 Width = 150 Anchors = [akRight, akBottom] BorderSpacing.Right = 12 @@ -628,11 +627,11 @@ object frmMain: TfrmMain end object tsClients: TTabSheet Caption = 'Clients' - ClientHeight = 803 + ClientHeight = 836 ClientWidth = 340 object lbClients: TListBox Left = 0 - Height = 803 + Height = 836 Top = 0 Width = 340 Align = alClient @@ -647,7 +646,7 @@ object frmMain: TfrmMain end object tsLocations: TTabSheet Caption = 'Locations' - ClientHeight = 803 + ClientHeight = 836 ClientWidth = 340 object btnClearLocations: TSpeedButton AnchorSideLeft.Control = btnDeleteLocation @@ -656,7 +655,7 @@ object frmMain: TfrmMain Left = 194 Height = 35 Hint = 'Clear' - Top = 762 + Top = 795 Width = 36 BorderSpacing.Left = 6 Glyph.Data = { @@ -707,7 +706,7 @@ object frmMain: TfrmMain Left = 152 Height = 35 Hint = 'Delete' - Top = 762 + Top = 795 Width = 36 Anchors = [akLeft, akBottom] BorderSpacing.Bottom = 6 @@ -757,7 +756,7 @@ object frmMain: TfrmMain Left = 110 Height = 35 Hint = 'Add' - Top = 762 + Top = 795 Width = 36 Anchors = [akTop, akRight] BorderSpacing.Right = 6 @@ -809,7 +808,7 @@ object frmMain: TfrmMain AnchorSideBottom.Control = btnDeleteLocation Cursor = 63 Left = 6 - Height = 750 + Height = 783 Top = 6 Width = 328 Anchors = [akTop, akLeft, akRight, akBottom] @@ -1039,7 +1038,7 @@ object frmMain: TfrmMain AnchorSideBottom.Control = spChat Left = 350 Height = 35 - Top = 634 + Top = 667 Width = 830 Anchors = [akLeft, akRight, akBottom] BevelInner = bvRaised @@ -1075,7 +1074,7 @@ object frmMain: TfrmMain AnchorSideBottom.Control = pnlBottom Left = 350 Height = 202 - Top = 677 + Top = 710 Width = 830 Anchors = [akTop, akLeft, akRight, akBottom] BevelOuter = bvNone @@ -1141,7 +1140,7 @@ object frmMain: TfrmMain Cursor = crVSplit Left = 350 Height = 8 - Top = 669 + Top = 702 Width = 830 Align = alNone Anchors = [akLeft, akRight, akBottom] @@ -1158,7 +1157,7 @@ object frmMain: TfrmMain AnchorSideRight.Side = asrBottom AnchorSideBottom.Control = pnlChatHeader Left = 350 - Height = 596 + Height = 629 Top = 38 Width = 830 Anchors = [akTop, akLeft, akRight, akBottom] diff --git a/Client/UfrmMain.pas b/Client/UfrmMain.pas index b1f6c2d..cb4a749 100644 --- a/Client/UfrmMain.pas +++ b/Client/UfrmMain.pas @@ -36,7 +36,8 @@ uses StdCtrls, Spin, UEnums, laz.VirtualTrees, Buttons, UMulBlock, UWorldItem, math, LCLIntf, UOverlayUI, UStatics, UEnhancedMemoryStream, ActnList, XMLPropStorage, ImagingClasses, dateutils, UPlatformTypes, UMap, UPacket, - UGLFont, DOM, XMLRead, XMLWrite, strutils, ULightManager, fgl, UTiledata; + UGLFont, DOM, XMLRead, XMLWrite, strutils, ULightManager, fgl, UTiledata, + UActions; type TAccessChangedListener = procedure(AAccessLevel: TAccessLevel) of object;