Patch Requests

A general chat area, here you can post anything that doesn't belong in another forum.
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

This patch request is a little more complicated then some of the other things I've done.

I'd like a patch that makes the QED not end the game, and teleport you to Korath III instead. This patch is for the same mod as the cannon shot and Space Heater patches.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
nwe74
Grunt
Posts: 17
Joined: Sun Sep 18, 2022 18:29

Re: Patch Requests

Post by nwe74 »

The patch for modifying the destroyed QED to behave as a "teleporter" to the Korath III base is actually very simple:

Code: Select all

%patch $FFD0 $0D
This patch alone makes the game unwinnable, you will need another for winning the game. Is your previous request for the same mod?
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

Yes, all but the demo and 32 level patch are for Prison Escape.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

So I want to use in-level teleporters for Prison Escape, and I found this patch from the Alphamatic 2.0 that is supposed to mark the "zap" sprite from the teleporter as removable:

Code: Select all

#Teleport effects sprites are spawned as "removable":
%patch $FEB1 $C6 $47 $02 $03
%patch $FF04 $C6 $47 $02 $03
The problem is, this patch only works for the sprite at the top of the teleporter, not the big one at the bottom, and I can't find a patch in the Alphamatic patch file to fix this. Can someone please extend this patch for the other teleporter sprite as well?
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
User avatar
K1n9_Duk3
Vorticon Elite
Posts: 898
Joined: Mon Aug 25, 2008 9:30
Location: Germany
Contact:

Re: Patch Requests

Post by K1n9_Duk3 »

SodiumtheGlitcher wrote: Sat Aug 09, 2025 20:50 So I want to use in-level teleporters for Prison Escape, and I found this patch from the Alphamatic 2.0 that is supposed to mark the "zap" sprite from the teleporter as removable:

Code: Select all

#Teleport effects sprites are spawned as "removable":
%patch $FEB1 $C6 $47 $02 $03
%patch $FF04 $C6 $47 $02 $03
The problem is, this patch only works for the sprite at the top of the teleporter, not the big one at the bottom, and I can't find a patch in the Alphamatic patch file to fix this. Can someone please extend this patch for the other teleporter sprite as well?
To give this patch some proper context, here is what I told Gridlock when I sent him this patch back in September 2020:
[...] this patch should remove the effects sprites once they are far enough off-screen:

Code: Select all

# teleport effects sprites are spawned as "removable":
%patch $FEB1 $C6 $47 $02 $03
%patch $FF04 $C6 $47 $02 $03
This patch overwrites the code that would normally assign the object type ("obclass") to the effects object. The effects objects now have an obclass of 0, but that won't have any negative effects on these objects.

The teleporters need to be set up so that the teleporter that has just been entered can't be seen from the teleport destination. There must be more than 6 tiles between the visible area and the teleport effects sprites, otherwise they won't get removed and will just keep animating.
So just to be clear: the first patch (the one at $FEB1) makes the "teleport spark" (above the teleporter) removable. The second patch (the one at $FF04) makes the "teleport zap" (inside, where Keen enters the machine) removable.

I assure you that these patches are correct. But if they only seem to work for the small sprite at the top and not for the bigger sprites inside the machine, you are probably dealing with a level design issue and/or a misunderstanding of what making an object "removable" actually means.

Making the animations "removable" will have no effect if the sprites stay visible on the screen (or remain very close to the borders of the visible screen area) inside your level after Keen was teleported. You have to make sure that Keen gets teleported to a position where there are more than 6 tiles in between the effects and the visible screen around the destination of the teleporter.

---

A better solution might be to rewrite the effects states so that the animation will end after a fixed amount of time. But that requires additional code, which means some other code would need to be overwritten to make room for the new code. Finding code that can be overwritten without causing any issues is tricky, especially without having access to your full patch file, but I think I found a way to implement the new code where it should not cause any conflicts with other patches:

Code: Select all

%ext ck5
%version 1.4


#####################################################################
# FORCE TELEPORT ANIMATIONS TO REMOVE THEMSELVES AFTER 4 ITERATIONS #
#####################################################################

#simplify TEDDeath into Quit(NULL) to make room for new code:
%patch $3AF6
	$33 $C0			#	xor	ax, ax
	$50			#	push	ax
	$E8 $FF03w		#	call near ptr Quit	;will NEVER return!

