Lazarus

Programming => LCL => Topic started by: S4MD!ng on February 10, 2019, 01:15:43 am

Title: Controlling Multiple Objects
Post by: S4MD!ng on February 10, 2019, 01:15:43 am
Hi Guys

I've only just learnt how to implement classes in my projects but there si 2 things that i need to do but havent figured out how to
1) Be able to destroy the shape when Life = 0 in the code below:
Code: Pascal  [Select][+][-]
  1. Procedure Shape.LifeReduction();
  2. begin
  3.   Life        :=Life-1;
  4.   if Life      = 0 then
  5.   begin
  6.     Shape.Destroy;
  7.   end;
  8. end;
(This gives me the error [Identifier idents no member "Destroy"])

2) Is there a way to be able call a certain procedure for every object of a certain type?
    like, for example in this code, move every object in the Shape Class 10 units up?
Here is a copy of the entire unit;
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
  9.  
  10. type
  11.  
  12.   Shape = class(TShape) //Inherits all the procedures, functions and variables of the class TShape
  13.     Private
  14.       X1, Y1:Integer;   //Current Position
  15.       Gradient1:Real;   //Gradient Number of the straight nine between it and the target point
  16.       Life:Integer;     //Life Total (Degrades and will delete the shape when reaches 0)
  17.     Public
  18.       Constructor Create(AOwner: TComponent); Override;      //Overrides the Base classes Constructor maybe?
  19.       Procedure Gradient(X2, Y2:integer);   //Sets Gradient
  20.       Procedure LifeReduction();            //Controls Life Points
  21.   end;
  22.  
  23.   { TForm1 }
  24.  
  25.   TForm1 = class(TForm)
  26.     Button1: TButton;
  27.     Player: TShape;
  28.     procedure Button1Click(Sender: TObject);
  29.     procedure FormCreate(Sender: TObject);
  30.   private
  31.  
  32.   public
  33.  
  34.   end;
  35.  
  36. var
  37.   Form1: TForm1;
  38.  
  39. implementation
  40.  
  41. {$R *.lfm}
  42.  
  43. { TForm1 }
  44.  
  45. constructor Shape.Create(AOwner: TComponent);    //Don't know what (AOwner:TComponent) does???
  46. begin
  47.   inherited;                                     //Don't know what this does either
  48.   Self.Parent := TForm1(AOwner);
  49.   X1          :=Random(320);
  50.   Y1          :=Random(240);
  51.   Self.top    :=Y1;
  52.   Self.Left   :=X1;
  53.   Self.Height :=16;
  54.   Self.Width  :=16;
  55.   Life        :=10
  56. end;
  57.  
  58. Procedure Shape.Gradient(X2, Y2:integer);       //Obvious in its purpose
  59. begin
  60.   Gradient1   :=(Y2-Self.Y1)/(X2-Self.X1);
  61. end;
  62. Procedure Shape.LifeReduction();
  63. begin
  64.   Life        :=Life-1;
  65.   if Life      = 0 then
  66.   begin
  67.     Shape.Destroy;                              //Identifier Idents No Member "Destroy"
  68.   end;
  69. end;
  70.  
  71. procedure TForm1.FormCreate(Sender: TObject);
  72. var
  73.   s1: Shape;
  74. begin
  75.   s1          := Shape.Create(Self);
  76.   s1.Gradient(Player.Left, Player.Top);
  77. end;
  78.  
  79. procedure TForm1.Button1Click(Sender: TObject);  //Lets Me Create LOTS Of Shapes on the click of a button
  80. var
  81.   s1: Shape;
  82. begin
  83.   s1          := Shape.Create(Self);            //Self Refers to TForm1 (Creates the Shape)
  84.   s1.Gradient(Player.Left, Player.Top);         //Runs the Procedure Gradient Target Objects Coordinates
  85. end;
  86.  
  87. end.
  88.  
Title: Re: Controlling Multiple Objects
Post by: lucamar on February 10, 2019, 02:17:09 am
From specific to general:

1) Your Shape class doesn't have a Destroy method, hence the message. Add:

