sync: restore source from Google Drive
Sync changed files to Google Drive / sync (push) Successful in 1h45m13s

This commit is contained in:
admin_d committed 2026-09-12 16:50:30 +05:00
1 parent e2d8247e91
commit 7063d1b1c9
993 files changed
+949976

No files matched your search

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+306
View File
@@ -0,0 +1,306 @@
unit KM_HandEntity;
{$I KaM_Remake.inc}
interface
uses
KM_Defaults, KM_Points, KM_CommonClasses, KM_HandTypes, KM_Entity, KromOGLUtils;
type
// Common class for TKMUnit / TKMHouse / TKMUnitGroup
// "class abstract" disallows anyone from creating an instance of it (can create only child classes)
TKMHandEntity = class abstract(TKMEntity)
private
fType: TKMHandEntityType;
fOwner: TKMHandID;
fAllowAllyToSelect: Boolean; // Allow ally to select entity
function GetOwner: TKMHandID; inline;
function GetType: TKMHandEntityType;
protected
function GetPositionForDisplayF: TKMPointF; virtual; abstract;
procedure SetOwner(const aOwner: TKMHandID); virtual;
function GetAllowAllyToSelect: Boolean; virtual;
procedure SetAllowAllyToSelect(aAllow: Boolean); virtual;
function GetIsSelectable: Boolean; virtual; abstract;
public
constructor Create(aType: TKMHandEntityType; aUID: Integer; aOwner: TKMHandID);
constructor Load(LoadStream: TKMemoryStream); override;
procedure Save(SaveStream: TKMemoryStream); override;
property EntityType: TKMHandEntityType read GetType;
property Owner: TKMHandID read GetOwner write SetOwner;
property PositionForDisplayF: TKMPointF read GetPositionForDisplayF;
property AllowAllyToSelect: Boolean read GetAllowAllyToSelect write SetAllowAllyToSelect;
property IsSelectable: Boolean read GetIsSelectable;
function IsUnit: Boolean;
function IsGroup: Boolean;
function IsHouse: Boolean;
function ObjToStringBasic(const aSeparator: String = '|'): String;
function ObjToString(const aSeparator: String = '|'): String; override;
function ObjToStringShort(const aSeparator: String = '|'): String; override;
end;
TKMHandEntityPointer<T> = class abstract(TKMHandEntity)
private
fPointerCount: Cardinal;
protected
function GetInstance: T; virtual; abstract;
public
constructor Create(aType: TKMHandEntityType; aUID: Integer; aOwner: TKMHandID);
constructor Load(LoadStream: TKMemoryStream); override;
procedure Save(SaveStream: TKMemoryStream); override;
function GetPointer: T; //Returns self and adds one to the pointer counter
procedure ReleasePointer; //Decreases the pointer counter
property PointerCount: Cardinal read fPointerCount;
function ObjToString(const aSeparator: String = '|'): String; override;
end;
TKMHighlightEntity = record
Entity: TKMHandEntity;
Color: TColor4;
constructor New(aEntity: TKMHandEntity); overload;
constructor New(aEntity: TKMHandEntity; aColor: TColor4); overload;
procedure SetEntity(aEntity: TKMHandEntity);
procedure SetColor(aColor: TColor4);
function IsSet: Boolean;
procedure Reset;
end;
const
DEFAULT_HIGHLIGHT_COL = icCyan;
implementation
uses
SysUtils, KM_GameParams,
KM_CommonExceptions;
{ TKMHandEntity }
constructor TKMHandEntity.Create(aType: TKMHandEntityType; aUID: Integer; aOwner: TKMHandID);
begin
inherited Create(aUID);
fType := aType;
fOwner := aOwner;
fAllowAllyToSelect := False; // Entity view for allies is blocked by default
end;
constructor TKMHandEntity.Load(LoadStream: TKMemoryStream);
begin
inherited;
LoadStream.Read(fType, SizeOf(fType));
LoadStream.Read(fOwner, SizeOf(fOwner));
LoadStream.Read(fAllowAllyToSelect);
end;
procedure TKMHandEntity.Save(SaveStream: TKMemoryStream);
begin
inherited;
SaveStream.Write(fType, SizeOf(fType));
SaveStream.Write(fOwner, SizeOf(fOwner));
SaveStream.Write(fAllowAllyToSelect);
end;
procedure TKMHandEntity.SetAllowAllyToSelect(aAllow: Boolean);
begin
fAllowAllyToSelect := aAllow;
end;
function TKMHandEntity.GetAllowAllyToSelect: Boolean;
begin
Result := ALLOW_SELECT_ALLIES and fAllowAllyToSelect;
end;
function TKMHandEntity.GetOwner: TKMHandID;
begin
if Self = nil then Exit(HAND_NONE);
Result := fOwner;
end;
function TKMHandEntity.GetType: TKMHandEntityType;
begin
if Self = nil then Exit(etNone);
Result := fType;
end;
function TKMHandEntity.IsUnit: Boolean;
begin
if Self = nil then Exit(False);
Result := fType = etUnit;
end;
function TKMHandEntity.IsGroup: Boolean;
begin
if Self = nil then Exit(False);
Result := fType = etGroup;
end;
function TKMHandEntity.IsHouse: Boolean;
begin
if Self = nil then Exit(False);
Result := fType = etHouse;
end;
procedure TKMHandEntity.SetOwner(const aOwner: TKMHandID);
begin
fOwner := aOwner;
end;
function TKMHandEntity.ObjToStringBasic(const aSeparator: String = '|'): String;
begin
Result := Format('UID = %d%sPos = %s', [UID, aSeparator, PositionForDisplayF.ToString]);
end;
function TKMHandEntity.ObjToStringShort(const aSeparator: String = '|'): String;
begin
Result := inherited ObjToStringShort(aSeparator) +
Format('%sPos = %s', [aSeparator, PositionForDisplayF.ToString]);
end;
function TKMHandEntity.ObjToString(const aSeparator: String = '|'): String;
begin
Result := inherited ObjToString(aSeparator) +
Format('%sOwner = %d%sPositionF = %s%sAllowAllyToSel = %s',
[aSeparator,
Owner, aSeparator,
PositionForDisplayF.ToString, aSeparator,
BoolToStr(AllowAllyToSelect, True)]);
end;
{ TKMHandEntityPointer }
constructor TKMHandEntityPointer<T>.Create(aType: TKMHandEntityType; aUID: Integer; aOwner: TKMHandID);
begin
inherited Create(aType, aUID, aOwner);
fPointerCount := 0;
end;
constructor TKMHandEntityPointer<T>.Load(LoadStream: TKMemoryStream);
begin
inherited;
LoadStream.Read(fPointerCount);
end;
function TKMHandEntityPointer<T>.ObjToString(const aSeparator: String = '|'): String;
begin
Result := inherited ObjToString(aSeparator) + Format('%sPointerCnt = %d', [aSeparator, fPointerCount]);
end;
procedure TKMHandEntityPointer<T>.Save(SaveStream: TKMemoryStream);
begin
inherited;
SaveStream.Write(fPointerCount);
end;
// Returns self and adds on to the pointer counter
function TKMHandEntityPointer<T>.GetPointer: T;
begin
Assert(gGameParams.AllowPointerOperations, 'GetPointer is not allowed outside of game tick update procedure, it could cause game desync');
Inc(fPointerCount);
Result := GetInstance;
end;
{Decreases the pointer counter}
//Should be used only by gHands for clarity sake
procedure TKMHandEntityPointer<T>.ReleasePointer;
var
ErrorMsg: UnicodeString;
begin
Assert(gGameParams.AllowPointerOperations, 'ReleasePointer is not allowed outside of game tick update procedure, it could cause game desync');
if fPointerCount < 1 then
begin
ErrorMsg := 'Unit remove pointer for U: ';
try
ErrorMsg := ErrorMsg + ObjToStringShort(',');
except
on E: Exception do
ErrorMsg := ErrorMsg + IntToStr(UID) + ' Pos = ' + PositionForDisplayF.ToString;
end;
raise ELocError.Create(ErrorMsg, KMPointRound(PositionForDisplayF));
end;
Dec(fPointerCount);
end;
{ TKMHighlightEntity }
constructor TKMHighlightEntity.New(aEntity: TKMHandEntity; aColor: TColor4);
begin
Entity := aEntity;
Color := aColor;
end;
constructor TKMHighlightEntity.New(aEntity: TKMHandEntity);
begin
Entity := aEntity;
Color := DEFAULT_HIGHLIGHT_COL;
end;
function TKMHighlightEntity.IsSet: Boolean;
begin
Result := Entity <> nil;
end;
procedure TKMHighlightEntity.Reset;
begin
Entity := nil;
Color := DEFAULT_HIGHLIGHT_COL;
end;
procedure TKMHighlightEntity.SetColor(aColor: TColor4);
begin
Color := aColor;
end;
procedure TKMHighlightEntity.SetEntity(aEntity: TKMHandEntity);
begin
Entity := aEntity;
end;
end.
+64
View File
@@ -0,0 +1,64 @@
unit KM_HandEntityHelper;
{$I KaM_Remake.inc}
interface
uses
KM_HandEntity, KM_Units, KM_UnitWarrior, KM_UnitGroup, KM_Houses;
type
TKMHandEntityHelper = class helper for TKMHandEntity
function AsUnit: TKMUnit;
function AsUnitWarrior: TKMUnitWarrior;
function AsGroup: TKMUnitGroup;
function AsHouse: TKMHouse;
end;
implementation
{ TKMHandEntityHelper }
function TKMHandEntityHelper.AsGroup: TKMUnitGroup;
begin
if (Self = nil)
or not IsGroup
or not (Self is TKMUnitGroup) then
Exit(nil);
Result := TKMUnitGroup(Self);
end;
function TKMHandEntityHelper.AsHouse: TKMHouse;
begin
if (Self = nil)
or not IsHouse
or not (Self is TKMHouse) then
Exit(nil);
Result := TKMHouse(Self);
end;
function TKMHandEntityHelper.AsUnit: TKMUnit;
begin
if (Self = nil)
or not IsUnit
or not (Self is TKMUnit) then
Exit(nil);
Result := TKMUnit(Self);
end;
function TKMHandEntityHelper.AsUnitWarrior: TKMUnitWarrior;
begin
if (Self = nil)
or not IsUnit
or not (Self is TKMUnitWarrior) then
Exit(nil);
Result := TKMUnitWarrior(Self);
end;
end.
+170
View File
@@ -0,0 +1,170 @@
unit KM_HandLocks;
{$I KaM_Remake.inc}
interface
uses
KM_ResHouses,
KM_CommonClasses, KM_Defaults,
KM_ResTypes, KM_HandTypes;
type
TKMBoolHouseType = array [TKMHouseType] of Boolean;
// Permissions to build, trade and train
TKMHandLocks = class
private
fHouseUnlocked: array [TKMHouseType] of Boolean; //If building requirements performed
fUnitBlocked: array [TKMUnitType] of Boolean; //Allowance derived from mission script
fHandHouseLock: array [TKMHouseType] of TKMHandHouseLock;
procedure UpdateReqDone(aType: TKMHouseType);
function GetHouseBlocked(aHouseType: TKMHouseType): Boolean;
function GetHouseGranted(aHouseType: TKMHouseType): Boolean;
function GetHandHouseLock(aHouseType: TKMHouseType): TKMHandHouseLock;
procedure SetHandHouseLock(aHouseType: TKMHouseType; const aValue: TKMHandHouseLock);
public
AllowToTrade: array [WARE_MIN..WARE_MAX] of Boolean; //Allowance derived from mission script
constructor Create;
property HouseBlocked[aHouseType: TKMHouseType]: Boolean read GetHouseBlocked;
property HouseGranted[aHouseType: TKMHouseType]: Boolean read GetHouseGranted;
property HouseLock[aHouseType: TKMHouseType]: TKMHandHouseLock read GetHandHouseLock write SetHandHouseLock;
procedure HouseCreated(aType: TKMHouseType);
function HouseCanBuild(aType: TKMHouseType): Boolean;
procedure SetUnitBlocked(aIsBlocked: Boolean; aUnitType: TKMUnitType; aInTownHall: Boolean = False);
function GetUnitBlocked(aUnitType: TKMUnitType; aInTownHall: Boolean = False): Boolean;
procedure Save(SaveStream: TKMemoryStream);
procedure Load(LoadStream: TKMemoryStream);
end;
implementation
uses
KM_Resource;
{ TKMHandLocks }
constructor TKMHandLocks.Create;
var
W: TKMWareType;
HT: TKMHouseType;
begin
inherited;
for W := WARE_MIN to WARE_MAX do
AllowToTrade[W] := True;
//Release Store at the start of the game by default
fHouseUnlocked[htStore] := True;
for HT := Low(TKMHouseType) to High(TKMHouseType) do
fHandHouseLock[HT] := hlDefault;
end;
procedure TKMHandLocks.UpdateReqDone(aType: TKMHouseType);
var
H: TKMHouseType;
begin
for H := HOUSE_MIN to HOUSE_MAX do
if gRes.Houses[H].ReleasedBy = aType then
fHouseUnlocked[H] := True;
end;
// New house, either built by player or created by mission script
procedure TKMHandLocks.HouseCreated(aType: TKMHouseType);
begin
UpdateReqDone(aType);
end;
// Get effective permission
function TKMHandLocks.HouseCanBuild(aType: TKMHouseType): Boolean;
begin
Result := False;
case fHandHouseLock[aType] of
hlDefault: Result := fHouseUnlocked[aType];
hlBlocked: Result := False;
hlGranted: Result := True;
end;
end;
function TKMHandLocks.GetHouseBlocked(aHouseType: TKMHouseType): Boolean;
begin
Result := fHandHouseLock[aHouseType] = hlBlocked;
end;
function TKMHandLocks.GetHouseGranted(aHouseType: TKMHouseType): Boolean;
begin
Result := fHandHouseLock[aHouseType] = hlGranted;
end;
function TKMHandLocks.GetHandHouseLock(aHouseType: TKMHouseType): TKMHandHouseLock;
begin
Result := fHandHouseLock[aHouseType];
end;
procedure TKMHandLocks.SetHandHouseLock(aHouseType: TKMHouseType; const aValue: TKMHandHouseLock);
begin
Assert(aValue <> hlNone, 'Can''t set hlNone Hand House Lock');
fHandHouseLock[aHouseType] := aValue;
end;
//procedure TKMHandLocks.SetHouseBlocked(aHouseType: TKMHouseType; const aValue: Boolean);
//begin
// if aValue then
// fPlayerHouseLock[aHouseType] := phlBlocked
// else
// fPlayerHouseLock[aHouseType] := phlDefault;
//end;
//
//
//procedure TKMHandLocks.SetHouseGranted(aHouseType: TKMHouseType; const aValue: Boolean);
//begin
// fPlayerHouseLock[aHouseType] := phlGranted;
//end;
function TKMHandLocks.GetUnitBlocked(aUnitType: TKMUnitType; aInTownHall: Boolean = False): Boolean;
begin
Result := fUnitBlocked[aUnitType];
end;
procedure TKMHandLocks.SetUnitBlocked(aIsBlocked: Boolean; aUnitType: TKMUnitType; aInTownHall: Boolean = False);
begin
fUnitBlocked[aUnitType] := aIsBlocked;
end;
procedure TKMHandLocks.Save(SaveStream: TKMemoryStream);
begin
SaveStream.PlaceMarker('HandLocks');
SaveStream.Write(fHandHouseLock, SizeOf(fHandHouseLock));
SaveStream.Write(fUnitBlocked, SizeOf(fUnitBlocked));
SaveStream.Write(AllowToTrade, SizeOf(AllowToTrade));
SaveStream.Write(fHouseUnlocked, SizeOf(fHouseUnlocked));
end;
procedure TKMHandLocks.Load(LoadStream: TKMemoryStream);
begin
LoadStream.CheckMarker('HandLocks');
LoadStream.Read(fHandHouseLock, SizeOf(fHandHouseLock));
LoadStream.Read(fUnitBlocked, SizeOf(fUnitBlocked));
LoadStream.Read(AllowToTrade, SizeOf(AllowToTrade));
LoadStream.Read(fHouseUnlocked, SizeOf(fHouseUnlocked));
end;
end.
File diff suppressed because it is too large. Load diff
+430
View File
@@ -0,0 +1,430 @@
unit KM_HandSpectator;
{$I KaM_Remake.inc}
interface
uses
KM_Hand, KM_FogOfWar,
KM_CommonClasses, KM_Defaults,
KM_HandEntity;
type
//Wrap to let choose FOW player sees (and let 1 player control several towns
//or several players to control 1 town in future)
TKMSpectator = class
private
fHandIndex: TKMHandID;
fHighlight: TKMHighlightEntity; //Unit/House/Group that is shown highlighted to draw players attention
fHighlightEnd: Cardinal; //Highlight has a short time to live
fHighlightDebug: TKMHighlightEntity; //Unit/House/Group that is shown highlighted to draw players attention
fHighlightDebug2: TKMHighlightEntity;
fHighlightDebug3: TKMHighlightEntity;
fHighlightRoute: TKMHighlightEntity;
fSelected: TKMHandEntity;
fLastSelected: TKMHandEntity;
fIsSelectedMyObj: Boolean; // We can select ally's house/unit
fLastSpecSelectedObjUID: array [0..MAX_HANDS-1] of Integer; //UIDs of last selected objects for each hand while spectating/watching replay
fFOWIndex: TKMHandID; //Unit/House/Group selected by player and shown in UI
fFogOfWarOpen: TKMFogOfWarOpen; //Stub for MapEd
fFogOfWar: TKMFogOfWarCommon; //Pointer to current FOW view, updated by UpdateFogOfWarIndex
procedure SetHighlight(Value: TKMHandEntity);
procedure SetHighlightEntity(Value: TKMHighlightEntity);
procedure SetSelected(Value: TKMHandEntity);
function GetSelected: TKMHandEntity;
procedure SetHandIndex(const Value: TKMHandID);
procedure SetFOWIndex(const Value: TKMHandID);
procedure UpdateFogOfWarIndex;
function GetLastSpecSelectedEntity: TKMHandEntity;
procedure UpdateNewSelected(var aNewSelected: TKMHandEntity); overload;
function GetSelectedHandID: TKMHandID;
function GetHighlight: TKMHandEntity;
public
constructor Create(aHandIndex: TKMHandID);
destructor Destroy; override;
property Highlight: TKMHandEntity read GetHighlight write SetHighlight;
property HighlightEntity: TKMHighlightEntity read fHighlight write SetHighlightEntity;
property HighlightDebug: TKMHighlightEntity read fHighlightDebug write fHighlightDebug;
property HighlightDebug2: TKMHighlightEntity read fHighlightDebug2 write fHighlightDebug2;
property HighlightDebug3: TKMHighlightEntity read fHighlightDebug3 write fHighlightDebug3;
property HighlightRoute: TKMHighlightEntity read fHighlightRoute write fHighlightRoute;
property Selected: TKMHandEntity read GetSelected write SetSelected;
property LastSelected: TKMHandEntity read fLastSelected;
procedure NilLastSelected;
property IsSelectedMyObj: Boolean read fIsSelectedMyObj write fIsSelectedMyObj;
function Hand: TKMHand;
property HandID: TKMHandID read fHandIndex write SetHandIndex;
property SelectedHandID: TKMHandID read GetSelectedHandID;
property FOWIndex: TKMHandID read fFOWIndex write SetFOWIndex;
property FogOfWar: TKMFogOfWarCommon read fFogOfWar;
property LastSpecSelectedEntity: TKMHandEntity read GetLastSpecSelectedEntity;
function HitTestCursor(aIncludeAnimals: Boolean = False): TKMHandEntity;
function HitTestCursorWGroup(aIncludeAnimals: Boolean = False): TKMHandEntity;
procedure UpdateNewSelected; overload;
procedure UpdateSelect(aCheckUnderCursor: Boolean = True; aAllowAnimals: Boolean = False);
procedure Load(LoadStream: TKMemoryStream);
procedure Save(SaveStream: TKMemoryStream);
procedure UpdateState(aTick: Cardinal);
procedure ResetHighlightDebug;
end;
implementation
uses
KM_Entity, KM_HandEntityHelper,
KM_GameParams, KM_Cursor,
KM_HandsCollection, KM_HandTypes,
KM_Units, KM_UnitGroup, KM_UnitWarrior, KM_Houses,
KM_CommonUtils,
KM_GameTypes,
KM_Main;
{ TKMSpectator }
constructor TKMSpectator.Create(aHandIndex: TKMHandID);
var
I: Integer;
begin
inherited Create;
fHandIndex := aHandIndex;
fHighlight.Color := DEFAULT_HIGHLIGHT_COL;
fHighlightDebug.Color := DEFAULT_HIGHLIGHT_COL;
fHighlightDebug2.Color := DEFAULT_HIGHLIGHT_COL;
fHighlightRoute.Color := DEFAULT_HIGHLIGHT_COL;
//Stub that always returns REVEALED
fFogOfWarOpen := TKMFogOfWarOpen.Create;
UpdateFogOfWarIndex;
for I := Low(fLastSpecSelectedObjUID) to High(fLastSpecSelectedObjUID) do
fLastSpecSelectedObjUID[I] := UID_NONE;
end;
destructor TKMSpectator.Destroy;
begin
Highlight := nil;
Selected := nil;
fFogOfWarOpen.Free;
inherited;
end;
procedure TKMSpectator.UpdateFogOfWarIndex;
begin
//fGame = nil in Tests
if (gGameParams <> nil) and (gGameParams.Mode in [gmMultiSpectate, gmMapEd, gmReplaySingle, gmReplayMulti]) then
if FOWIndex = -1 then
fFogOfWar := fFogOfWarOpen
else
fFogOfWar := gHands[FOWIndex].FogOfWar
else
fFogOfWar := gHands[HandID].FogOfWar;
end;
function TKMSpectator.GetHighlight: TKMHandEntity;
begin
Result := fHighlight.Entity;
end;
//Return last seleted object for current chosen hand
function TKMSpectator.GetLastSpecSelectedEntity: TKMHandEntity;
var
entity: TKMHandEntity;
UID: Integer;
begin
Result := nil;
UID := fLastSpecSelectedObjUID[fHandIndex];
if UID <> UID_NONE then
begin
entity := gHands.GetObjectByUID(UID);
if (entity <> nil) and entity.IsSelectable then
Result := entity
else
fLastSpecSelectedObjUID[fHandIndex] := UID_NONE; // Last selected object is not valid anymore, so reset UID
end;
end;
function TKMSpectator.GetSelected: TKMHandEntity;
begin
if Self = nil then Exit(nil);
Result := fSelected;
end;
function TKMSpectator.GetSelectedHandID: TKMHandID;
begin
Result := fHandIndex;
if Self = nil then Exit;
if fSelected <> nil then
Result := fSelected.Owner;
end;
procedure TKMSpectator.Load(LoadStream: TKMemoryStream);
begin
LoadStream.CheckMarker('Spectator');
LoadStream.Read(fHandIndex);
UpdateFogOfWarIndex;
end;
procedure TKMSpectator.Save(SaveStream: TKMemoryStream);
begin
SaveStream.PlaceMarker('Spectator');
SaveStream.Write(fHandIndex);
end;
function TKMSpectator.Hand: TKMHand;
begin
if Self = nil then Exit(nil);
Result := gHands[fHandIndex];
end;
//Test if there's object below that player can interact with
//Units and Houses, not Groups
function TKMSpectator.HitTestCursor(aIncludeAnimals: Boolean = False): TKMHandEntity;
begin
Result := gHands.GetUnitByUID(gCursor.ObjectUID);
if ((Result is TKMUnit) and TKMUnit(Result).IsDeadOrDying)
or ((Result is TKMUnitAnimal) and not aIncludeAnimals) then
Result := nil;
//If there's no unit try pick a house on the Cell below
if Result = nil then
begin
Result := gHands.HousesHitTest(gCursor.Cell.X, gCursor.Cell.Y);
if (Result is TKMHouse) and TKMHouse(Result).IsDestroyed then
Result := nil;
end;
end;
//Test if there's object below that player can interact with
//Units and Houses and Groups
function TKMSpectator.HitTestCursorWGroup(aIncludeAnimals: Boolean = False): TKMHandEntity;
var
G: TKMUnitGroup;
begin
Result := HitTestCursor(aIncludeAnimals);
if Result is TKMUnitWarrior then
begin
if gGameParams.Mode in [gmMultiSpectate, gmMapEd, gmReplaySingle, gmReplayMulti] then
G := gHands.GetGroupByMember(TKMUnitWarrior(Result))
else
G := gHands[fHandIndex].UnitGroups.GetGroupByMember(TKMUnitWarrior(Result));
//Warrior might not be assigned to a group while walking out of the Barracks
if G <> nil then
Result := G
else
Result := nil; //Can't select warriors until they have been assigned a group
end;
end;
procedure TKMSpectator.UpdateNewSelected;
var
tmpSelected: TKMHandEntity;
begin
//We do not want to change Selected object actually, just update fIsSelectedMyObj field is good enough
tmpSelected := Selected;
UpdateNewSelected(tmpSelected);
end;
procedure TKMSpectator.UpdateNewSelected(var aNewSelected: TKMHandEntity);
begin
if gGameParams.Mode in [gmMultiSpectate, gmMapEd, gmReplaySingle, gmReplayMulti] then
Exit;
if aNewSelected.Owner <> -1 then
begin
if aNewSelected.Owner <> fHandIndex then // check if we selected our unit/ally's or enemy's
begin
if ALLOW_SELECT_ALL
or (aNewSelected.AllowAllyToSelect and (Hand.Alliances[aNewSelected.Owner] = atAlly)) then
fIsSelectedMyObj := False
else
aNewSelected := nil;
end else
fIsSelectedMyObj := True;
end;
end;
//Select anything player CAN select below cursor
procedure TKMSpectator.UpdateSelect(aCheckUnderCursor: Boolean = True; aAllowAnimals: Boolean = False);
var
newSelected: TKMHandEntity;
UID: Integer;
begin
newSelected := nil;
if aCheckUnderCursor then
begin
newSelected := gHands.GetUnitByUID(gCursor.ObjectUID);
//In-game player can select only own and ally Units
UpdateNewSelected(newSelected);
//Don't allow the player to select dead units
if ((newSelected is TKMUnit) and TKMUnit(newSelected).IsDeadOrDying)
or (not aAllowAnimals and (newSelected is TKMUnitAnimal)) then //...or animals
newSelected := nil;
//If Id belongs to some Warrior, try to select his group instead
if newSelected is TKMUnitWarrior then
begin
newSelected := gHands.GetGroupByMember(TKMUnitWarrior(newSelected));
UpdateNewSelected(newSelected);
end;
//Update selected groups selected unit
if newSelected is TKMUnitGroup then
TKMUnitGroup(newSelected).SelectedUnit := TKMUnitGroup(newSelected).MemberByUID(gCursor.ObjectUID);
//If there's no unit try pick a house on the Cell below
if newSelected = nil then
begin
newSelected := gHands.HousesHitTest(gCursor.Cell.X, gCursor.Cell.Y);
//In-game player can select only own and ally Units
if (newSelected is TKMHouse) then
UpdateNewSelected(newSelected);
//Don't allow the player to select destroyed houses
if (newSelected is TKMHouse) and TKMHouse(newSelected).IsDestroyed then
newSelected := nil;
end;
//Don't clear the old selection unless we found something new
if newSelected <> nil then
Selected := newSelected;
end
else
begin
newSelected := Selected; //To avoid nil-ing of fSelected
//In-game player can select only own and ally Units
UpdateNewSelected(newSelected); //Updates fIsSelectedMyObj
end;
// In a replay we want in-game statistics (and other things) to be shown for the owner of the last select object
if gGameParams.IsReplayOrSpectate then
begin
UID := UID_NONE;
if Selected is TKMHouse then
begin
HandID := TKMHouse(Selected).Owner;
UID := TKMHouse(Selected).UID;
end;
if Selected is TKMUnit then
begin
HandID := TKMUnit(Selected).Owner;
UID := TKMUnit(Selected).UID;
end;
if Selected is TKMUnitGroup then
begin
HandID := TKMUnitGroup(Selected).Owner;
UID := TKMUnitGroup(Selected).UID;
end;
if (Selected <> nil) and (UID <> UID_NONE) then
fLastSpecSelectedObjUID[fHandIndex] := UID;
end;
// Update F11 Debug panel UIDs
if gMain <> nil then
gMain.FormMain.SetEntitySelected(Selected.UID, Selected.AsGroup.SelectedUnit.UID);
end;
procedure TKMSpectator.SetFOWIndex(const Value: TKMHandID);
begin
fFOWIndex := Value;
UpdateFogOfWarIndex;
end;
procedure TKMSpectator.SetHighlight(Value: TKMHandEntity);
begin
//We don't increase PointersCount of object because of savegames identicality over MP
//Objects report on their destruction and set it to nil
fHighlight.Entity := Value;
fHighlight.Color := DEFAULT_HIGHLIGHT_COL;
fHighlightEnd := TimeGet + 3000;
end;
procedure TKMSpectator.SetHighlightEntity(Value: TKMHighlightEntity);
begin
fHighlight := Value;
SetHighlight(Value.Entity);
end;
procedure TKMSpectator.SetHandIndex(const Value: TKMHandID);
begin
Assert(MULTIPLAYER_CHEATS or (gGameParams.Mode <> gmMulti));
fHandIndex := Value;
if not (gGameParams.Mode in [gmMultiSpectate, gmMapEd, gmReplaySingle, gmReplayMulti]) then
Selected := nil;
UpdateFogOfWarIndex;
end;
procedure TKMSpectator.SetSelected(Value: TKMHandEntity);
begin
if Self = nil then Exit;
fLastSelected := fSelected;
//We don't increase PointersCount of object because of savegames identicality over MP
//Objects report on their destruction and set it to nil
fSelected := Value;
end;
procedure TKMSpectator.NilLastSelected;
begin
fLastSelected := nil;
end;
procedure TKMSpectator.ResetHighlightDebug;
begin
if Self = nil then Exit;
fHighlightDebug.Reset;
fHighlightDebug2.Reset;
fHighlightDebug3.Reset;
fHighlightRoute.Reset;
end;
procedure TKMSpectator.UpdateState;
begin
//Hide the highlight
if TimeGet > fHighlightEnd then
fHighlight.Reset;
//Units should be deselected when they go inside a house
if Selected is TKMUnit then
if not TKMUnit(Selected).Visible then
Selected := nil;
end;
end.
File diff suppressed because it is too large. Load diff
+36
View File
@@ -0,0 +1,36 @@
unit KM_HandTypes;
interface
uses
KM_ResFonts;
type
TKMHandType = (
hndHuman,
hndComputer
);
TKMHandEntityType = (etNone, etUnit, etGroup, etHouse);
//* House lock state
TKMHandHouseLock = (
hlNone,
hlDefault,
hlBlocked, // Never allowed
hlGranted // Always allowed
);
TKMOverlayTextSettings = record
WordWrap: Boolean;
Font: TKMFont;
end;
const
HAND_NONE = -1; //No player
HAND_ANIMAL = -2; //animals
const
DELIVERY_NO_ID = -1;
implementation
end.
File diff suppressed because it is too large. Load diff
+143
View File
@@ -0,0 +1,143 @@
unit KM_WareDistribution;
{$I KaM_Remake.inc}
interface
uses
KM_ResTypes,
KM_CommonClasses;
const
// These have been adjusted slightly from the old KaM defaults.
// The number means how many items should be in houses input max, and also affects delivery priority.
DISTRIBUTION_DEFAULTS: array [1..4, 1..4] of Byte = (
(5, 5, 0, 0),
(3, 5, 3, 3),
(2, 5, 0, 0),
(4, 5, 3, 0)
);
type
TKMWareDistribution = class
private
fWareDistribution: array [1..4, 1..4] of Byte;
procedure SetWareDistribution(aWare: TKMWareType; aHouse: TKMHouseType; aValue: Byte);
function GetWareDistribution(aWare: TKMWareType; aHouse: TKMHouseType): Byte;
public
constructor Create;
property WareDistribution[aWare: TKMWareType; aHouse: TKMHouseType]: Byte read GetWareDistribution write SetWareDistribution; default;
procedure LoadFromStr(aString: String);
function PackToStr: String;
procedure Save(SaveStream: TKMemoryStream);
procedure Load(LoadStream: TKMemoryStream);
end;
implementation
uses
SysUtils, Math;
{TKMWareDistribution}
constructor TKMWareDistribution.Create;
var
I, K: Integer;
begin
for I := 1 to 4 do
for K := 1 to 4 do
fWareDistribution[I, K] := DISTRIBUTION_DEFAULTS[I, K]; // Load default ratios
end;
procedure TKMWareDistribution.SetWareDistribution(aWare: TKMWareType; aHouse: TKMHouseType; aValue: Byte);
begin
case aWare of
wtIron: if aHouse = htWeaponSmithy then fWareDistribution[1,1] := aValue else
if aHouse = htArmorSmithy then fWareDistribution[1,2] := aValue;
wtCoal: if aHouse = htIronSmithy then fWareDistribution[2,1] := aValue else
if aHouse = htMetallurgists then fWareDistribution[2,2] := aValue else
if aHouse = htWeaponSmithy then fWareDistribution[2,3] := aValue else
if aHouse = htArmorSmithy then fWareDistribution[2,4] := aValue;
wtTimber: if aHouse = htArmorWorkshop then fWareDistribution[3,1] := aValue else
if aHouse = htWeaponWorkshop then fWareDistribution[3,2] := aValue;
wtCorn: if aHouse = htMill then fWareDistribution[4,1] := aValue else
if aHouse = htSwine then fWareDistribution[4,2] := aValue else
if aHouse = htStables then fWareDistribution[4,3] := aValue;
else raise Exception.Create('Unexpected resource at SetWareDistribution');
end;
end;
function TKMWareDistribution.GetWareDistribution(aWare: TKMWareType; aHouse: TKMHouseType): Byte;
begin
Result := 5; //Default should be 5, for house/resource combinations that don't have a setting (on a side note this should be the only place the resourse limit is defined)
case aWare of
wtIron: if aHouse = htWeaponSmithy then Result := fWareDistribution[1,1] else
if aHouse = htArmorSmithy then Result := fWareDistribution[1,2];
wtCoal: if aHouse = htIronSmithy then Result := fWareDistribution[2,1] else
if aHouse = htMetallurgists then Result := fWareDistribution[2,2] else
if aHouse = htWeaponSmithy then Result := fWareDistribution[2,3] else
if aHouse = htArmorSmithy then Result := fWareDistribution[2,4];
wtTimber: if aHouse = htArmorWorkshop then Result := fWareDistribution[3,1] else
if aHouse = htWeaponWorkshop then Result := fWareDistribution[3,2];
wtCorn: if aHouse = htMill then Result := fWareDistribution[4,1] else
if aHouse = htSwine then Result := fWareDistribution[4,2] else
if aHouse = htStables then Result := fWareDistribution[4,3];
else //Handled in 1st row to avoid repeating in if .. else lines
end;
end;
procedure TKMWareDistribution.LoadFromStr(aString: String);
function IsValid: Boolean;
var
I: Integer;
begin
Result := Length(aString) = 16; // Ware distribution string length should be equal to 16
if Result then
for I := 1 to 16 do
Result := Result and InRange(Ord(aString[I]), 48, 53); //In ware distribution string only digits from 0 to 5 are allowed'
end;
var
I, J: Integer;
begin
aString := Trim(aString); // Trim possible spaces
//Distribution format is string of 16 digits, each digit should be between 0 and 5
if IsValid then
for I := 1 to 16 do
fWareDistribution[1+((I-1) div 4), 1+((I-1) mod 4)] := StrToInt(aString[I])
else
for I := 1 to 4 do
for J := 1 to 4 do
fWareDistribution[I, J] := DISTRIBUTION_DEFAULTS[I, J];
end;
function TKMWareDistribution.PackToStr: String;
var
I, J: Integer;
begin
Result := '';
for I := 1 to 4 do
for J := 1 to 4 do
Result := Result + IntToStr(fWareDistribution[I, J]);
end;
procedure TKMWareDistribution.Save(SaveStream: TKMemoryStream);
begin
SaveStream.PlaceMarker('WareDistribution');
SaveStream.Write(fWareDistribution, SizeOf(fWareDistribution));
end;
procedure TKMWareDistribution.Load(LoadStream: TKMemoryStream);
begin
LoadStream.CheckMarker('WareDistribution');
LoadStream.Read(fWareDistribution, SizeOf(fWareDistribution));
end;
end.