#overwrite rest of TEDDeath with new counting "think" routine ($037D032CRL):
%patch $3AFC
	$55			#	push	bp
	$8B $EC			#	mov	bp, sp
	$8B $5E $06		#	mov	bx, [bp+6]
	$FF $47 $3E		#	inc	[bx+3Eh]
	$83 $7F $3E 4		#	cmp	[bx+3Eh], 4
	$75 $05			#	jne 	done
	$C7 $47 $1C $0000w	#	mov	[bx+1Ch], 0
	$5D			#done:	pop	bp
	$CB			#	retf

#make the animations use the think routine:
%patch $31DDC $037D032CRL	# when the top animation loops to the first frame
%patch $31E18 $037D032CRL	# when the bottom animation loops to the first frame

#####################################################################


%end
Each state of the teleport animation takes 6 tics and only the second state in each loop uses the new think function. That means the think function is executed after 12 tics. When the code is executed for the 4th time, it means 48 tics have elapsed since the animation was started. The animation for Keen entering the teleport consists of 5 states that take 9 tics each, which means 45 tics total.

That means the animations should remove themselves shortly after Keen actually gets teleported to the new location. The timing may vary slightly, depending on the frame rate (the effects will be updated for the first time 1 frame before Keen's entering animation gets updated for the first time), so it's possible that the teleport effects might vanish a little bit too early. If that bothers you, you can modify the patch to allow 5 or 6 iterations instead of just 4. Modify the 4 to the left of the "cmp [bx+3Eh]..." comment to do that.

Under some circumstances, it might actually be useful to let the animation run a little longer. If the players can see the teleporter from the position Keen gets teleported to, seeing that teleporter "cool down" can give them a visual hint as to where they came from and where they are now. It could help them not get lost in the level.

For those working with the source code:
The new think routine from the patch above is basically the same as the following C code:

Code: Select all

void T_EffectsCount(objtype *ob)
{
	ob->temp1++;
	if (ob->temp1 == 4)
	{
		ob->state = NULL;	// remove this object
	}
}
The object's temp1 field will always be set to 0 when the object is spawned. GetNewObj takes care of that, so no additional code is necessary to set it to 0 when spawning the effects objects. It's perfectly safe to count to 4 with this think routine.

Simply add the new function to the source code. It doesn't really matter where you put it, but I would add the new function in K5_ACT1.C, after the SpawnTeleport function.

Of course you will also need to make the effects states use this new think routine, so you'll need to modify them like this:

Code: Select all

void T_EffectsCount(objtype *ob);	// prototype, just in case...

statetype s_teleport1     = {TELEPORTSPARK1SPR, TELEPORTSPARK1SPR, step, false, false, 6, 0, 0, NULL, NULL, R_Draw, &s_teleport2};
statetype s_teleport2     = {TELEPORTSPARK2SPR, TELEPORTSPARK2SPR, step, false, false, 6, 0, 0, T_EffectsCount, NULL, R_Draw, &s_teleport1};
statetype s_teleportzap1  = {TELEPORTZAP1SPR,   TELEPORTZAP1SPR,   step, false, false, 6, 0, 0, NULL, NULL, R_Draw, &s_teleportzap2};
statetype s_teleportzap2  = {TELEPORTZAP2SPR,   TELEPORTZAP2SPR,   step, false, false, 6, 0, 0, T_EffectsCount, NULL, R_Draw, &s_teleportzap1};
The prototype could also be moved into the K5_DEF.H header file, but as long as that new function is not referenced anywhere else in the code, having the prototype in K5_ACT1.C works just as well.

Note that you should still be making the effects "removable" (or "always active") for this code, especially when you increase the number of animation loops to more than 4. To do that in C code, simply add the following line directly after each of the "GetNewObj(true);" lines in the SpawnTeleport function:

Code: Select all

	new->active = ac_removable;
This makes sure that the animation will not just pause when it's off-screen and then resume when it becomes visible again. I'm pretty sure you don't want the teleport animation of a teleporter you used 5 minutes ago to still be active for a few frames when you return to it.
Hail to the K1n9, baby!
http://k1n9duk3.shikadi.net
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

Thanks for the patch and the in-depth explanation! As someone with almost no patching knowledge, these were very helpful. And I somewhat understand the reason. I was testing this on the Gravitational Damping Hub, but your new patches work just fine.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

I'd like a patch for Keen 5 that controls how much ammo Keen starts the game with.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
Vortlike
Vortininja
Posts: 84
Joined: Thu Mar 28, 2024 18:51

Re: Patch Requests

Post by Vortlike »

Seeing your request about ammo at start surprised me because I assumed that keen 4-6 patching was more advanced than the vorticon patches. So I took a look at the wiki.

The wiki is fairly decently categorized but sometimes you have to search a little. I didn't find it right away.

If you go to patches on the keen wiki. then click "keen 5 patches" you can then see ammo as one of the categories.
It then takes you to a page for ammo for all keen games. And it appears at first it's only for keen 1-3. However if you look closely it says "Related patches that can affect the ammo counter are Patch:Level exit and Patch:Game start"

So you have to click "Patch:Game start"

Now you get a whole new page that does include keen 5. It actually also has keen 1-3 patches I didn't know about before.

But for keen 5 you will find:

Code: Select all

Keen 5
#Stuff Keen has at game start (Defaults)
%patch $5C8F {$6F54W} [$0000W] #Extra Keen at score (High word)
%patch $5C95 {$6F52W} [$4E20W] #Extra Keen at score (Low word)
%patch $5C9B {$6F6AW} [$0003W] #Lives (3)
%patch $5CA1 {$6F56W} [$0005W] #Ammo (5)
So that last line should be the one you want.
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

The reason I didn't find that patch was because I only checked the patches in the neural stunner section, and assumed that the patch would be there, but apparently not. Now I know for future reference.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

This patch request sounds ridiculous and is probably nowhere close to being feasible, but is there any way possible to make it so when a Shikadi Master teleports, it spawns another one somewhere in the level with the same teleporting settings as normal? The idea I had was that you would have to solve the level before the game crashed. If this is not possible, it's not that big of a deal. It's just a silly idea I came up with.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
User avatar
K1n9_Duk3
Vorticon Elite
Posts: 898
Joined: Mon Aug 25, 2008 9:30
Location: Germany
Contact:

Re: Patch Requests

Post by K1n9_Duk3 »

I think such a patch would be possible, but I wouldn't recommend doing something like that. Making the current level unwinnable based on random chance is just bad design. And if the level can crash the game, it's even worse.

Just think about this for a second from the player's perspective. You have spent a lot of time playing the game, but you forgot to save before entering the level or you saved inside the level. Now you find out that the level is unwinnable. You lose a lot of progress... because that's fun, right? If you only have one save slot, and that game was saved in an unwinnable state, your only option is to get Keen killed and restart the level (if you have lives left). But if the game crashes, you might not even have enough time to do that.
Hail to the K1n9, baby!
http://k1n9duk3.shikadi.net
Benvolio
Vorticon Elite
Posts: 1069
Joined: Sun May 29, 2011 12:43
Location: Ireland
Contact:

Re: Patch Requests

Post by Benvolio »

Yeah I think exploiting mechanisms that far outside intended engine gameplay will reduce entertainment value. Some glitches can have fun outcomes but a crash due to sprite overload is probably not one of them!

I'm sure timed levels can be done another way. If code that could do it in Xkykeen3 existed in Vorticons then I'm sure something could be source modded into Keen5.
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

Well if that's a bad idea, I've had an idea for a LONG time for a built-in timer, but only on 1 specific level. Would it be possible to make a patch that has a timer for a level (say level 12 specifically) that takes up the ammo position on the scorebox? (like the Boobus Bombs from Keen Dreams take up the ammo slot once you enter the boss level) If this is not possible, that means that another one of my older projects and this one will have to be timed in a different way. (like using Volte-faces like in the Alphamatic)
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
User avatar
K1n9_Duk3
Vorticon Elite
Posts: 898
Joined: Mon Aug 25, 2008 9:30
Location: Germany
Contact:

Re: Patch Requests

Post by K1n9_Duk3 »

I really don't feel like writing patches for this one, but here is how a timed level could be implemented in source code:

Step 1:
Add a countdown variable to the gametype struct in CK_DEF.H:

Code: Select all

typedef struct
{
	Uint16 worldx, worldy;
	boolean leveldone[GAMELEVELS];
	/*...*/
	Sint16 difficulty;
	objtype *riding;
	Sint32 countdown;	// <- add this line
} gametype;
And while you're at it, add the following macro definitions in the "GLOBAL CONSTANTS & MACROS" section of that file as well:

Code: Select all

#define COUNTDOWN_MAP 12	// map number that uses the countdown
#define COUNTDOWN_VAL 99999	// starting value of the countdown
The game logic runs on a 70 Hz timer, so a countdown value of 70 would mean 1 second. 99999 would mean 23 minutes and 48.56 seconds. Since you probably don't want to use the 99999 I used in this example, these macros just make your life easier.

These macros allow you to modify the map number or the countdown length in one place, instead of having to modify several parts of the code.

Step 2:
Let the PlayLoop() function in CK_PLAY.C handle the countdown. A good place to add it would be after the invincibility countdown code:

Code: Select all

		if (invincible)
		{
			if ((invincible = invincible - tics) < 0)
				invincible = 0;
		}
		
		/* add the following lines */
		if (gamestate.countdown)
		{
			gamestate.countdown -= tics;
			if (gamestate.countdown <= 0)
			{
				gamestate.countdown = 0;
				
				// use this if running out of time should count as a death:
				playstate = ex_died;
				
				// use this if running out of time should win the level:
				playstate = ex_completed;
				
				// use this if running out of time should abort the level:
				gamestate.mapon = 0;
				playstate = ex_warped;
			}
		}
There are several possible ways to handle the countdown running out. It depends on what you want to achieve with the countdown. I have listed a couple of possible alternatives in the code. You should only use one of the alternatives and delete the other ones.

Step 3:
Start the countdown when entering the level that should use a countdown. A good place to add the code for this would be the ScanInfoPlane() function in K?_SPEC.C. If you are modding Keen 5, modify the code in its K5_SPEC.C file as follows:

Code: Select all

	InitObjArray();                  // start spawning things with a clean slate

	memset(lumpneeded, 0, sizeof(lumpneeded));
	gamestate.numfuses = 0;
	
	/* add the following lines */
	if (mapon == COUNTDOWN_MAP)
	{
		gamestate.countdown = COUNTDOWN_VAL;
	}
	else
	{
		gamestate.countdown = 0;
	}
The compiler will automatically insert the values you have declared in CK_DEF.H.

Step 4:
When loading a saved game, the code will execute the ScanInfoPlane() function again, so you will need to edit the LoadTheGame() function in CK_GAME.C and make it create a backup of the countdown value to prevent the countdown from being restarted when loading a game that was saved within the level:

Code: Select all

boolean LoadTheGame(Sint16 handle)
{
	Uint16	i;
	objtype	*prev,*next,*followed;
	Uint16	compressed,expanded;
	memptr	bigbuffer;
#ifdef KEEN5
	Sint16	numfuses;
#endif
	Sint32	countdown;  // <- add this line

	if (!CA_FarRead(handle, (byte far *)&gamestate, sizeof(gamestate)))
		return false;

#ifdef KEEN5
	//
	// remember the fuses value for later - SetupGameLevel calls
	// ScanInfoPlane, which resets this part of the gamestate
	//
	numfuses = gamestate.numfuses;
#endif
	countdown = gamestate.countdown;  // <- add this line
	
	/*...*/
	
	scoreobj->temp2 = -1;
	scoreobj->temp1 = -1;
	scoreobj->temp3 = -1;
	scoreobj->temp4 = -1;
#ifdef KEEN5
	gamestate.numfuses = numfuses;	// put value from saved game back in place 
#endif
	gamestate.countdown = countdown;  // <- add this line
	return true;
}
Step 5:
If you want to add the countdown to the score box, you will need to open CK_KEEN2.C and edit the UpdateScore() function. Depending on the gameplay inside the timed level, some parts of the score box would be better options to replace with a timer value than others. For example, if there are no enemies in the level, the player doesn't need to shoot and the ammo number could be replaced. If there are no bonus items to collect inside the level, the score number could be replaced. If it's impossible to die in the level (and you edit the countdown code in PlayLoop() to let Keen win the level or warp back to the world map), you could replace the lives number.

The next aspect to consider is that you will need relatively large countdown values if you don't want the level to end within 2 seconds, so you would need more than 2 digits to display the countdown value. The countdown value needs to be converted to get a meaningful number that can be displayed in 2 digits. One way to do that would be to convert the countdown into seconds (divide by 70). Another way would be to convert it into percent (divide by ((COUNTDOWN_VAL+99)/100)).

Since you mentioned replacing the ammo value, here is how I would rewrite the ammo display part of the UpdateScore() function to achieve that (using the conversion to percent):

Code: Select all

//
// ammo changed
//
	number = gamestate.ammo;
	/* add the following 4 lines */
	if (mapon == COUNTDOWN_MAP)
	{
		number = gamestate.countdown / ((COUNTDOWN_VAL + 99) / 100);
	}
	/* end of countdown additions */
	if (number != ob->temp3)
	{
		block = (spritetype _seg *)grsegs[SCOREBOXSPR];
		width = block->width[0];
		planesize = block->planesize[0];
		dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]
			+ planesize + width*20 + 7*CHARWIDTH;

		if (number > 99)
			strcpy (str,"99");
		else
			ltoa (number,str,10);

		// erase leading spaces
		length = strlen(str);
		for (i=2;i>length;i--)
			MemDrawChar (41,dest+=CHARWIDTH,width,planesize);

		// draw digits
		ch = str;
		while (*ch)
			MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);