Code: Pascal  [Select][+][-]
  1. { in the interface }
  2. Shape = class(shape);
  3.   { rest of declarations }
  4. public
  5.   destructor Destroy; override;
  6. end;
  7.  
  8. { in the implementation }
  9. destructor Shape.Destroy;
  10. begin
  11.   inherited Destroy;
  12. end;

2) It is rather unusual for an object to destroy itself but if you insist on doing so, don't over-qualify the call (and this is valid for all method calls from inside their class); simply do:
Code: Pascal  [Select][+][-]
  1. Procedure Shape.LifeReduction();
  2. begin
  3.   Life        :=Life-1;
  4.   if Life      = 0 then
  5.   begin
  6.     Destroy;
  7.     {or at most: Self.Destroy}
  8.   end;
  9. end;

3) That classs name "Shape" is ... a bad one. The usual convention for types is to start with a "T" and to have a name somehow related to its inheritance and its purpose. In your case, then, something like p.e.
    TPlayerShape = class(TShape);

4) This is nitpicking but method names should be more pro-active, so instead of LifeReduction() use something like ReduceLife()

5) More nitpicking: Make Gradient a property with getter/setter methods and make it a TPoint

6) While we are at it, conventions dictate that private and protected fields are named starting with an "F", so:
Code: Pascal  [Select][+][-]
  1.       FX1, FY1:Integer;   //Current Position;
  2.       { I would use a TPoint for that: FCurrPos: TPoint }
  3.       FGradient:Real;   //Gradient Number of the straight nine between it and the target point
  4.       FLife:Integer;
  5.  


And I'll stop here; too much already, isn't?  :D
HTH
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 10, 2019, 02:45:46 am
Thanks for the help, I've gone through my code and made all the changes that you suggested about the general form of the code.
Would there be a way for the program to make it so that everytime i press TButton1, it calls the procedure ReduceLife() for every object in the class, so that after 10 button presses, it will just delete the oldest object in the class, and create a new one?
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 10, 2019, 03:10:09 am
Also, I tried using the gradient as a property as you suggested, however, this is the first time that i have used the properties and it gives me the error
"unit1.pas(25,65) Error: Illegal symbol for property access"
Code: Pascal  [Select][+][-]
  1. TArrow = class(TShape) //Inherits all the procedures, functions and variables of the class TShape
  2.     Private
  3.       FX1, FY1:Integer;   //Current Position ---Addendum 1 Use TPoint instead (No idea how to do this)
  4.       FGradient1:Real;   //Gradient Number of the straight nine between it and the target point
  5.       FLife:Integer;     //Life Total (Degrades and will delete the shape when reaches 0)
  6.     Public
  7.       Constructor Create(AOwner: TComponent); Override;
  8.       Destructor Destroy; Override;
  9.       Procedure SetGradient(X2, Y2:integer);   //Sets Gradient
  10.       Function GetGradient:Real;
  11.       Procedure ReduceLife();            //Controls Life Points
  12.       property Gradient: Real read GetGradient write SetGradient;
  13.   end;
How do I make this work properly?
Title: Re: Controlling Multiple Objects
Post by: jamie on February 10, 2019, 03:23:30 am
 you need to create the GetGradient and SetGradient code..

The GetGradient is the Function that returns the REAL

SetGradient is the Procedure that accepts a REAL

these are methods in your class, you need to specify the header in the Private section and
create the Body in the Implementation section of the UNIT.

 also, it is customary to place a "f" at the start of the names for these.

 You can not call standard functions / procedures from a property like that however, you can use a
proxy to do it and that would be the methods of the class I spoke of.
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 10, 2019, 04:09:21 am
I moved FSetGradient and FGetGradient to the private section. and implemented both of them, but i still get the same error -
"unit1.pas(25,67) Error: Illegal symbol for property access",
but this only occurs for the write part, not the read. whats the problem here.

