Always relevant ScriptedPawns in MonsterHunt

Discussions about GameTypes
Post Reply
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Always relevant ScriptedPawns in MonsterHunt

Post by Barbie »

and why it is not a good design.

I made a new thread because this has nothing to do with the teleporting of queen. In that thread was stated that in map Arden+fix1 the monster counter counts different depending on setting ScriptedPawns to "AlwaysRelevant" or not. I digged into why such can happen and found, that the setting of game's "Difficulty" also affects the number of existing monsters at game start. Sadly the wiki is still down so I have to derive the chain of events from the source code only: When an Actor enters the game, it is passed to function IsRelevant() of the GameInfo Actor. That one in turn asks the BaseMutator's function "AlwaysKeep()", if that Actor should always be kept. If true, that Actor stays in game and other Mutators are not asked about that. The original MonsterHunt code as well as MonsterHunt2Gold do so (see this post for details).
If the BaseMutator says that this Actor is not worth to be kept in every case (BaseMutator.AlwaysKeep() returns FALSE), the GameInfo's function IsRelevant() proceeds: the game difficulty level is compared with the Actor's bDifficulty setting:

Code: Select all

	if  (Difficulty==0 && !Other.bDifficulty0 )
	||  (Difficulty==1 && !Other.bDifficulty1 )
	||  (Difficulty==2 && !Other.bDifficulty2 )
	||  (Difficulty==3 && !Other.bDifficulty3 ) 
	...
		return False;
This means that depending on that bDifficulty some Actors only appear at certain game difficulty levels.

Coming back to that different Monster amount in map Arden+fix1 you'll probably anticipate what is the case there - yes, some monsters have some none default bDifficulty settings what leads to a different number of monsters depending on game's difficulty setting:

Code: Select all

Difficulty | Monster amount
0          | 168
1          | 168
2          | 170
3          | 170
4          | 171
Running that map with difficulty=4 (what means nothing is left out by difficulty check) has the same effect as setting ScriptedPawns to "AlwaysRelevant". The number of monsters is 171 then.

If a mapper is so silly to assign game flow relevant events to Actors with different bDifficulty values, players may get stuck depending on game's difficulty level. But this is not the case in map Arden+fix1. (Although there exists a Nali appearing only at difficulty level 0 and 1 and his death triggers a CreatureFactory, this is without consequences because that CreatureFactory has no SpawnPoints. :D ) If such would happen, it is a mistake of the mapper but not a reason to declare all ScripedPawns as AlwaysRelevant. Mapping has to follow the game design, but game behaviour shouldn't be changed because there may be broken maps.

Why isn't setting ScriptedPawns to AlwaysRelevant a good design then?
Two reasons: The first is that other mutators are not participated on the decision if a ScriptedPawn is relevant or not (with the consequences not to being passed to IsRelevant() and CheckReplacement()). The other reason is ignoring mapper's setting of appearance of monsters depending on game's difficulty level.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Always relevant ScriptedPawns in MonsterHunt

Post by JackGriffin »

I appreciate the detailed stating of your position. I always enjoy a conversation where I get to learn things.

I just wonder if it really matters. This hasn't been a problem for years, there are tons of replacement mutators that have all worked just great with both MH and MH2. There's a hundred+ mods that do what they need to do without trouble. In a way you are pointing out a problem that exists pretty much only with the way you want to do your replacements. I fully agree you are correct with your statement but you are campaigning for something that just isn't going to get fixed unless you do it. This is precisely why I have always released my code...you can take MH2 and do anything you want. Improve the hell out of it, that would make a few server admins quite happy. Is there room for improvement? Oh God yes...everything I've ever done is by trial and error and brute force testing. Look closely and there are many things that could be done so much better but that's why you have the source.

