🚧 Implement actions as objects

This commit is contained in:
Andreas Schneider 2022-05-29 11:46:22 +02:00
parent dc36f0b09c
commit d75a85d269
6 changed files with 531 additions and 26 deletions

View File

@ -325,7 +325,7 @@
<MinVersion Minor="5" Release="3" Valid="True"/>
</Item6>
</RequiredPackages>
<Units Count="47">
<Units Count="48">
<Unit0>
<Filename Value="CentrED.lpr"/>
<IsPartOfProject Value="True"/>
@ -577,6 +577,10 @@
<Filename Value="USelectionHelper.pas"/>
<IsPartOfProject Value="True"/>
</Unit46>
<Unit47>
<Filename Value="UActions.pas"/>
<IsPartOfProject Value="True"/>
</Unit47>
</Units>
</ProjectOptions>
<CompilerOptions>

465
Client/UActions.pas Normal file
View File

@ -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<TPacket>;
TIdList = specialize TFPGList<Word>;
{ 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.

View File

@ -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;

View File

@ -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);

View File

@ -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]

View File

@ -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;