Code: Pascal  [Select][+][-]
  1. type        //Conventions Dictate that we private and protected fields are names starting with an "F"
  2.  
  3.   { TArrow }
  4.  
  5.   TArrow = class(TShape) //Inherits all the procedures, functions and variables of the class TShape
  6.     Private
  7.       FX1, FY1:Integer;   //Current Position ---Addendum 1 Use TPoint instead
  8.       FGradient1:Real;   //Gradient Number of the straight nine between it and the target point
  9.       FLife:Integer;     //Life Total (Degrades and will delete the shape when reaches 0)
  10.       Procedure FSetGradient(X2, Y2:integer);   //Sets Gradient
  11.       Function FGetGradient:Real;
  12.     Public
  13.       Constructor Create(AOwner: TComponent); Override;
  14.       Destructor Destroy; Override;
  15.       Procedure ReduceLife();            //Controls Life Points
  16.       property Gradient: Real read FGetGradient write FSetGradient;
  17.   end;
  18.  
  19. {Then in the implementation}
  20.  
  21. procedure TArrow.FSetGradient(X2, Y2:Integer);
  22. begin
  23.    FGradient1:=(Y2-FY1)/(X2-FX1);
  24. end;
  25. function TArrow.FGetGradient: Real;       //Obvious in its purpose
  26. begin
  27.    GetGradient:=FGradient1
  28. end;
  29.  

Also, its probably a bit late for this but what is the point of using a property in the class, is there something I can do with it that I cant do with the functions alone or something?
Title: Re: Controlling Multiple Objects
Post by: Handoko on February 10, 2019, 04:14:21 am
Setter only takes 1 parameter:

https://stackoverflow.com/questions/8823609/how-to-give-setter-a-2nd-parameter-in-delphi
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 10, 2019, 04:31:24 am
That Seems to have done the trick, Thanks
Title: Re: Controlling Multiple Objects
Post by: lucamar on February 10, 2019, 11:29:42 am
Would there be a way for the program to make it so that everytime i press TButton1, it calls the procedure ReduceLife() for every object in the class, so that after 10 button presses, it will just delete the oldest object in the class, and create a new one?

Depends on how you're creating the objects, but assuming it's by calling TArrow.Create(Self) with Self being the form it may be something like:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i: Integer;
  4. begin
  5.   for i := 0 to ComponentCount - 1 do begin
  6.     if Components[i].InheritsFrom(TArrow) then
  7.       (Components[i] as TArrow).ReduceLife;
  8.   end;
  9. end;

One caveat: there is already a TArrow class in the LCL! It may not matter much if you never use it but it would be best if you find another name for your class :)


Re. making Gradient a property, my sugestion was that you make it a TPoint precisely to avoid this situation but I see now that it would complicate things a little. If you still need to set the gradient from two integers then add a method SetGradientTo(X, Y: integer), like you had before.
Title: Re: Controlling Multiple Objects
Post by: Thaddy on February 10, 2019, 01:42:43 pm
Apart from the TArrow issue, I also would not use the real type.. That's been deprecated a long time ago. Use single or double. real is already aliased to double, btw.

For legacy support we have real48. Real is historically a 48 bit type. That support is really only for legacy code that relies on a 48 bit width.
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 10, 2019, 06:12:47 pm
Well, I tried to implement the code to reduce all of the objects life at once, (It produced an error after the 10th, but I know why it did that.) as such I've put each object in an array with 11 places, and am shifting the array 1 place to the left  after each button press (which also creates a new object, adds it to the array etc...)
Im trying to call TDart.ReduceLife (I changed TArrow to TDart) for every object using
Code: Pascal  [Select][+][-]
  1. procedure QueueShift(Queue: Array of TDart);
  2. var
  3.   tmp : TDart;
  4.   i,j : Integer;
  5. begin
  6.   for j := 0 to Length(Queue) do         {This Part Here}
  7.     begin
  8.        (Queue[j]).ReduceLife();
  9.     end;
  10.  
  11.   tmp := Queue[0];
  12.   for i := 1 to Length(Queue) do
  13.     begin
  14.       Queue[i-1] := Queue[i];
  15.       Queue[Length(Queue)] := tmp;
  16.     end;
  17. end;

However, it doesn't seem to be deleting the objects and also, when I press Button 1, it gives me the error "raised exception class 'External SIGSEGV' I the line
Code: Pascal  [Select][+][-]
  1. FLife := FLife-1