You are also underestimating how upset players got over Arden. I got so many emails and messages over the tentacles in the huts. It REALLY bothered players when they weren't there, even though they meant nothing. They probably still exist over at Herm's forum. It was his MHM group that did the testing and got bent over the monstercount conflict. (I'm looking at you POTS...)

Anyway no one is saying you are wrong. Your choices though are to:
- change your mod so it works with the existing servers and gets some use
- make a new MH that functions like you want it to
So long, and thanks for all the fish
Aldebaran
Masterful
Posts: 672
Joined: Thu Jan 28, 2016 7:30 pm

Re: Always relevant ScriptedPawns in MonsterHunt

Post by Aldebaran »

So if I remove "if(Other.IsA('ScriptedPawn')) return true;" in function AlwaysKeep in MH2Gold and set the servers difficulty to 4 at once it should produce the maximum monster count... EDIT: Yes it works!

It's great to see how this is arranged, thank you Barbie to have a look into this context.

It would be interesting to know if there are more differences in gameplay in monster hunt if difficulty is changed from 0 to 4. MH2Gold is not designed for bots (see readme), so this is omited, also I think bots difficulty is to be set separately in the config file. Perhaps the difficulty is map depended as I read this out from Barbie?
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Always relevant ScriptedPawns in MonsterHunt

Post by JackGriffin »

You really need Nels to comment on the bots. By far he's done the most work in getting them to function correctly. In fact his MH version doesn't begin from team deathmatch (players on one side, monsters on other) in order to let the bots work. It's very good too. The bots are quite smart.
So long, and thanks for all the fish
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Always relevant ScriptedPawns in MonsterHunt

Post by sektor2111 »

Meh, even if you have skill/difficulty 0 without making sure about relevance you will have some chances to break maps, Yeah, I had troubles here because Shrimp has a BAD option with shadow in server. I really don't need his original deal but STILL need relevance - mapper has not set nothing as for another case.
Next point - another map (and not the single) was one of those done with dinosaurs all counted with a default counter having 254. Map was fixed and over-fixed not because of monsters setup, but because some of them were gone. Even if you have difficulty 0 - for easy hunting, you can have all Monsters over there. I don't get these complaints for not using at least a CheckReplacement toward monster relevance, WHY NOT ?

SKILL dependent on Difficulty (which I have 3 - max for creatures in run-line) is capped by creature to the same 3 in some PreBeginPlay if I well recall. However by waiting and assigning a monster tracker for creature living shortly, this actor can boost creature after prebeginplay to whatever skill you need. But... exist a problem, tested by me in 2013 if I well recall, monster having real skill 7 will always fail target, it's actually dumber than sh!t. That's why Epic capped them at 3 because the aiming formula is crapped up and goes nowhere - some "modders" proved how much garbage can do with some whatever MonsterLord unable to aim player at all. Stupidity at full power. For Bot it's not the same story, Bot goes evil based on its own difficulty deal but Shrimp has found a way for giving some FALSE skill to monster which actually at value 2 it's USELESS processing some math entirely without purpose when Bot skill is being chosen.

Code: Select all

		case 2:
			DiffScale = 100;
			break;
...
S.Health = (S.Health * DiffScale) / 100; //3 * 100 / 100 = 3 - the same sh!t.
I did not modify too much what Shrimp did, but I moved this into BaseMutator for being called at once with relevance and getting rid of this useless parameter as long as it's known and won't need other processing, because here I had to cap mapping craps done to monsters so I added other codes where Shrimp was only blabbering especially at damage byte.
These are easy to figure without any WIKI, codes are there, READABLE so We can summarize pawn's existence and relevance. Let's say that I don't think that some pawn is removed when difficulty is 0 and not removed when difficulty is 4 if you do the right relevance deal - maybe admin wants an easy MH, but also HAVING Monsters. Why this removal ? Else 4 don't seems very logic

Code: Select all

var byte  Difficulty;									// 0=easy, 1=medium, 2=hard, 3=very hard.
Said GameInfo
Then Pawn

Code: Select all

if ( Level.Game != None )
		Skill += Level.Game.Difficulty; 
	Skill = FClamp(Skill, 0, 3);
What WIKI do we need to figure that difficulty of creature is limited at 3 regarding to some dumb value performed in run-line ? 4 is a non-sense. Where did you get this 4 from ? Just to pass that filter ? Seriously ? C'mon, IsRelevant can be helpful as well and monsters can be changed speeding up relevance checking and preventing those tests which are exhaustive at 3000 monsters anyway.
Also I'm not sure if I well recall about a HighDetail actor as not being removed in a LowDetail game - this might be entertaining to study. For all of you working with MH, I have ALL monsters even if difficulty is 0 or 1 - yeah, just because a "Return True" wherever is placed is VERY HELPFUL and I did not see many maps with monsters having that filter screwed and you can replace monsters anyway in other formula than default garbage based on CheckReplacement. Be serious, that's not an answer. We have a MonsterHunt game so definitely the purpose is to have monsters - ALL Monsters, doesn't matter which MH version I'm firing. No break and no crap regarding to difficulty setup. Want my reason for using difficulty 3 in run-line in all servers ignited by me ? I'm doing this for a few years, take a look at this:

Code: Select all

	if ( Skill > 2 )
		bLeadTarget = true;
	else if ( (Skill == 0) && (Health < 500) )
	{
		bLeadTarget = false;		
		ReFireRate = 0.75 * ReFireRate;
	}	

	if ( bIsBoss )
		Health = Health + 0.15 * Skill * Health;
This is ScriptedPawn root, skilled monsters are superior.
Reading Krall class:

Code: Select all

	if ( Skill == 0 )
		ProjectileSpeed *= 0.85;
	else if ( Skill > 2 )
	{
		bCanStrafe = true;
		ProjectileSpeed *= 1.1;
	}
Projectiles fired by a skilled Krall are faster than those fired by a NOOB Krall and Skilled Krall will strafe away being a bit more a bad-ass than a retarded one, that's why I'm using that byte and NOT because of relevance, relevance is always properly in my controllers and... I can replace monsters but right now that's history. Of course if you spawn monsters later in a low skill server is hard to boost them up, you have to recall these PreBeginPlay sequences or else no one can figure skilled monster. I failed once this chapter until I realized what I had to do - RUN-LINE. Without processing replacements game is more clear and smoother.

For OFF-Line MH (or ON-Line) in Bot company there are a few nice things doable but not many people are interested so I think these options are mine so far. All starts with rewriting movement code and continuing with some tweaks - to not forget wrapping a new Bot which works fine regarding to "goto" called often, no worries here. Perhaps another future version will use external statics and all that stuff for server-side code, but not today...
Not the last thing about Bot in MH, there is a small tweak making Bot to find even a less reachable MonsterWayPoint using... default Engine :lol2: .
I did a test map when I worked at XC_MonsterHunt - result was fascinating, in original MH Bot did not even move a finger, but he went mad in XC_MonsterHunt jumping at WayPoint which was entirely in air not on the ground reachable as usual. Of course engine has some limitations, some new code can be implemented (not done yet because I don't need that) but that's a story for Bot fans not for Bot haters. For over-sized maps over engine boundaries only messing up with pushers out of any NavigationPoint Bot can do something else it's gone...

These I think are the easy part.
Now I'm interested which part of C++ can be called from UScript even here in MH because I sat down thinking at this line which Higor wrote

Code: Select all

#exec _cpptext void StaticConstructor();
I would like to know what else can be used this way but I'm not sure if I can get some comprehensive answers.

Edit: And to not forget a few important things. If "I guess" well, in my even older MH2 controllers, I do have a relevance check, monsters replacements, and even using difficulty ZERO and ALL Monsters which mapper has set. The question is: What exactly doesn't work at monsters here ? And no, I'm disagree with relevance removal regarding to what you say. If Shrimp used that combined with a lousy idea that's another story. If a mapper has 303 Monsters in map but in your game you have only 290 definitely I won't use your "solution". Last time if mappers did not connected creatures with stuff it's probably because a monster connected to a critical thing (activate teleporter, switch playerstarts, open a door, firing a factory, etc, etc.) which is removed from map will break map, simple as that, all these are because of broken MH versions which you are happily planning and saying that relevance it's not a good design. What is a good design, making map to go broken ? Seriously ? Even ECoop controllers are doing a relevance check because there are many monsters are connected with stuff in SP/Coop maps, feel free to look at those things because they won't hurt you and try to do things as should. Some admins will never change original MonsterHunt and then if you are planning a monsters replacement mutator using default replacement, you'll fail - first problem is relevance from original MH,the second is collision deal, the third is probably Skaarj Weapon :lol: . However, replacements are doable even if mapper was moron or Admin wants only original MH - THIS IS your key not removing relevance because it's not a "good design", maybe you want to light me what a good design is in MH, where we do want all Monsters, not fragments, and we want them connected with map not dropped around as junks done last years in maps. When a map has 200 Creatures in primitive original MH, definitely your "improved" one should have the same and you should not make mapper to get mad.
Aldebaran wrote:So if I remove "if(Other.IsA('ScriptedPawn')) return true;" in function AlwaysKeep in MH2Gold
And you can move it into CheckReplacement - simple as that, and destroy stupid shadow deal - that's PLAYER stuff not server stuff.
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Always relevant ScriptedPawns in MonsterHunt

Post by Barbie »

Aldebaran wrote:It would be interesting to know if there are more differences in gameplay in monster hunt if difficulty is changed from 0 to 4.
There are some other side effects depending on Level's Difficulty. One is the noise some Actors produce, for example in Pawn.ChangedWeapon():

Code: Select all

if ( (Level.Game != None) && (Level.Game.Difficulty > 1) )
	MakeNoise(0.1 * Level.Game.Difficulty);
or Pawn's skill setting in PreBeginPlay():

Code: Select all

Skill += Level.Game.Difficulty;
Damage scaling in TeamCannon.Shoot():

Code: Select all

P.Damage *= (0.4 + 0.15 * Level.game.Difficulty);
Aldebaran wrote:Perhaps the difficulty is map depended
All Actors have a filter (defined in Actor.uc):

Code: Select all

var(Filter) float		  OddsOfAppearing; // 0-1 - chance actor will appear in relevant game modes.
// What kind of gameplay scenarios to appear in.
var(Filter) bool          bDifficulty0;  // Appear in difficulty 0.
var(Filter) bool          bDifficulty1;  // Appear in difficulty 1.
var(Filter) bool          bDifficulty2;  // Appear in difficulty 2.
var(Filter) bool          bDifficulty3;  // Appear in difficulty 3.
var(Filter) bool          bSinglePlayer; // Appear in single player.
var(Filter) bool          bNet;          // Appear in regular network play.
var(Filter) bool          bNetSpecial;   // Appear in special network play mode.
As you see, the bDifficultyX build a bit mask for appearance depending on game level's Difficulty. For MH-Arden+Fix1.Nali0 for example the settings are:
MH-Arden+Fix1.Nali0.Filter.jpg
MH-Arden+Fix1.Nali0.Filter.jpg (31.51 KiB) Viewed 2580 times
As you can see, he appears only at difficulty level 0 and 1.
sektor2111 wrote:

Code: Select all

if ( Level.Game != None )
		Skill += Level.Game.Difficulty; 
	Skill = FClamp(Skill, 0, 3);
difficulty of creature is limited at 3
Sorry to correct you here, but Pawn's Skill is limited, not the difficulty. This may sound quibblingly but "difficulty" is a property of game and "Skill" a property of Pawn. We should use the terms given by the code.
sektor2111 wrote:Where did you get this 4 from ? Just to pass that filter ? Seriously ?
Exactly, I wanted to proof that the filter is responsible for different amount of monsters. And the easiest way to do this was using a number out of the range of [0...3] for Difficulty.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Always relevant ScriptedPawns in MonsterHunt

Post by sektor2111 »

Barbie wrote:As you can see, he appears only at difficulty level 0 and 1.
That's YOUR problem, guys, not mine, I have this Nali even in difficulty 3 and I'll explain how to not waste this time in relevance check and everyone is there happily waiting to be hunted - I kill Nalis because are ugly as a toilette and I get some wick score because I don't give a damn on them.
If actor is passing bGameRelevant and AlwaysKeep, next one is CheckReplacement VIA IsRelevant. If nothing is found, checks are keep going adding other bits of executions which from me are really pointless.
These

Code: Select all

	if
	(	(Difficulty==0 && !Other.bDifficulty0 )
	||  (Difficulty==1 && !Other.bDifficulty1 )
	||  (Difficulty==2 && !Other.bDifficulty2 )
	||  (Difficulty==3 && !Other.bDifficulty3 )
	||  (!Other.bSinglePlayer && (Level.NetMode==NM_Standalone) ) 
	||  (!Other.bNet && ((Level.NetMode == NM_DedicatedServer) || (Level.NetMode == NM_ListenServer)) )
	||  (!Other.bNetSpecial  && (Level.NetMode==NM_Client)) )
		return False;

	if( bNoMonsters && (Pawn(Other) != None) && !Pawn(Other).bIsPlayer )
		return False;

	if( FRand() > Other.OddsOfAppearing )
		return False;
Are no longer executed if a positive result is returned earlier and creatures are there doesn't matter their "filter" - a stupidity after all - monsters and no monsters. I wanna see a CTF without Flags, this game is MonsterHunt and we have to made sure about monsters unless mappers will start some moron mapping without connecting nothing because creatures might be gone. Already some of them have no clue what is Event and what is Tag and how to connect them...
In 2016 I was about to re-write all GameInfo but... I decided to allow normal mutators to run else I could completely remove these stupid filters - it's just an useless processing for MonsterHunt, just for pain (- lol 254 counted monsters).
And yes, relevance is not a "final" function so it can be rewritten any time with a direct MH implementation, enforcing ScriptedPawn will except screwing up with shadow and all that junk useless for a server, that's why GameInfo it's good as it is in big parts...
Aldebaran
Masterful
Posts: 672
Joined: Thu Jan 28, 2016 7:30 pm

Re: Always relevant ScriptedPawns in MonsterHunt

Post by Aldebaran »

Ok thanx Barbie for the explanation. After changing difficulty here to 4 I played some very strong but also exciting matches. I would like to know what difficulties you all prefer in monster hunt.

I can understand both positions: sektor2111 wants to have all monsters in a map whatever difficulty server has. Barbie lets the mapper decide and the mapper should make a map that can be completed in any difficulty whatever monster counts in. So should the server fix something the mapper does in his own way? Perhaps here the monster hunt mod should allow the admin to decide what he wants (in a simple bool variable).
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Always relevant ScriptedPawns in MonsterHunt

Post by sektor2111 »

Aldebaran wrote:So should the server fix something the mapper does in his own way?
In its own way means to not mock servers, my monster is not shown here but it's shown there or even NOT shown. If you want a random monster in map, solution it's already done without to mess with pawn's setup - self person here as a dumb sample. A code from a map which I tweaked/changed because it was pretty much lousy designed:

Code: Select all

if ( FRand() > 0.4 )
		{
			NewRot.Yaw=-30232;
			CompleteNewMonster(SetNewMonster(Spawn(class'RockTentacle',,,vect(-862,-1770,-8),NewRot),3500,,True),'Hide',3000,3,-0.5,3,,1,True);
		}
		else
		{
			NewRot.Yaw=-4832;
			CompleteNewMonster(SetNewMonster(Spawn(class'RockTentacle',,,vect(-1415,-1770,-8),NewRot),3500,,True),'Hide',3000,3,-0.5,3,,1,True);
		}
Code can be more simple but I want POWER. This monster can be in map in two spots randomly only if map is running in time interval Friday-Sunday. In the rest of days is not there. This is my way in randomization without jerking at admins. Tested in original MonsterHunt - default map goal, but works in multi MH server too...
User avatar
Shrimp
Adept
Posts: 273
Joined: Wed Oct 10, 2018 11:15 am
Location: Australia
Contact:

Re: Always relevant ScriptedPawns in MonsterHunt

Post by Shrimp »

18 years of hindsight and development experience will make any prior implementations look painfully bad. :tongue:
ShrimpWorks
Unreal Archive - preserving over 25 years of user-created content for the Unreal series!
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Always relevant ScriptedPawns in MonsterHunt

Post by sektor2111 »

Shrimp wrote:18 years of hindsight and development experience will make any prior implementations look painfully bad.
Actually the prior implementation of MonsterShadow located in server happily moved in client with modifications according to DrawScale and all that was enough good looking so far and running even in lousy machines. I gotta admit It was a full headache and I got no help from nowhere, and then ScoreKill foreaching for no reason while monster appearance not counted was another performance and finally I got Skaarj Working properly with AlarmPoints firing various actors, while in MH503 was like a myth, and the list with "normalized" stuff which I wrote keeps going, I ended at final stage to make people to use OLD weapons as they are without mooing at Skaarj, then XC age and several stuff learned made me to have these species X times better than in 2008, after learning how to ruin these bugs I could wrote custom DM CTF games which also are in Monster Gaming Server and everything it's working as on purpose - after 18-19 years of... whatever (I caught only 9-10 years with UScript).
The next explanations after 18 years - breath and hang on, or sit down to not fall - we can implement new Paths in maps with bad Bot Pathing right in run-time, Bot is no longer moron running in spot without purpose and so on... Actually more code could be removed because of newer SetEnemy changed with the new Engine extension or properly implemented in a conformed stock dedicated to ScriptedPawn... taa, daaa... :agree1:
The rest of relevance problems are not mine which are suddenly happening as in 2009-2012, I always had monsters in maps and I don't care if mapper was bitching at admins with lousy settings. I could do more but my Level is limited - I'm not a coder after all.

Edit:
1) Welcome to UT99.ORG, I was almost to forget this...
2) You can explain to these people what problems were encountered in prior versions (450, 500) and why relevance checking is good as it is - except SpawnPoint check which no one will remove because... it's bStatic... Other debates about MH are probably spam and OFF-Topic.
3) Excuse me for my grammar this is not a priority for me.
User avatar
Shrimp
Adept
Posts: 273
Joined: Wed Oct 10, 2018 11:15 am
Location: Australia
Contact:

