Downloads containing MayZapper.asc

Downloads
Name Author Game Mode Rating
JJ2+ Only: Astral Witchcraft minmay Multiple N/A Download file

File preview

// by may
// 1.0
// CC0 / public domain
#include "SEweapon.asc"
#include "MLLE-Weapons.asc"
#pragma require "SEweapon.asc"
#pragma require "MLLE-Weapons.asc"
#pragma require "MayZapper.asc"
#pragma require "MayZapper.png"
#pragma require "MayZapper2.png"
#pragma require "MayZapperShoot.wav"
#pragma require "MayZapperShootPU.wav"
#pragma require "MayZapperEnd.wav"

/*
CREDITS:

endSample is from https://freesound.org/people/Glaneur%20de%20sons/sounds/34169/
licensed under CC-BY.

shootSample is made from https://freesound.org/people/Daleonfire/sounds/376694/
and https://freesound.org/people/RICHERlandTV/sounds/661677/ which are both
CC 0 (public domain).

shootSamplePU is made from https://freesound.org/people/Daleonfire/sounds/376694/
and https://freesound.org/people/SomeSine/sounds/404337/ which are both CC 0
(public domain).

Bullet, pickup, powerup, and kill sprites are made from
https://opengameart.org/content/warped-shooting-fx
which are CC 0 (public domain).


*/

namespace MayZapper {
	/***** Easily-tweakable things *****/
	// Bullet dies after this many ticks.
	const uint8 ZAPPER_DURATION = 70;
	
	// Bullet starts with this much speed in the direction it's fired in,
	// plus any speed from the player's movement. If it's ever below this
	// speed, it will accelerate to it.
	const float BASE_SPEED = 12.0f;
	
	// Bullet can bounce up to this many times. Hitting a wall with no
	// bounces left will destroy the bullet.
	const int NUM_BOUNCES = 1;
	const int NUM_BOUNCES_PU = 3;
	
	// If true, enemy Zapper bullets in multiplayer games will be drawn with
	// red sprites, so you can tell them apart from your teammates' bullets.
	const bool drawEnemyBulletsRed = true;
	/***** End of easily-tweakable things *****/
	
	
	
	const uint SAMPLE_COUNT = 3;
	// catches slopes of up to RADIUS:1 or 1:RADIUS pixels
	const int SLOPE_CHECK_RADIUS = 4;
	const float PI = 3.141592f;
	
	const int BOUNCES_VAR_INDEX = 5;
	const int FLAGS_VAR_INDEX = 8;
	
	enum CUSTOM_FLAGS {
		FLAG_NO_SOUND = 1,
	}
	
	enum ANIMATION_OFFSETS {
		ANIM_ZAPPER_BULLET,
		ANIM_ZAPPER_BULLETBOUNCED,
		ANIM_ZAPPER_BULLETPU,
		ANIM_ZAPPER_BULLETBOUNCEDPU,
		ANIM_ZAPPER_BULLETENEMY,
		ANIM_ZAPPER_BULLETENEMYBOUNCED,
		ANIM_ZAPPER_BULLETENEMYPU,
		// okay maybe I should've used more underscores
		ANIM_ZAPPER_BULLETENEMYBOUNCEDPU,
		ANIM_ZAPPER_PICKUP,
		ANIM_ZAPPER_PICKUPPU,
		ANIM_ZAPPER_END,
		ANIM_ZAPPER_ENDPU,
		ANIM_ZAPPER_MONITOR,
		ANIM_ZAPPER_CRATE,
		ANIM_ZAPPER_HITBOX,
	}
	
	void setBounces(jjOBJ@ bullet, int bounces) {
		bullet.var[BOUNCES_VAR_INDEX] = bounces;
	}
	
	void setHasSound(jjOBJ@ bullet, bool hasSound) {
		bullet.var[FLAGS_VAR_INDEX] = hasSound ? 0 : 1;
	}