"
How Would I fix this, and would the code do as intended, and shift each object 10 places, calling Reduce Life 10 times, and hence deleting the object.

Here is an updated version of the Code if that helps...

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
  9.  
  10. type        //Conventions Dictate that we private and protected fields are names starting with an "F"
  11.  
  12.   { TArrow }
  13.  
  14.   TDart = class(TShape) //Inherits all the procedures, functions and variables of the class TShape
  15.     Private
  16.       FX1, FY1:Integer;   //Current Position ---Addendum 1 Use TPoint instead
  17.       FGradient1:Real;   //Gradient Number of the straight nine between it and the target point
  18.       FLife:Integer;     //Life Total (Degrades and will delete the shape when reaches 0)
  19.       Procedure FSetGradient(M:Real);   //Sets Gradient  ---Addendum 2 The set function requires only 1 parameter.
  20.       Function FGetGradient:Real;
  21.     Public
  22.       Constructor Create(AOwner: TComponent); Override;
  23.       Destructor Destroy; Override;
  24.       Procedure ReduceLife();            //Controls Life Points
  25.       property Gradient: Real read FGetGradient write FSetGradient;
  26.   end;
  27.  
  28.   { TForm1 }
  29.  
  30.   TForm1 = class(TForm)
  31.     Button1: TButton;
  32.     Player: TShape;
  33.     procedure Button1Click(Sender: TObject);
  34.     procedure FormCreate(Sender: TObject);
  35.   private
  36.  
  37.   public
  38.  
  39.   end;
  40.  
  41. var
  42.   Form1: TForm1;
  43.   TDartQueue:array[0..15] of TDart;
  44. implementation
  45.  
  46. {$R *.lfm}
  47.  
  48. { TForm1 }
  49.  
  50.  
  51.  
  52. constructor TDart.Create(AOwner: TComponent);    //Don't know what (AOwner:TComponent) does???
  53. begin
  54.   inherited;                                     //Don't know what this does either
  55.   Self.Parent := TForm1(AOwner);
  56.   FX1         :=Random(320);
  57.   FY1         :=Random(240);
  58.   Self.top    :=FY1;
  59.   Self.Left   :=FX1;
  60.   Self.Height :=16;
  61.   Self.Width  :=16;
  62.   FLife        :=10
  63. end;
  64.  
  65. destructor TDart.Destroy;
  66. begin
  67.   inherited Destroy;
  68. end;
  69. procedure TDart.FSetGradient(M:Real);
  70. begin
  71.    FGradient1:=M;
  72. end;
  73. function TDart.FGetGradient: Real;       //Obvious in its purpose
  74. begin
  75.    FGetGradient:=FGradient1
  76. end;
  77. procedure TDart.ReduceLife();
  78. begin
  79.   FLife        := FLife-1;
  80.   if FLife      <= 0 then
  81.   begin
  82.     Destroy;                              //Identifier Idents No Member "Destroy"
  83.   end;
  84. end;
  85.  
  86. procedure TForm1.FormCreate(Sender: TObject);
  87. var
  88.   s1: TDart;
  89. begin
  90.   s1          := TDart.Create(Self);
  91.   s1.FSetGradient((Player.Top-s1.FY1)/(Player.Left-s1.FX1));
  92. end;
  93. procedure QueueShift(Queue: Array of TDart);
  94. var
  95.   tmp : TDart;
  96.   i,j : Integer;
  97. begin
  98.   for j := 0 to Length(Queue) do
  99.     begin
  100.        (Queue[j]).ReduceLife();
  101.     end;
  102.  
  103.   tmp := Queue[0];
  104.   for i := 1 to Length(Queue) do
  105.     begin
  106.       Queue[i-1] := Queue[i];
  107.       Queue[Length(Queue)] := tmp;
  108.     end;
  109. end;
  110.  
  111.  
  112.  
  113. procedure TForm1.Button1Click(Sender: TObject);  //Lets Me Create LOTS Of Shapes on the click of a button
  114. var
  115.   s1: TDart;
  116. begin
  117.   s1:= TDart.Create(Self);            //Self Refers to TForm1 (Creates the Shape)
  118.   s1.FSetGradient((Player.Top-s1.FY1)/(Player.Left-s1.FX1));         //Runs the Procedure Gradient Target Objects Coordinates
  119.   TDartQueue[15]:=s1;
  120.   QueueShift(TDartQueue);
  121. end;
  122.  
  123. end.
  124.  
