Browse Source

Fix style of `Iter` and all related classes

core_refactor
Anton Tarasenko 2 years ago
parent
commit
a6738c55ad
  1. 3
      sources/Aliases/Aliases.uc
  2. 86
      sources/BaseRealm/Iter.uc
  3. 13
      sources/Data/Collections/ArrayListIterator.uc
  4. 13
      sources/Data/Collections/HashTableIterator.uc
  5. 6
      sources/Data/Collections/Tests/TEST_Iterator.uc
  6. 48
      sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc
  7. 126
      sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc

3
sources/Aliases/Aliases.uc

@ -98,7 +98,8 @@ protected function ReadOtherSources(HashTable otherSourcesData)
local HashTableIterator iter; local HashTableIterator iter;
customSource.length = 0; customSource.length = 0;
iter = HashTableIterator(otherSourcesData.Iterate().LeaveOnlyNotNone()); iter = HashTableIterator(otherSourcesData.Iterate());
iter.LeaveOnlyNotNone();
for (iter = iter; !iter.HasFinished(); iter.Next()) for (iter = iter; !iter.HasFinished(); iter.Next())
{ {
key = iter.GetKey(); key = iter.GetKey();

86
sources/BaseRealm/Iter.uc

@ -1,7 +1,8 @@
/** /**
* Base class for iterator, an auxiliary object for iterating through * Author: dkanus
* a set of objects obtained from some context-dependent source. * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* Copyright 2022 Anton Tarasenko * License: GPL
* Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -21,56 +22,47 @@
class Iter extends AcediaObject class Iter extends AcediaObject
abstract; abstract;
/** //! Base class for iterator, an auxiliary object for iterating through
* Iterators can filter objects they're iterating on by a presence or lack of //! a set of objects obtained from some context-dependent source.
* a certain property, recording this choice requires 3 values, so `bool`
* isn't enough and we need to use this `enum` instead. /// Status of the [`Iter`]'s filter regarding some specific property.
*/ ///
enum IterFilter /// [`Iter`]s can filter objects they're iterating on by the presence or lack of a certain property,
{ /// recording this choice requires 3 values (for requiring having/not having a certain property and
// We don't use relevant property for filtering /// for ignoring it).
ITF_Nothing, /// This enumeration is for inner purposes and is there to unify iterator implementations.
// Iterated objects must have that property enum IterFilter {
ITF_Have, /// We don't use relevant property for filtering
// Iterated objects must not have that property IF_Nothing,
ITF_NotHave /// Iterated objects must have that property
IF_Have,
/// Iterated objects must not have that property
IF_NotHave
}; };
/** /// Advances iterator to the next item.
* Makes iterator pick next item. ///
* Use `HasFinished()` to check whether you have iterated all of them. /// Makes iterator refer to the next item from the source and returns `true`, as long as the source
* /// of items isn't yet exhausted.
* @return Reference to caller `Iterator` to allow for method chaining. /// In case there's no more items, method has to return `false` and do nothing else.
*/ /// [`Iter::HasFinished()`] can also be used to check whether there are more items available.
public function Iter Next(); public function bool Next();
/** /// Returns value currently pointed to by an iterator.
* Returns current value pointed to by an iterator. ///
* /// Does not advance iteration: use [`Iter::Next()`] to pick next value.
* Does not advance iteration: use `Next()` to pick next value. ///
* /// In case iterator has already reached the end (and [`Iter::Next()``] returned `false`), this
* @return Current value being iterated over. If `Iterator()` has finished /// method will return `none`.
* iterating over all values or was not initialized - returns `none`. /// Note that depending on context `none` values can also be returned, use
* Note that depending on context `none` values can also be returned, /// [`Iter::LeaveOnlyNotNone()`] method to prevent that.
* use `LeaveOnlyNotNone()` method to prevent that.
*/
public function AcediaObject Get(); public function AcediaObject Get();
/** /// Checks if caller [`Iter`] has finished iterating.
* Checks if caller `Iterator` has finished iterating.
*
* @return `true` if caller `Iterator` has finished iterating or
* was not initialized. `false` otherwise.
*/
public function bool HasFinished(); public function bool HasFinished();
/** /// Makes caller iterator skip any `none` items during iteration.
* Makes caller iterator skip any `none` items during iteration. public function LeaveOnlyNotNone();
*
* @return Reference to caller `Iterator` to allow for method chaining.
*/
public function Iter LeaveOnlyNotNone();
defaultproperties defaultproperties {
{
} }

13
sources/Data/Collections/ArrayListIterator.uc

@ -1,6 +1,6 @@
/** /**
* Iterator for iterating over `ArrayList`'s items. * Iterator for iterating over `ArrayList`'s items.
* Copyright 2022 Anton Tarasenko * Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -41,30 +41,29 @@ public function bool Initialize(Collection relevantArray)
return true; return true;
} }
public function Iter LeaveOnlyNotNone() public function LeaveOnlyNotNone()
{ {
skipNoneReferences = true; skipNoneReferences = true;
return self;
} }
public function Iter Next() public function bool Next()
{ {
local int collectionLength; local int collectionLength;
if (!skipNoneReferences) if (!skipNoneReferences)
{ {
currentIndex += 1; currentIndex += 1;
return self; return HasFinished();
} }
collectionLength = relevantCollection.GetLength(); collectionLength = relevantCollection.GetLength();
while (currentIndex < collectionLength) while (currentIndex < collectionLength)
{ {
currentIndex += 1; currentIndex += 1;
if (!relevantCollection.IsNone(currentIndex)) { if (!relevantCollection.IsNone(currentIndex)) {
return self; return HasFinished();
} }
} }
return self; return HasFinished();
} }
public function AcediaObject Get() public function AcediaObject Get()

13
sources/Data/Collections/HashTableIterator.uc

@ -1,6 +1,6 @@
/** /**
* Iterator for iterating over `HashTable`'s items. * Iterator for iterating over `HashTable`'s items.
* Copyright 2022 Anton Tarasenko * Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -51,30 +51,29 @@ public function bool Initialize(Collection relevantArray)
return true; return true;
} }
public function Iter LeaveOnlyNotNone() public function LeaveOnlyNotNone()
{ {
skipNoneReferences = true; skipNoneReferences = true;
return self;
} }
public function Iter Next() public function bool Next()
{ {
local int collectionLength; local int collectionLength;
if (!skipNoneReferences) if (!skipNoneReferences)
{ {
hasNotFinished = relevantCollection.IncrementIndex(currentIndex); hasNotFinished = relevantCollection.IncrementIndex(currentIndex);
return self; return HasFinished();
} }
collectionLength = relevantCollection.GetLength(); collectionLength = relevantCollection.GetLength();
while (hasNotFinished) while (hasNotFinished)
{ {
hasNotFinished = relevantCollection.IncrementIndex(currentIndex); hasNotFinished = relevantCollection.IncrementIndex(currentIndex);
if (relevantCollection.IsSomethingByIndex(currentIndex)) { if (relevantCollection.IsSomethingByIndex(currentIndex)) {
return self; return HasFinished();
} }
} }
return self; return HasFinished();
} }
public function AcediaObject Get() public function AcediaObject Get()

6
sources/Data/Collections/Tests/TEST_Iterator.uc

@ -108,7 +108,8 @@ protected static function Test_ArrayList()
Issue("Iterator for empty `ArrayList` does not return `none` as" Issue("Iterator for empty `ArrayList` does not return `none` as"
@ "a current item."); @ "a current item.");
TEST_ExpectNone(iter.Get()); TEST_ExpectNone(iter.Get());
TEST_ExpectNone(iter.Next().Get()); iter.Next();
TEST_ExpectNone(iter.Get());
for (i = 0; i < default.items.length; i += 1) { for (i = 0; i < default.items.length; i += 1) {
array.AddItem(default.items[i]); array.AddItem(default.items[i]);
@ -142,7 +143,8 @@ protected static function Test_HashTable()
Issue("Iterator for empty `HashTable` does not return `none` as" Issue("Iterator for empty `HashTable` does not return `none` as"
@ "a current item."); @ "a current item.");
TEST_ExpectNone(iter.Get()); TEST_ExpectNone(iter.Get());
TEST_ExpectNone(iter.Next().Get()); iter.Next();
TEST_ExpectNone(iter.Get());
for (i = 0; i < default.items.length; i += 1) { for (i = 0; i < default.items.length; i += 1) {
array.SetItem(__().box.int(i), default.items[i]); array.SetItem(__().box.int(i), default.items[i]);

48
sources/Gameplay/BaseClasses/Frontend/World/EntityIterator.uc

@ -54,81 +54,61 @@ public function EPawn GetPawn();
/** /**
* Makes caller iterator skip any entities that do not support `EPawn` * Makes caller iterator skip any entities that do not support `EPawn`
* interface during iteration. * interface during iteration.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyPawns(); public function LeaveOnlyPawns();
/** /**
* Makes caller iterator skip any entities that support `EPawn` interface * Makes caller iterator skip any entities that support `EPawn` interface
* during iteration. * during iteration.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyNonPawns(); public function LeaveOnlyNonPawns();
/** /**
* Makes caller iterator skip any entities that are placeable (support * Makes caller iterator skip any entities that are placeable (support
* `EPlaceable` interface) in the game. * `EPlaceable` interface) in the game.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyPlaceables(); public function LeaveOnlyPlaceables();
/** /**
* Makes caller iterator skip any entities that are not placeable (don't * Makes caller iterator skip any entities that are not placeable (don't
* support `EPlaceable` interface) into the game world. * support `EPlaceable` interface) into the game world.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyNonPlaceables(); public function LeaveOnlyNonPlaceables();
/** /**
* Makes caller iterator skip any entities that are not visible in the game * Makes caller iterator skip any entities that are not visible in the game
* world. * world.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyVisible(); public function LeaveOnlyVisible();
/** /**
* Makes caller iterator skip any entities that are visible in the game * Makes caller iterator skip any entities that are visible in the game
* world. * world.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyInvisible(); public function LeaveOnlyInvisible();
/** /**
* Makes caller iterator skip any entities that are able to collide with other * Makes caller iterator skip any entities that are able to collide with other
* entities in the game world. * entities in the game world.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyColliding(); public function LeaveOnlyColliding();
/** /**
* Makes caller iterator skip any entities that are unable to collide with * Makes caller iterator skip any entities that are unable to collide with
* other entities in the game world. * other entities in the game world.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyNonColliding(); public function LeaveOnlyNonColliding();
/** /**
* Makes caller iterator skip any non-static entities that do not change over * Makes caller iterator skip any non-static entities that do not change over
* time, leaving only dynamic ones. * time, leaving only dynamic ones.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyStatic(); public function LeaveOnlyStatic();
/** /**
* Makes caller iterator skip any static entities that do not change over time. * Makes caller iterator skip any static entities that do not change over time.
*
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyDynamic(); public function LeaveOnlyDynamic();
/** /**
* Leaves only placeable entities that are located no further than `radius` * Leaves only placeable entities that are located no further than `radius`
@ -140,9 +120,8 @@ public function EntityIterator LeaveOnlyDynamic();
* close to. * close to.
* @param radius Maximum distance that entities are allowed to be away * @param radius Maximum distance that entities are allowed to be away
* from `location`. * from `location`.
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyNearby( public function LeaveOnlyNearby(
EPlaceable placeable, EPlaceable placeable,
float radius); float radius);
@ -155,9 +134,8 @@ public function EntityIterator LeaveOnlyNearby(
* @param location Location to which entities must be close to. * @param location Location to which entities must be close to.
* @param radius Maximum distance that entities are allowed to be away * @param radius Maximum distance that entities are allowed to be away
* from `location`. * from `location`.
* @return Reference to caller `EntityIterator` to allow for method chaining.
*/ */
public function EntityIterator LeaveOnlyNearbyToLocation( public function LeaveOnlyNearbyToLocation(
Vector location, Vector location,
float radius); float radius);
@ -166,7 +144,7 @@ public function EntityIterator LeaveOnlyNearbyToLocation(
* *
* `placeable` must have collisions enabled for any entity to touch it. * `placeable` must have collisions enabled for any entity to touch it.
*/ */
public function EntityIterator LeaveOnlyTouching(EPlaceable placeable); public function LeaveOnlyTouching(EPlaceable placeable);
defaultproperties defaultproperties
{ {

126
sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc

@ -2,7 +2,7 @@
* `EntityIterator` / `TracingIterator` implementation for `KF1_Frontend`. * `EntityIterator` / `TracingIterator` implementation for `KF1_Frontend`.
* Both iterators do essentially the same and can be implemented with * Both iterators do essentially the same and can be implemented with
* a single class. * a single class.
* Copyright 2022 Anton Tarasenko * Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -112,11 +112,11 @@ protected function Finalizer()
iterated = false; iterated = false;
// Clear conditions // Clear conditions
// ~ Simple conditions // ~ Simple conditions
pawnsFilter = ITF_Nothing; pawnsFilter = IF_Nothing;
placeablesFilter = ITF_Nothing; placeablesFilter = IF_Nothing;
collidingFilter = ITF_Nothing; collidingFilter = IF_Nothing;
visibleFilter = ITF_Nothing; visibleFilter = IF_Nothing;
staticFilter = ITF_Nothing; staticFilter = IF_Nothing;
// ~ Distance conditions // ~ Distance conditions
distanceCheck = false; distanceCheck = false;
_.memory.Free(shortestLimitation.placeable); _.memory.Free(shortestLimitation.placeable);
@ -162,7 +162,7 @@ public final function InitializeTracing(Vector start, Vector end)
} }
startPosition = start; startPosition = start;
endPosition = end; endPosition = end;
collidingFilter = ITF_Have; collidingFilter = IF_Have;
initialized = true; initialized = true;
tracingIterator = true; tracingIterator = true;
} }
@ -237,30 +237,30 @@ private final function bool ProcessActor(
if (allowedList.length > 0 && IsAllowed(nextActor, allowedList)) { if (allowedList.length > 0 && IsAllowed(nextActor, allowedList)) {
return false; return false;
} }
if (staticFilter == ITF_Have && !nextActor.bStatic) { if (staticFilter == IF_Have && !nextActor.bStatic) {
return false; return false;
} }
if (staticFilter == ITF_NotHave && nextActor.bStatic) { if (staticFilter == IF_NotHave && nextActor.bStatic) {
return false; return false;
} }
isCollidable = (nextActor.bCollideActors || (LevelInfo(nextActor) != none)); isCollidable = (nextActor.bCollideActors || (LevelInfo(nextActor) != none));
if (collidingFilter == ITF_Have && !isCollidable) { if (collidingFilter == IF_Have && !isCollidable) {
return false; return false;
} }
if (collidingFilter == ITF_NotHave && isCollidable) { if (collidingFilter == IF_NotHave && isCollidable) {
return false; return false;
} }
if (placeablesFilter == ITF_Have && nextActor.bWorldGeometry) { if (placeablesFilter == IF_Have && nextActor.bWorldGeometry) {
return false; return false;
} }
if (placeablesFilter == ITF_NotHave && !nextActor.bWorldGeometry) { if (placeablesFilter == IF_NotHave && !nextActor.bWorldGeometry) {
return false; return false;
} }
isVisible = IsActorVisible(nextActor); isVisible = IsActorVisible(nextActor);
if (visibleFilter == ITF_Have && !isVisible) { if (visibleFilter == IF_Have && !isVisible) {
return false; return false;
} }
if (visibleFilter == ITF_NotHave && isVisible) { if (visibleFilter == IF_NotHave && isVisible) {
return false; return false;
} }
if (distanceCheck && !IsCloseEnough(nextActor)) { if (distanceCheck && !IsCloseEnough(nextActor)) {
@ -395,7 +395,7 @@ private final function TryIterating()
return; return;
} }
core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance()); core = ServerLevelCore(class'ServerLevelCore'.static.GetInstance());
if (pawnsFilter == ITF_Have) { if (pawnsFilter == IF_Have) {
targetClass = class'Pawn'; targetClass = class'Pawn';
} }
else { else {
@ -456,7 +456,7 @@ private final function DoIterate_PickBest(
// preferable, but only doable if we're also filtering by distance. // preferable, but only doable if we're also filtering by distance.
if (distanceCheck) if (distanceCheck)
{ {
if (collidingFilter == ITF_Have) { if (collidingFilter == IF_Have) {
DoIterate_Colliding(core, targetClass); DoIterate_Colliding(core, targetClass);
} }
else { else {
@ -466,7 +466,7 @@ private final function DoIterate_PickBest(
return; return;
} }
// If above fails - try to at least limit iteration to dynamic actors // If above fails - try to at least limit iteration to dynamic actors
if (staticFilter == ITF_NotHave) if (staticFilter == IF_NotHave)
{ {
DoIterate_Dynamic(core, targetClass); DoIterate_Dynamic(core, targetClass);
return; return;
@ -581,14 +581,13 @@ private final function DoIterate_All(
} }
} }
public function Iter Next() public function bool Next()
{ {
if (!initialized) { if (!initialized) {
return self; return false;
} }
TryIterating();
currentIndex += 1; currentIndex += 1;
return self; return HasFinished();
} }
public function AcediaObject Get() public function AcediaObject Get()
@ -655,11 +654,10 @@ public function bool HasFinished()
return (currentIndex >= foundActors.length); return (currentIndex >= foundActors.length);
} }
public function Iter LeaveOnlyNotNone() public function LeaveOnlyNotNone()
{ {
// We cannot iterate over `none` actors with native iterators, so this // We cannot iterate over `none` actors with native iterators, so this
// condition is automatically satisfied // condition is automatically satisfied
return self;
} }
private final function UpdateFilter( private final function UpdateFilter(
@ -669,8 +667,8 @@ private final function UpdateFilter(
if (!initialized) return; if (!initialized) return;
if (iterated) return; if (iterated) return;
if (actualValue == ITF_Nothing) { if (actualValue == IF_Nothing) {
actualValue = ITF_Have; actualValue = IF_Have;
} }
else if (actualValue != newValue) else if (actualValue != newValue)
{ {
@ -679,65 +677,54 @@ private final function UpdateFilter(
} }
} }
public function EntityIterator LeaveOnlyPawns() public function LeaveOnlyPawns()
{ {
UpdateFilter(pawnsFilter, ITF_Have); UpdateFilter(pawnsFilter, IF_Have);
return self;
} }
public function EntityIterator LeaveOnlyNonPawns() public function LeaveOnlyNonPawns()
{ {
UpdateFilter(pawnsFilter, IF_NotHave);
UpdateFilter(pawnsFilter, ITF_NotHave);
return self;
} }
public function EntityIterator LeaveOnlyPlaceables() public function LeaveOnlyPlaceables()
{ {
UpdateFilter(placeablesFilter, ITF_Have); UpdateFilter(placeablesFilter, IF_Have);
return self;
} }
public function EntityIterator LeaveOnlyNonPlaceables() public function LeaveOnlyNonPlaceables()
{ {
UpdateFilter(placeablesFilter, ITF_NotHave); UpdateFilter(placeablesFilter, IF_NotHave);
return self;
} }
public function EntityIterator LeaveOnlyVisible() public function LeaveOnlyVisible()
{ {
UpdateFilter(visibleFilter, ITF_Have); UpdateFilter(visibleFilter, IF_Have);
return self;
} }
public function EntityIterator LeaveOnlyInvisible() public function LeaveOnlyInvisible()
{ {
UpdateFilter(visibleFilter, ITF_NotHave); UpdateFilter(visibleFilter, IF_NotHave);
return self;
} }
public function EntityIterator LeaveOnlyColliding() public function LeaveOnlyColliding()
{ {
UpdateFilter(collidingFilter, ITF_Have); UpdateFilter(collidingFilter, IF_Have);
return self;
} }
public function EntityIterator LeaveOnlyNonColliding() public function LeaveOnlyNonColliding()
{ {
UpdateFilter(collidingFilter, ITF_NotHave); UpdateFilter(collidingFilter, IF_NotHave);
return self;
} }
public function EntityIterator LeaveOnlyStatic() public function LeaveOnlyStatic()
{ {
UpdateFilter(staticFilter, ITF_Have); UpdateFilter(staticFilter, IF_Have);
return self;
} }
public function EntityIterator LeaveOnlyDynamic() public function LeaveOnlyDynamic()
{ {
UpdateFilter(staticFilter, ITF_NotHave); UpdateFilter(staticFilter, IF_NotHave);
return self;
} }
private function AddDistanceLimitation(NearbyLimitation newLimitation) private function AddDistanceLimitation(NearbyLimitation newLimitation)
@ -761,15 +748,15 @@ private function AddDistanceLimitation(NearbyLimitation newLimitation)
} }
} }
public function EntityIterator LeaveOnlyNearby( public function LeaveOnlyNearby(
EPlaceable placeable, EPlaceable placeable,
float radius) float radius)
{ {
local NearbyLimitation newLimitation; local NearbyLimitation newLimitation;
if (!initialized) return self; if (!initialized) return;
if (iterated) return self; if (iterated) return;
if (placeable == none) return self; if (placeable == none) return;
placeable.NewRef(); placeable.NewRef();
newLimitation.placeable = placeable; newLimitation.placeable = placeable;
@ -777,35 +764,32 @@ public function EntityIterator LeaveOnlyNearby(
newLimitation.distance = radius; newLimitation.distance = radius;
newLimitation.distanceSquared = radius * radius; newLimitation.distanceSquared = radius * radius;
AddDistanceLimitation(newLimitation); AddDistanceLimitation(newLimitation);
return self;
} }
public function EntityIterator LeaveOnlyNearbyToLocation( public function LeaveOnlyNearbyToLocation(
Vector location, Vector location,
float radius) float radius)
{ {
local NearbyLimitation newLimitation; local NearbyLimitation newLimitation;
if (!initialized) return self; if (!initialized) return;
if (iterated) return self; if (iterated) return;
newLimitation.location = location; newLimitation.location = location;
newLimitation.distance = radius; newLimitation.distance = radius;
newLimitation.distanceSquared = radius * radius; newLimitation.distanceSquared = radius * radius;
AddDistanceLimitation(newLimitation); AddDistanceLimitation(newLimitation);
return self;
} }
public function EntityIterator LeaveOnlyTouching(EPlaceable placeable) public function LeaveOnlyTouching(EPlaceable placeable)
{ {
if (!initialized) return self; if (!initialized) return;
if (iterated) return self; if (iterated) return;
if (placeable == none) return self; if (placeable == none) return;
touchingCheck = true; touchingCheck = true;
placeable.NewRef(); placeable.NewRef();
touchers[touchers.length] = placeable; touchers[touchers.length] = placeable;
return self;
} }
defaultproperties defaultproperties

Loading…
Cancel
Save