#if GRMODE == EGAGR
		ShiftScore ();
#endif
		ob->needtoreact = true;
		ob->temp3 = number;

		changed = true;
	}
Of course, if you are replacing the ammo number with a countdown number, you might also want to change the stunner icon of the score box into something else (a clock icon, for example) during the level that displays the countdown. This also means replacing the countdown icon with the ammo icon again when entering a different level. To achieve this, modify the code like this:

Code: Select all

		// draw digits
		ch = str;
		while (*ch)
			MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize);
		
		/* add the following lines */
		dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0]
			+ planesize + width*16 + 6*CHARWIDTH;
		if (mapon == COUNTDOWN_MAP)
		{
			i = 28; // the green stunner icon in the original Keens
		}
		else
		{
			i = 24;	// the red stunner icon in the original Keens
		}
		MemDrawChar(i++, dest, width, planesize);
		MemDrawChar(i++, dest+CHARWIDTH, width, planesize);
		dest += width*8;
		MemDrawChar(i++, dest, width, planesize);
		MemDrawChar(i, dest+CHARWIDTH, width, planesize);
		/* end of lines to add */

#if GRMODE == EGAGR
		ShiftScore ();
#endif
This code will use tiles from the TILE8 tileset to overwrite the icon in the score box, similar to how it uses tiles from the same tileset to place the decimal digits for the numbers into the score box image. The tiles that make up the red stunner icon in the original TILE8 tileset images will be used for the normal levels (since the default stunner icon in the score box is red as well) and the level that uses the countdown will use the tiles that make up the green stunner icon in the original TILE8 tileset.