Title: Re: Controlling Multiple Objects
Post by: lucamar on February 10, 2019, 07:37:03 pm
I'm not very sure about exactly what you're trying to do, but try this:
Code: Pascal  [Select][+][-]
  1. procedure QueueShift(Queue: Array of TDart);
  2. var
  3.   tmp : TDart;
  4.   i,j : Integer;
  5. begin
  6.   {Decrease life for each Dart object}
  7.   for j := 0 to High(Queue) do         {This Part Here}
  8.     Queue[j].ReduceLife();
  9.  
  10.   { This moves each Dart object one place down except
  11.       the first, which is moved to the top of the queue}
  12.   tmp := Queue[0];
  13.   for i := 1 to High(Queue) do
  14.     Queue[i-1] := Queue[i];
  15.   Queue[High(Queue)] := tmp;
  16. end;

I'm not satisfied with that, though, because as soon as any TDart object reaches FLife = 0 it gets destroyed, so any subsequent try to call to ReduceLife() will give you that SISSEGV exception. What would I do? Change TDart to this:

Code: Pascal  [Select][+][-]
  1.   TDart = class(TShape) //Inherits all the procedures, functions and variables of the class TShape
  2.     Private
  3.       FX1, FY1:Integer;   //Current Position ---Addendum 1 Use TPoint instead
  4.       FGradient1:Real;   //Gradient Number of the straight nine between it and the target point
  5.       FLife:Integer;     //Life Total (Degrades and will delete the shape when reaches 0)
  6.       Procedure SetGradient(M:Real);   //Sets Gradient  ---Addendum 2 The set function requires only 1 parameter.
  7.       Function GetGradient:Real;
  8.     Public
  9.       Constructor Create(AOwner: TComponent); Override;
  10.       Destructor Destroy; Override;
  11.       Procedure ReduceLife();            //Controls Life Points
  12.       property Life: Integer read FLife;
  13.       property Gradient: Real read FGetGradient write FSetGradient;
  14.   end;

Then implement ReduceLife() like this:
 
Code: Pascal  [Select][+][-]
  1. procedure TDart.ReduceLife();
  2. begin
  3.   if FLife > 0 then
  4.     Dec(FLife);
  5. end;

And QueueShift() would be like this:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.QueueShift(Queue: Array of TDart);
  2. var
  3.   tmp : TDart;
  4.   i,j : Integer;
  5. begin
  6.   for j := 0 to High(Queue) do begin
  7.     Queue[j].ReduceLife(); {Decrease life}
  8.     if Queue[j].Life = 0 then begin {Ended up dead?}
  9.       Queue[j].Free;                {free it up and ...}
  10.       {if it is any but the last, move all the others down}
  11.       if j < High(Queue) then
  12.         for i := j+1 to High(Queue) do
  13.           Queue[i-1] := Queue[i];
  14.       { ... and create a new one in the last position}
  15.       Queu[High(Queue)] := TDart.Create(Self)
  16.     end;
  17.   end;
  18. end;