Re: Always relevant ScriptedPawns in MonsterHunt

Post by Shrimp »

Unfortunately I cannot explain anything about problems encountered at any point in development; its so long ago I wouldn't even recognise the code or know what its doing at all... Would probably take a couple of months to re-learn UnrealScript from scratch before I even know what's going on :omfg:

I'm actually scared to look at it because I know how bad it probably looks...
ShrimpWorks
Unreal Archive - preserving over 25 years of user-created content for the Unreal series!
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Always relevant ScriptedPawns in MonsterHunt

Post by sektor2111 »

Shrimp wrote:Would probably take a couple of months to re-learn UnrealScript from scratch before I even know what's going on :omfg:

I'm actually scared to look at it because I know how bad it probably looks...
I can offer a warranty that any PC won't explode while Script is checked, else MonsterHunt is still game number 4 in all these years as "bad" as it is, Domination is behind MonsterHunt even it's an original one.

Dodging a bit with Barbie's permission toward thread - I have now another second reason to roam from time to time UT99.org at once with XC_Engine specific threads. If Sir Watson is here, for me this is a very Big Event - I go to drink something for you, and for "Relevance" idea...
In case that you are thinking at some Official Update, perhaps you will want to accept some hints toward main controller (especially in any PM mode).

Cheers, it's nice to see you joining here, eternal gratitude for maintaining alive that WEB space so many years.
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Always relevant ScriptedPawns in MonsterHunt

Post by JackGriffin »

Shrimp wrote: I'm actually scared to look at it because I know how bad it probably looks...
Before you get down on yourself in the slightest you need to know the joy you've brought to so damn many people. MH coding and development has been my hobby for years and it has provided a foundation for entire clans. A lot of people love that gametype and arguably it has kept UT afloat these past few years.

You did good.
So long, and thanks for all the fish
Post Reply