Please keep in mind that these stunner icons from the TILE8 tileset weren't used in the original Keens and therefore nobody seems to have noticed that they don't quite fit the final score box image. The horizontal line of grey pixels at the bottom needs to be moved down by 1 pixel to make the tile graphics match the score box image. You will definitely need to edit the TILE8 tile images and make them fit your score boy image if you want to use this code.

---

There are other ways to implement timed events, even without editing the source code or patching the code.

For example, level 16 from the Keen-version of "The Prisoner's Dilemma" uses tile animations to make a barrier appear after a certain amount of time has elapsed. The only problem with this approach is that saving the game inside a level and then loading that game will reset all animating tiles to the state they were in when the level was started. That means if you load a game that was saved before the animated tiles turn into non-animated tiles at the end, loading the game will basically restart the countdown from the beginning.

This particular quirk of the game engine is something to keep in mind when designing the levels for any mod, not just when creating countdown tiles. For example, if you have any tile-based hazards in the path of a platform, the timing of the hazard tiles will change every time a saved game is loaded and could make certain passages impossible to beat.
Hail to the K1n9, baby!
http://k1n9duk3.shikadi.net
User avatar
SodiumtheGlitcher
Vortininja
Posts: 204
Joined: Wed Oct 16, 2024 0:24
Location: Impossible Bulleting out of Keen 6 and Geometry Dashin' both at the same time

Re: Patch Requests

Post by SodiumtheGlitcher »

I honestly was not expecting patches for this, so I'll probably pull a Gridlock and just use Volte-faces, since that method works pretty well (at least it did in the Alphamatic). However having the source modifications there is helpful for the other more advanced modders in the community.
Put me in a room full of chlorine. Results: I die and turn into a pillar of salt and a very buggy Keen 6.

IMPOSSIBLE BULLET FOR LIFE!!!!!!!!!!!!!
Post Reply