Note that doing it this way implies some more changes in other places and there are other places where things are not quite as they should. I don't have much more time now but I'll try later to do a clean-and-tidy-up of the rest of the code. But go ahead and try to do it yourself too, how else can you learn? :)
Title: Re: Controlling Multiple Objects
Post by: User137 on February 10, 2019, 08:31:27 pm
If it's a visual where order doesn't matter (very common in games especially for particles and all moving objects), you can optimize it:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.QueueShift(Queue: Array of TDart);
  2. var j : Integer;
  3. begin
  4.   for j := High(Queue) downto 0 do begin
  5.     Queue[j].ReduceLife(); {Decrease life}
  6.     if Queue[j].Life = 0 then begin {Ended up dead?}
  7.       Queue[j].Free;                {free it up and ...}
  8.       { ... and create a new one in the same index}
  9.       Queue[j] := TDart.Create(Self);
  10.  
  11.       // Or if you want to just reduce the queue instead of making a new dart:
  12.       // Move last index to the free'd spot
  13.       // Queue[j]:=Queue[high(Queue)];
  14.       // And then shrink the array to discard the duplicate of last index
  15.       // setlength(Queue, high(Queue));
  16.       // Note: high(Queue) is same as length(Queue)-1
  17.       // Must go through array from last to first for this to work!
  18.     end;
  19.   end;
  20. end;
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 11, 2019, 12:32:43 am
In case this heps, ill give you some background information, basically, for my A Level coursework, im making a game, and an element of the game is a sort of doge the projectile minigame, where many Darts (TDart) move in specific patterns. The player, (which is a TImage that the player can controll) needs to avoid the darts.

At the moment:
I can create the darts

I need to implement:
Them moving around the screen in set patterns (for example moving up by 10 units)
Detecting whether they have collided with the player (Which shouldn't be that hard, its only ALevel so it doesnt have to be optimised, so i can just do this in a function of the class, )

And once each of the projectiles have moved a set distance, they are destroyed (mayber they loose all their velocity or something {This is what the ReduceLife subroutine is meant to do, not reduce the life of the player, but of each projectile, so eventually they are destroyed.})
Title: Re: Controlling Multiple Objects
Post by: jamie on February 11, 2019, 02:27:10 am
Then you'll need to get to know the TRect and its helper members to or use the lower level rect functions
for intersect,   Iswithin etc

 You'll also need to do a little trig and geometry because when you are trying to change direction of the
shooter and the one getting shot also needs to be able to move to avoid the shooter, you need to take into
account of G-forces and not allow them to be able to make sharp turns and instant takeoffs!

 So setting the max g force per object and then using that as one of the parameters for the calculation
to generate a S curve, bell curve etc..
Title: Re: Controlling Multiple Objects
Post by: S4MD!ng on February 11, 2019, 02:57:22 am
OK, so i decided to go back to basics and try and make something that would do something visual, so that i know that it is doing something. So i changes ReduceLife() to also move the TDart up by 10. also, everytime that i press TButton1, it creates a new TDart, adds it to an array, and then calls, Reduce life for each item in the array.

It works to some extent, when i press the button, it generates a Tshape, and moves it up by 10. but for every subsequent button press, it moves the fist object up by 10, but none of the others created by the same button that worked for the first one!
Code: Pascal  [Select][+][-]
  1. {Nothing has changes elsewhere}
  2. procedure TDart.ReduceLife();
  3. begin
  4.   if FLife > 0 then
  5.     Dec(FLife);      //decreases FLife
  6.     Self.Top:= Self.Top-10;
  7. end;
  8.  
  9. procedure TForm1.Button1Click(Sender: TObject);  //Lets Me Create LOTS Of Shapes on the click of a button
  10. var
  11.   s1: TDart;
  12.   j:integer;
  13. begin
  14.   s1:= TDart.Create(Self);            //Self Refers to TForm1 (Creates the Shape)
  15.   s1.FSetGradient((Player.Top-s1.FY1)/(Player.Left-s1.FX1));         //Runs the Procedure Gradient Target Objects Coordinates
  16.   TDartList[i] := s1;
  17.   for j := 0 to High(TDartList) do
  18.   begin
  19.     TDartList[j].ReduceLife;
  20.   end;
  21.   i:=i+1;
  22.   SetLength(TDartList, (High(TDartList)+1))
  23. end;
  24.  
  25. begin
  26.   i := 0;
  27.   SetLength(TDartList, 1);
  28. end.
Title: Re: Controlling Multiple Objects
Post by: jamie on February 11, 2019, 11:19:18 pm
The array is changing so the HIGH intrinsic function may not be work.

 Try using Length(TDartList)-1 instead of High(TDartList)
TinyPortal © 2005-2018