	shared class Zapper : se::WeaponInterface {
		jjANIMSET@ weaponAnimSet;
		bool samplesLoaded = false;
		// fallback sounds
		SOUND::Sample shootSample = SOUND::AMMO_GUNVELOCITY;
		SOUND::Sample shootSamplePU = SOUND::AMMO_GUNVELOCITY;
		SOUND::Sample endSample = SOUND::AMMO_FIREGUN2A;
		
		// this weapon is fast so that it's good for chasing people. Its large
		// hitbox stops it from phasing through *objects* unless you give it a
		// higher speed, but its "hitbox" for colliding with *masks* is one
		// pixel. It does enough masked pixel checks to guarantee that walls at
		// least this thick always get hit; if your level has thinner walls,
		// decrease it. If your level only has thicker walls, you can increase it
		// to get better performance.
		// DECREASING THIS DOES NOT MAKE COLLISIONS MORE PRECISE FOR WALLS THAT ARE
		// ALREADY THICK ENOUGH. DO NOT DECREASE IT UNLESS YOUR LEVEL ACTUALLY HAS
		// THIN MASKS. it's in pixels btw
		int MIN_SAFE_MASK_THICKNESS = 8;
		
		uint hitboxFrameIndex = 0;

		jjANIMSET@ loadAnims(jjANIMSET@ animSet) {
			if (animSet !is null) {
				@weaponAnimSet = @animSet;
				jjPIXELMAP spritesheet("MayZapper.png");
				@animSet = animSet.load(spritesheet, 60, 20);
				uint firstFrame = jjAnimations[animSet.firstAnim].firstFrame;
				for (uint i = firstFrame; i < firstFrame+35; i++) {
					jjAnimFrames[i].hotSpotX = -jjAnimFrames[i].width+8;
					jjAnimFrames[i].hotSpotY = -jjAnimFrames[i].height/2+1;
				}
				// setting firstAnim like this looks scary but
				// it's fiiiiine!
				jjPIXELMAP spritesheet2("MayZapper2.png");
				@animSet = animSet.load(spritesheet2, 32, 32);
				animSet.firstAnim -= ANIM_ZAPPER_PICKUP;
				
				// use the same hot spot as vanilla powerups
				uint monitorFirstFrame = jjAnimations[animSet.firstAnim+ANIM_ZAPPER_MONITOR].firstFrame;
				for (uint i = 0; i < 10; i++) {
					jjAnimFrames[monitorFirstFrame+i].hotSpotX = -14;
					jjAnimFrames[monitorFirstFrame+i].hotSpotY = -14;
				}
				
				// use the same hot spot as vanilla crates
				// (i don't actually like this spot but whatever, who cares about crates. which is also why i didnt bother making the crate sprite look good)
				jjANIMFRAME@ crateFrame = jjAnimFrames[jjAnimations[animSet.firstAnim+ANIM_ZAPPER_CRATE].firstFrame];
				crateFrame.hotSpotX = -13;
				crateFrame.hotSpotY = -14;
			}
			hitboxFrameIndex = jjAnimations[animSet.firstAnim+ANIM_ZAPPER_HITBOX].firstFrame;
			return @animSet;
		}
		
		array<bool>@ loadSamples(const array<SOUND::Sample>& samples) {
			if (samples.length() != SAMPLE_COUNT) {
				return @array<bool>(SAMPLE_COUNT,false);
			}
			samplesLoaded = true;
			array<bool> rval = {jjSampleLoad(samples[0], "MayZapperShoot.wav"), jjSampleLoad(samples[1], "MayZapperEnd.wav"), jjSampleLoad(samples[2], "MayZapperShootPU.wav")};
			if (rval[0]) shootSample = samples[0];
			if (rval[1]) endSample = samples[1];
			if (rval[2]) shootSamplePU = samples[2];
			return @rval;
		}
		
		uint getSampleCount() const {
			return SAMPLE_COUNT;
		}
		
		uint getTraits(bool powerup) const {
			return se::WeaponTrait::weapon_default_traits;
		}
		
		uint getMaxDamage(bool powerup) const {
			return powerup ? 2 : 1;
		}
		
		void explode(jjOBJ@ obj) const {
			if (obj.var[FLAGS_VAR_INDEX] & FLAG_NO_SOUND == 0) jjSample(obj.xPos, obj.yPos, endSample);
			jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT)].curAnim = obj.killAnim;
			obj.delete();
		}
		
		void behave(jjOBJ@ obj) const {
			switch (obj.state) {
				case STATE::START:
					if (obj.creatorType == CREATOR::PLAYER) {
						bool powerup = obj.animSpeed == 4;
						if (obj.eventID == OBJECT::TNT) {
							powerup = jjPlayers[obj.creatorID].powerup[WEAPON::TNT];
							if (!powerup) {
								obj.animSpeed = 2;
								obj.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_BULLET;
								obj.killAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_END;
								obj.var[6] = 0x0;
								obj.special = weaponAnimSet.firstAnim+ANIM_ZAPPER_BULLET;
							}
						}
						if (jjPlayers[obj.creatorID].isLocal && (obj.var[FLAGS_VAR_INDEX] & FLAG_NO_SOUND == 0)) {
							jjSample(obj.xPos, obj.yPos, powerup ? shootSamplePU : shootSample, 63, 39690+jjRandom()%8820);
						}
					}
					obj.counterEnd = jjObjectPresets[obj.eventID].counterEnd;
					obj.xSpeed += obj.var[7] / 65536.0f;
					obj.state = STATE::FLY;
					break;
				case STATE::FLY:
					{
					if (--obj.counterEnd == 0) {
						explode(obj);
						return;
					}
					float xSpeed = obj.xSpeed;
					float ySpeed = obj.ySpeed;
					float xPos = obj.xPos;
					float yPos = obj.yPos;
					if (jjMaskedPixel(int(xPos), int(yPos))) {
						// bullet is in a mask before moving, probably due
						// to either the player shooting it inside a wall,
						// or an animated tile.
						// Try to recover by moving backwards until an unmasked
						// pixel is found, up to a limit.
						float denom = abs(xSpeed) > abs(ySpeed) ? abs(xSpeed) : abs(ySpeed);
						// If the bullet is too slow, don't bother.
						if (denom >= 0.125f) {
							float xSpeedNorm = xSpeed/denom;
							float ySpeedNorm = ySpeed/denom;
							uint limit = uint(denom > 16.0f ? 16 : denom);
							while (limit-- > 0 && !jjMaskedPixel(int(xPos += xSpeedNorm), int(yPos += ySpeedNorm)));
						}
						if (jjMaskedPixel(int(xPos), int(yPos))) {
							// If it's still in a mask, give up and die.
							explode(obj);
							return;
						}
					}
					float fractionOfSpeed = 1.0f;
					
					float speedDenom = sqrt(xSpeed*xSpeed+ySpeed*ySpeed);
					float speedXNorm = xSpeed/speedDenom;
					float speedYNorm = ySpeed/speedDenom;
					
					// bullets below base speed accelerate to base speed
					float speedFromBase = BASE_SPEED-speedDenom;
					if (speedFromBase > 0.0f) {
						float acc = speedFromBase < 0.125f ? speedFromBase : 0.125f;
						xSpeed += speedXNorm*acc;
						ySpeed += speedYNorm*acc;
					}

					float maskCheckInc = MIN_SAFE_MASK_THICKNESS/(1.0f+abs(xSpeed)+abs(ySpeed));
					for (float hitDist = 1.0f; hitDist > 0.0f; hitDist -= maskCheckInc) {
						if (jjMaskedPixel(int(xPos+xSpeed*hitDist), int(yPos+ySpeed*hitDist))) { // bullet will hit a mask
							// now find the exact first masked pixel it'll hit.
							float searchX = xPos;
							float searchY = yPos;
							float denom = (abs(xSpeed) > abs(ySpeed) ? abs(xSpeed) : abs(ySpeed))*hitDist;
							if (denom >= 1.0f) {
								float xSpeedNorm = xSpeed/denom;
								float ySpeedNorm = ySpeed/denom;
								uint limit = uint(denom > 64.0f ? 64 : denom);
								while (limit-- > 0 && !jjMaskedPixel(int(searchX += xSpeedNorm), int(searchY += ySpeedNorm)));
								fractionOfSpeed = limit/denom;
							} else {
								searchX = xPos+xSpeed;
								searchY = yPos+ySpeed;
								fractionOfSpeed = 1.0f;
							}
							
							if (obj.var[BOUNCES_VAR_INDEX] <= 0) {
								// No bounces left. Die.
								obj.xPos = searchX;
								obj.yPos = searchY;
								explode(obj);
								return;
							} else {
								// guess a surface normal. This is done by examining
								// a plus-shaped area around the hit pixel,
								// looking for the closest masked pixel on an adjacent
								// line and the closest unmasked pixel on its own line,
								// both horizontally and vertically, and assuming the greater
								// distance of the two to be a slope, or assuming a straight
								// horizontal/vertical wall if the maximum distance was reached.
								int horizontalDistance = 0, verticalDistance = 0;
								int horizontalSign = 0, verticalSign = 0;
								
								int unmaskedHorizontalDistance = 0;
								
								int xInt = int(searchX);
								int yInt = int(searchY);
								
								for (int dx = 1; dx <= SLOPE_CHECK_RADIUS; dx++) {
									if (!jjMaskedPixel(xInt+dx,yInt)) {
										unmaskedHorizontalDistance = dx;
										horizontalSign = 1; // down-right
										break;
									} else if (!jjMaskedPixel(xInt-dx,yInt)) {
										unmaskedHorizontalDistance = dx;
										horizontalSign = -1;
										break;
									}
								}
									
								int unmaskedVerticalDistance = 0;
								for (int dy = 1; dy <= SLOPE_CHECK_RADIUS; dy++) {
									if (!jjMaskedPixel(xInt,yInt+dy)) {
										unmaskedVerticalDistance = dy;
										verticalSign = 1; // right-down
										break;
									} else if (!jjMaskedPixel(xInt,yInt-dy)) {
										unmaskedVerticalDistance = dy;
										verticalSign = -1;
										break;
									}
								}
								
								if (verticalSign != 0) {
									for (int dy = 0; dy <= SLOPE_CHECK_RADIUS; dy++) {
										if (jjMaskedPixel(xInt+horizontalSign,yInt-dy*verticalSign)) {
											verticalDistance = dy+unmaskedVerticalDistance-1;
											break;
										}
									}
								}
								
								if (horizontalSign != 0) {
									for (int dx = 0; dx <= SLOPE_CHECK_RADIUS; dx++) {
										if (jjMaskedPixel(xInt-dx*horizontalSign,yInt+verticalSign)) {
											horizontalDistance = dx+unmaskedHorizontalDistance-1;
											break;
										}
									}
								}

								// Done getting mask info.
								float slopeXNorm, slopeYNorm;
								
								if (horizontalSign == 0) {
									if (verticalSign == 0) {
										// This happens if there are no unmasked pixels nearby,
										// due to the bullet being in a tiny hole (the
										// bullet starting in a fully masked wall was already
										// checked for near the beginning).
										explode(obj);
										//jjPrint("Zapper bullet trapped");
										return;
									}
									// Horizontal floor or ceiling (or a weird mask
									// pattern we might as well treat as one).
									slopeXNorm = 0;
									slopeYNorm = -verticalSign;
									//jjPrint("Horizontal wall detected");
								} else if (verticalSign == 0) {
									// Vertical wall (or a weird mask pattern we
									// might as well treat as one).
									slopeXNorm = -horizontalSign;
									slopeYNorm = 0;
									//jjPrint("Vertical wall detected");
								} else if (horizontalDistance >= verticalDistance) {
									float slopeDenom = sqrt(horizontalDistance*horizontalDistance+1);
									slopeXNorm = horizontalSign/slopeDenom;
									slopeYNorm = verticalSign*horizontalDistance/slopeDenom;
									//jjPrint("Horizontal slope detected: "+horizontalDistance*horizontalSign+":1");
								} else {
									float slopeDenom = sqrt(verticalDistance*verticalDistance+1);
									slopeYNorm = verticalSign/slopeDenom;
									slopeXNorm = horizontalSign*verticalDistance/slopeDenom;
									//jjPrint("Vertical slope detected: 1:"+verticalDistance*verticalSign);
								}
								
								float dot = slopeXNorm*speedXNorm+slopeYNorm*speedYNorm;
								
								xSpeed = (speedXNorm - 2*dot*slopeXNorm)*speedDenom;
								ySpeed = (speedYNorm - 2*dot*slopeYNorm)*speedDenom;
								
								xPos = xInt;
								yPos = yInt;
								
								float accDenom = sqrt(obj.xAcc**2+obj.yAcc**2);
								if (accDenom > 0.01f) {
									obj.xAcc = (obj.xAcc/accDenom - 2*dot*slopeXNorm)*accDenom;
									obj.yAcc = (obj.yAcc/accDenom - 2*dot*slopeYNorm)*accDenom;
								}
								
								// decrement number of bounces
								obj.var[BOUNCES_VAR_INDEX] = obj.var[BOUNCES_VAR_INDEX] - 1;
								
								// switch to bounced sprite
								if (obj.var[BOUNCES_VAR_INDEX] == 0) {
									obj.curAnim += 1;
								}
								
								// bounce effect
								if (obj.var[FLAGS_VAR_INDEX] & FLAG_NO_SOUND == 0) jjSample(xPos, yPos, endSample);
								jjObjects[jjAddObject(OBJECT::EXPLOSION, xPos, yPos, obj.objectID, CREATOR::OBJECT)].curAnim = obj.killAnim;
							}
							
							// don't do further mask checks, we already hit something!
							break;
						}
					}
					obj.xPos = xPos + xSpeed * fractionOfSpeed;
					obj.yPos = yPos + ySpeed * fractionOfSpeed;
					obj.xSpeed = xSpeed + obj.xAcc;
					obj.ySpeed = ySpeed + obj.yAcc;
					uint16 frameCount = jjAnimations[obj.curAnim].frameCount;
					obj.frameID = frameCount - (obj.counterEnd/4 % frameCount) - 1;
					
					// curFrame is set to a special hitbox sprite.
					// Using the actual sprite for curFrame has bad collision results because
					// there's no way to rotate it. (A large number of pre-rotated hitbox
					// sprites would work fine as an alternative, though.)
					obj.curFrame = hitboxFrameIndex;
					
					// Instead of obj.direction, zapper bullets face in the direction of
					// their movement vector. direction is just set here in case any other
					// code wants it.
					obj.direction = obj.xSpeed >= 0.0f ? DIRECTION::RIGHT : DIRECTION::LEFT;
					int bulletAngle = 1024-int(atan2(obj.ySpeed, obj.xSpeed)/(PI*2)*1024);
					
					for (int i = 0; i < jjLocalPlayerCount; i++) {
						int16 anim = obj.curAnim;
						// switch to enemy bullet anim if needed
						if (drawEnemyBulletsRed && jjPlayers[obj.creatorID].isEnemy(jjLocalPlayers[i])) anim += 4;
						
						int frame = jjAnimations[anim].firstFrame+obj.frameID;
						
						jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, frame, bulletAngle, playerID: jjLocalPlayers[i].playerID);
					}
					
					break;
					}
				case STATE::EXPLODE:
					explode(obj);
					break;
				case STATE::KILL:
				case STATE::DEACTIVATE:
					obj.delete();
					break;
			}
		}
			
		bool setAsWeapon(uint number, se::WeaponHook@ weaponHook) {
			if (se::isValidWeapon(number)) {
				jjWeapons[number].comesFromBirds = true;
				jjWeapons[number].comesFromBirdsPowerup = true;
				jjWeapons[number].comesFromGunCrates = true;
				jjWeapons[number].defaultSample = false;
				jjWeapons[number].gemsLost = 3;
				jjWeapons[number].gradualAim = false;
				jjWeapons[number].multiplier = 1;
				jjWeapons[number].replacedByBubbles = false;
				jjWeapons[number].replacedByShield = false;
				jjWeapons[number].spread = SPREAD::NORMAL;
				jjWeapons[number].style = WEAPON::NORMAL;
				
				uint basic = se::getBasicBulletOfWeapon(number);
				uint powered = se::getPoweredBulletOfWeapon(number);
				jjOBJ@ preset = jjObjectPresets[basic];
				jjOBJ@ presetPower = jjObjectPresets[powered];
				preset.animSpeed = 2;
				presetPower.animSpeed = 4;
				preset.counterEnd = presetPower.counterEnd = ZAPPER_DURATION;
				preset.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_BULLET;
				presetPower.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_BULLETPU;
				preset.curFrame = presetPower.curFrame = hitboxFrameIndex;
				preset.direction = presetPower.direction = 0;
				// yes, this is the property that determines whether bullets freeze
				// enemies/springs/etc. Bullets sure are special aren't they
				preset.freeze = presetPower.freeze = 0;
				preset.killAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_END;
				presetPower.killAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_ENDPU;
				preset.lightType = presetPower.lightType = LIGHT::POINT;
				preset.light = presetPower.light = 8; // alas, doesn't do anything right now
				preset.playerHandling = presetPower.playerHandling = HANDLING::PLAYERBULLET;
				preset.special = preset.curAnim;
				presetPower.special = presetPower.curAnim;
				preset.var[3] = presetPower.var[3] = number;
				preset.var[BOUNCES_VAR_INDEX] = NUM_BOUNCES;
				presetPower.var[BOUNCES_VAR_INDEX] = NUM_BOUNCES_PU;
				preset.var[6] = 0x0;
				presetPower.var[6] = 0x8;
				preset.xAcc = presetPower.xAcc = 0.0f;
				preset.xSpeed = presetPower.xSpeed = BASE_SPEED;
				preset.yAcc = presetPower.yAcc = 0.0f;
				preset.ySpeed = presetPower.ySpeed = 0.0f;
				preset.behavior = presetPower.behavior = @jjVOIDFUNCOBJ(behave);
				
				
				uint ammo3i = se::getAmmoPickupOfWeapon(number);
				if (ammo3i != 0) {
					jjOBJ@ ammo3 = jjObjectPresets[ammo3i];
					ammo3.behavior = @se::AmmoPickup(jjAnimations[weaponAnimSet.firstAnim+ANIM_ZAPPER_PICKUP], jjAnimations[weaponAnimSet.firstAnim+ANIM_ZAPPER_PICKUPPU]);
					ammo3.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_PICKUP;
					ammo3.frameID = 0;
					ammo3.determineCurFrame();
				}
				
				uint powerupi = se::getPowerupMonitorOfWeapon(number);
				if (powerupi != 0) {
					jjOBJ@ powerup = jjObjectPresets[powerupi];
					powerup.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_MONITOR;
					powerup.frameID = 0;
					powerup.determineCurFrame();
				}
				
				uint cratei = se::getAmmoCrateOfWeapon(number);
				if (cratei != 0) {
					jjOBJ@ crate = jjObjectPresets[cratei];
					crate.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_CRATE;
					crate.frameID = 0;
					crate.determineCurFrame();
				}
				
				if (weaponHook !is null) {
					weaponHook.resetCallbacks(number);
					weaponHook.setWeaponSprite(number, false, jjAnimations[weaponAnimSet.firstAnim+ANIM_ZAPPER_PICKUP]);
					weaponHook.setWeaponSprite(number, true, jjAnimations[weaponAnimSet.firstAnim+ANIM_ZAPPER_PICKUPPU]);
				}

				return true;
			}
			return false;
		}
	}
	
	class ZapperMLLEWrapper : Zapper, MLLEWeaponApply {
		bool Apply(uint number, se::WeaponHook@ weaponHook = null, jjSTREAM@ = null, uint8 ammo15EventID = 0) {
			if (weaponAnimSet is null) {
				uint8 animSetID = 0;
				while (jjAnimSets[ANIM::CUSTOM[animSetID]] != 0)
					++animSetID;
				loadAnims(jjAnimSets[ANIM::CUSTOM[animSetID]]);
			}
			
			if (!samplesLoaded) {
				array<SOUND::Sample> samples;
				array<SOUND::Sample>@ trySamples = SOUND::GetAllSampleConstantsInRoughOrderOfLikelihoodToBeUsed();
				uint index = trySamples.length();
				while (samples.length() < getSampleCount()) {
					while (index > 0 && jjSampleIsLoaded(trySamples[--index]));
					samples.insertLast(trySamples[index]);
				}
				loadSamples(samples);
			}
			
			if (ammo15EventID != 0) {
				jjOBJ@ ammo15 = @jjObjectPresets[ammo15EventID];
				ammo15.curAnim = weaponAnimSet.firstAnim+ANIM_ZAPPER_CRATE;
				ammo15.frameID = 0;
				ammo15.determineCurFrame();
			}
			
			return setAsWeapon(number, weaponHook);
		}
	}
}