Downloads containing MLLE-Weapons.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Anniversary Bash 26 CTF Jazz2Online Capture the flag N/A Download file
TSF with JJ2+ Only: Anniversary Bash 26 Battle Jazz2Online Battle N/A Download file
TSF with JJ2+ Only: Saline Shores PurpleJazz Capture the flag N/A Download file
TSF with JJ2+ Only: BioluminescenceFeatured Download Dragusela Capture the flag 8.5 Download file
TSF with JJ2+ Only: Mt. Gongga Loon Battle N/A Download file
JJ2+ Only: Knapweed Bog Lark Battle N/A Download file
TSF with JJ2+ Only: Freeport Loon Battle N/A Download file
JJ2+ Only: Threed RealmsFeatured Download Violet CLM Single player 9.7 Download file
TSF with JJ2+ Only: A Cloudy DayFeatured Download Dragusela Battle 9.5 Download file
TSF with JJ2+ Only: Ancient Hall of Wisdom Slaz Capture the flag N/A Download file
TSF with JJ2+ Only: The PillarFeatured Download Dragusela Battle 9 Download file
TSF with JJ2+ Only: Distant PlateauFeatured Download Dragusela Capture the flag 8.4 Download file
TSF with JJ2+ Only: Lake PartyFeatured Download Dragusela Battle 9.5 Download file
JJ2+ Only: Anniversary Bash 25 Battle Jazz2Online Battle N/A Download file
JJ2+ Only: Anniversary Bash 25 CTF Jazz2Online Capture the flag N/A Download file
JJ2+ Only: Forgotten CivilisationFeatured Download PurpleJazz Capture the flag 8.7 Download file
TSF with JJ2+ Only: Scarlet WealdFeatured Download PurpleJazz Capture the flag 9.5 Download file
TSF with JJ2+ Only: Blast ConcussionFeatured Download Violet CLM Capture the flag 9.7 Download file
TSF with JJ2+ Only: Star of the year V2Featured Download TreyLina Capture the flag 8.5 Download file
TSF with JJ2+ Only: Asteroid Armada PurpleJazz Battle N/A Download file
JJ2+ Only: Spaceships (v1.01) PurpleJazz Custom / Concept N/A Download file
TSF with JJ2+ Only: Halle der OrgelpfeifenFeatured Download Loon Capture the flag 8.7 Download file
TSF with JJ2+ Only: Winterfrost Mountains Dragusela Battle N/A Download file
TSF with JJ2+ Only: Obsidian CaveFeatured Download PurpleJazz Capture the flag 8.7 Download file
TSF with JJ2+ Only: Western WildernessFeatured Download Dragusela Capture the flag 9.2 Download file
TSF with JJ2+ Only: Celestial TemplesFeatured Download Dragusela Capture the flag 8.5 Download file
TSF with JJ2+ Only: Honey CataCombs Rysice Battle N/A Download file
TSF with JJ2+ Only: Moon BaseFeatured Download Dragusela Capture the flag 9 Download file
TSF with JJ2+ Only: Blue Moon Featured Download Dragusela Capture the flag 9.2 Download file
TSF with JJ2+ Only: Anniversary Bash 24 CTF Jazz2Online Capture the flag N/A Download file
TSF with JJ2+ Only: Anniversary Bash 24 Battle Jazz2Online Battle N/A Download file
TSF with JJ2+ Only: Acid ReignFeatured Download cooba Battle 9.2 Download file
TSF with JJ2+ Only: BioMess Rysice Battle N/A Download file
JJ2+ Only: RAID: Mirrow LegendsFeatured Download Violet CLM Battle 8.5 Download file
TSF with JJ2+ Only: Laboratory 38Featured Download Dragusela Battle 8 Download file
JJ2+ Only: Ethereal RiverFeatured Download Dragusela Battle 8.5 Download file
TSF with JJ2+ Only: Malice in WonderlandFeatured Download Loon Battle 9.9 Download file
TSF with JJ2+ Only: LandfallFeatured Download Loon Battle 9.4 Download file
TSF with JJ2+ Only: Peaceful Worlds Dragusela Multiple 7.5 Download file
TSF with JJ2+ Only: Lori FortressFeatured Download Primpy Single player 8.7 Download file
TSF with JJ2+ Only: Mystery of the Four... chandie Single player 6.6 Download file
TSF with JJ2+ Only: Crown of ThornsFeatured Download PurpleJazz Battle 9.2 Download file
TSF with JJ2+ Only: Anniversary Bash 23 levels Jazz2Online Multiple N/A Download file
JJ2+ Only: violetclm DOM Episodes I...Featured Download Violet CLM Custom / Concept 9 Download file
TSF with JJ2+ Only: Frosted PeaksFeatured Download PurpleJazz Capture the flag 9.1 Download file
TSF with JJ2+ Only: SalemFeatured Download Loon Battle 9 Download file
TSF with JJ2+ Only: SommerdammFeatured Download Loon Capture the flag 9 Download file
TSF with JJ2+ Only: Hollow of the HauntedFeatured Download cooba Capture the flag 9.2 Download file
TSF with JJ2+ Only: street fight!!!!!!! cooba Battle N/A Download file
TSF with JJ2+ Only: Anniversary Bash 22 levels Jazz2Online Multiple N/A Download file
TSF with JJ2+ Only: The AbyssFeatured Download PurpleJazz Battle 9.8 Download file
Multi-Layer Level Editor...Featured Download Violet CLM Utility 9.6 Download file
TSF with JJ2+ Only: Facilis descensus AvernoFeatured Download Loon Battle 8.5 Download file
TSF with JJ2+ Only: Umbral ThicketFeatured Download PurpleJazz Battle 9.5 Download file
JJ2+ Only: Tech Tree Violet CLM Capture the flag 7.8 Download file

File preview

#pragma require "MLLE-Weapons.asc"
#pragma require "SEweapon.asc"
#include "SEweapon.asc"

shared interface MLLEWeaponApply { bool Apply(uint, se::WeaponHook@ = null, jjSTREAM@ = null, uint8 = 0); }
namespace MLLEWeapons {
	class ObjectTemplate {
		int Age;
		int AnimSpeed;
		HANDLING::Bullet BulletHandling;
		bool CausesRicochet;
		int Counter;
		uint8 CounterEnd;
		int16 CurAnim;
		uint CurFrame;
		bool Deactivates;
		uint8 DoesHurt;
		int8 Energy;
		int8 FrameID;
		uint8 Freeze;
		bool IsBlastable;
		bool IsFreezable;
		bool IsTarget;
		uint8 JustHit;
		int16 KillAnim;
		int8 Light;
		LIGHT::Type LightType;
		STATE::State OldState;
		HANDLING::Player PlayerHandling;
		uint16 Points;
		bool ScriptedCollisions;
		int Special;
		STATE::State State;
		bool TriggersTNT;
		array<int> Var;
		float XAcc;
		float XSpeed;
		float YAcc;
		float YSpeed;
		ObjectTemplate(
			int age = 0,
			int animSpeed = 1,
			HANDLING::Bullet bulletHandling = HANDLING::HURTBYBULLET,
			bool causesRicochet = false,
			int counter = 0,
			uint8 counterEnd = 0,
			int16 curAnim = 0,
			bool deactivates = true,
			uint8 doesHurt = 0,
			int8 energy = 0,
			int8 frameID = 0,
			uint8 freeze = 0,
			bool isBlastable = true,
			bool isFreezable = false,
			bool isTarget = false,
			uint8 justHit = 0,
			int16 killAnim = 0,
			int8 light = 0,
			LIGHT::Type lightType = LIGHT::NONE,
			STATE::State oldState = STATE::START,
			HANDLING::Player playerHandling = HANDLING::PLAYERBULLET,
			uint16 points = 0,
			bool scriptedCollisions = false,
			int special = 0,
			STATE::State state = STATE::START,
			bool triggersTNT = false,
			array<int> var = array<int>(0),
			float xAcc = 0,
			float yAcc = 0,
			float xSpeed = 0,
			float ySpeed = 0
		) {
			Age = age;
			AnimSpeed = animSpeed;
			BulletHandling = bulletHandling;
			CausesRicochet = causesRicochet;
			Counter = counter;
			CounterEnd = counterEnd;
			CurAnim = curAnim;
			Deactivates = deactivates;
			DoesHurt = doesHurt;
			Energy = energy;
			FrameID = frameID;
			Freeze = freeze;
			IsBlastable = isBlastable;
			IsFreezable = isFreezable;
			IsTarget = isTarget;
			JustHit = justHit;
			KillAnim = killAnim;
			Light = light;
			LightType = lightType;
			OldState = oldState;
			PlayerHandling = playerHandling;
			Points = points;
			ScriptedCollisions = scriptedCollisions;
			Special = special;
			State = state;
			TriggersTNT = triggersTNT;
			for (uint i = 0; i < 11; ++i)
				Var.insertLast(i < var.length ? var[i] : 0);
			XAcc = xAcc;
			XSpeed = xSpeed;
			YAcc = yAcc;
			YSpeed = ySpeed;
		}
		void Apply(jjOBJ@ obj, uint firstAnim, bool objIsPreset) const {
			obj.age = Age;
			obj.animSpeed = AnimSpeed;
			obj.bulletHandling = BulletHandling;
			obj.causesRicochet = CausesRicochet;
			obj.counter = Counter;
			obj.counterEnd = CounterEnd;
			obj.curAnim = (CurAnim < 200) ? firstAnim + CurAnim : CurAnim;
			obj.deactivates = Deactivates;
			obj.doesHurt = DoesHurt;
			obj.energy = Energy;
			obj.frameID = FrameID;
			obj.determineCurFrame();
			obj.freeze = Freeze;
			obj.isBlastable = IsBlastable;
			obj.isFreezable = IsFreezable;
			obj.isTarget = IsTarget;
			obj.justHit = JustHit;
			obj.killAnim = (KillAnim < 200) ? firstAnim + KillAnim : KillAnim;
			obj.light = Light;
			obj.lightType = LightType;
			obj.oldState = OldState;
			obj.playerHandling = PlayerHandling;
			obj.points = Points;
			obj.scriptedCollisions = ScriptedCollisions;
			obj.special =
				(Special >= 0) ?
					(Special != 0 && Special < 200) ?
						firstAnim + Special - 1 :
						Special :
					obj.curAnim
				;
			obj.state = State;
			obj.triggersTNT = TriggersTNT;
			for (uint i = 0; i < 11; ++i)
				if (/*objIsPreset || */(i != 7 && i != 3)) //pxSpeed, bullet type
					obj.var[i] = Var[i];
			if (objIsPreset) {
				obj.xAcc =		XAcc;
				obj.xSpeed =	XSpeed;
				obj.yAcc =		YAcc;
				obj.ySpeed =	YSpeed;
			} else
				ApplyTNTSpeeds(obj);
		}
		void ApplyTNTSpeeds(jjOBJ@ obj) const {
			const auto x = obj.xSpeed, y = obj.ySpeed; //radius of 1
			obj.xAcc =		x * XAcc;
			obj.xSpeed =	x * XSpeed;
			obj.yAcc =		abs(x) * YAcc + y * XAcc; //was abs(x * Ything)
			obj.ySpeed =	abs(x) * YSpeed + y * XSpeed;
		}
	}
	
	enum AnimationIndices { PickupIndex, PoweredUpPickupIndex, PowerupIndex, AmmoCrateIndex, _COUNT };
	funcdef void behaviorFunction(jjOBJ@, bool);
	funcdef bool applyFunction(uint, se::WeaponHook@, jjSTREAM@);
	funcdef void presetFunction(jjOBJ@, int);
	abstract class WeaponInterface : se::WeaponInterface, MLLEWeaponApply, jjBEHAVIORINTERFACE {
		protected ObjectTemplate@ RegularObjectTemplate, PowerupObjectTemplate;
		
		private behaviorFunction@ AssignBehavior;
		private applyFunction@ ExtraApply;
		private presetFunction@ CustomAmmo3, CustomAmmo15, CustomPowerup;
		
		private se::MainCallback@ OnMain;
		private se::PlayerCallback@ OnPlayer;
		private se::PlayerCallback@ OnPlayerInput;
		private se::DrawingCallback@ OnDrawAmmo;
		private se::PacketCallback@ OnReceive;
		
		private array<string> SampleFilenames(0);
		private string AnimSetFilename;
		private uint AnimSetID;
		private bool GenerateSupplementalAnimations;
		protected int PickupFrameIDToUseForAmmoCrate;
		
		private array<int> Animations(AnimationIndices::_COUNT);
		protected int get_PickupAnimation() const { return Animations[AnimationIndices::PickupIndex]; }
		protected int get_PoweredUpPickupAnimation() const { return Animations[AnimationIndices::PoweredUpPickupIndex]; }
		protected int get_PowerupAnimation() const { return Animations[AnimationIndices::PowerupIndex]; }
		protected int get_AmmoCrateAnimation() const { return Animations[AnimationIndices::AmmoCrateIndex]; }
		
		protected bool IsRFMissile;
		private uint Traits;
		private uint PoweredUpTraits;
		bool WeaponHookRequired;
		
		protected uint RoundsPerPickup;
		protected SOUND::Sample PickupSample;
		
		protected int Multiplier;
		protected bool GradualAim;
		protected bool ReplacedByBubbles;
		protected SPREAD::Spread Spread;
		protected WEAPON::Style Style;
		
		protected jjANIMSET@ AnimSet;
		protected uint SetID;
		protected array<SOUND::Sample> Samples(0);
		protected array<bool> SamplesLoaded(0);
		protected se::PacketConstructor@ ConstructPacket;
		
		WeaponInterface(
			ObjectTemplate regularObjectTemplate,
			behaviorFunction@ behavior,
			ObjectTemplate@ powerupObjectTemplate = null,
			array<string> sampleFilenames = array<string> = {},
			array<ANIM::Set> otherAnimSetsToLoad = array<ANIM::Set> = {},
			const string &in animSetFilename = "",
			uint animSetID = 0,
			int pickupAnimation = -1,
			int poweredUpPickupAnimation = -1,
			int powerupAnimation = -1,
			int ammoCrateAnimation = -1,
			bool generateSupplementalAnimations = true,
			int pickupFrameIDToUseForAmmoCrate = 0,
			bool isRFMissile = false,
			uint traits = se::WeaponTrait::weapon_default_traits,
			int poweredUpTraits = -1,
			bool weaponHookRequired = false,
			uint roundsPerPickup = 3,
			SOUND::Sample pickupSample = SOUND::COMMON_PICKUPW1,
			int multiplier = 1,
			bool gradualAim = false,
			bool replacedByBubbles = false,
			SPREAD::Spread spread = SPREAD::NORMAL,
			WEAPON::Style style = WEAPON::NORMAL,
			applyFunction@ apply = null,
			presetFunction@ customAmmo3 = null,
			presetFunction@ customAmmo15 = null,
			presetFunction@ customPowerup = null,
			se::MainCallback@ onMain = null,
			se::PlayerCallback@ onPlayer = null,
			se::PlayerCallback@ onPlayerInput = null,
			se::DrawingCallback@ onDrawAmmo = null,
			se::PacketCallback@ onReceive = null
		) {
			@RegularObjectTemplate = @regularObjectTemplate;
			@AssignBehavior = @behavior;
			@PowerupObjectTemplate = @((powerupObjectTemplate !is null) ? powerupObjectTemplate : regularObjectTemplate);
			PowerupObjectTemplate.Var[6] = PowerupObjectTemplate.Var[6] | 8;
			RegularObjectTemplate.Var[6] = RegularObjectTemplate.Var[6] & ~8;
			SampleFilenames = sampleFilenames;
			for (uint i = 0; i < otherAnimSetsToLoad.length; ++i)
				if (jjAnimSets[otherAnimSetsToLoad[i]] == 0)
					jjAnimSets[otherAnimSetsToLoad[i]].load();
			AnimSetFilename = animSetFilename;
			AnimSetID = animSetID;
			Animations[AnimationIndices::PickupIndex] = pickupAnimation;
			Animations[AnimationIndices::PoweredUpPickupIndex] = poweredUpPickupAnimation;
			Animations[AnimationIndices::PowerupIndex] = powerupAnimation;
			Animations[AnimationIndices::AmmoCrateIndex] = ammoCrateAnimation;
			GenerateSupplementalAnimations = generateSupplementalAnimations;
			PickupFrameIDToUseForAmmoCrate = pickupFrameIDToUseForAmmoCrate;
			IsRFMissile = isRFMissile;
			Traits = traits;
			PoweredUpTraits = (poweredUpTraits == -1) ? traits : uint(poweredUpTraits);
			WeaponHookRequired = weaponHookRequired;
			RoundsPerPickup = roundsPerPickup;
			PickupSample = pickupSample;
			Multiplier = multiplier;
			GradualAim = gradualAim;
			ReplacedByBubbles = replacedByBubbles;
			Spread = spread;
			Style = style;
			@ExtraApply = @apply;
			@CustomAmmo3 = @customAmmo3;
			@CustomAmmo15 = @customAmmo15;
			@CustomPowerup = @customPowerup;
			@OnMain = @onMain;
			@OnPlayer = @onPlayer;
			@OnPlayerInput = @onPlayerInput;
			@OnDrawAmmo = @onDrawAmmo;
			@OnReceive = @onReceive;
		}
		
		array<bool>@ loadSamples(const array<SOUND::Sample>& samples) {
			if (SamplesLoaded.length < getSampleCount()){
				Samples = samples;
				SamplesLoaded.resize(samples.length);
				if (samples.length == SampleFilenames.length) {
					for (uint i = 0; i < samples.length; ++i)
						SamplesLoaded[i] = samples[i] != 0 && jjSampleLoad(samples[i], SampleFilenames[i]);
				}
			}
			return SamplesLoaded;
		}
		uint getSampleCount() const { return SampleFilenames.length; }
		
		jjANIMSET@ loadAnims(jjANIMSET@ animSet) {
			if (AnimSet is null) {
				if (AnimSetFilename.length > 0) {
					if (AnimSetFilename.findFirst('.') < 0)
						AnimSetFilename += ".j2a";
					animSet.load(AnimSetID, AnimSetFilename);
				}
				@AnimSet = @animSet;
			}
			return @animSet;
		}
		
		uint getTraits(bool powerup) const { return powerup ? PoweredUpTraits : Traits; }
		bool onIsRFBullet(jjOBJ@) { return IsRFMissile; }
		
		bool setAsWeapon(uint number, se::WeaponHook@ weaponHook) {
			if (se::isValidWeapon(number)) {
				jjWEAPON@ weapon = jjWeapons[number];
				weapon.defaultSample = false;
				weapon.multiplier = Multiplier;
				weapon.gradualAim = GradualAim;
				weapon.replacedByBubbles = ReplacedByBubbles;
				weapon.spread = Spread;
				weapon.style = Style;
				
				for (int i = 0; i < AnimationIndices::_COUNT; ++i)
					if (Animations[i] >= 0 && Animations[i] < 200) //in case Apply wasn't called for whatever reason
						Animations[i] += AnimSet.firstAnim;
				
				const auto combinedTraits = Traits | PoweredUpTraits;
				jjOBJ@ ammo3;
				{
					const int pa = Animations[AnimationIndices::PickupIndex], ppa = Animations[AnimationIndices::PoweredUpPickupIndex];
					
					if (weaponHook !is null) {
						weaponHook.resetCallbacks(number);
						
						if (pa >= 0 || ppa >= 0) {
							weaponHook.setWeaponSprite(number, false, jjAnimations[pa >= 0 ? pa : ppa]);
							weaponHook.setWeaponSprite(number, true, jjAnimations[ppa >= 0 ? ppa : pa]);
						}
							
						if (OnMain !is null)
							weaponHook.setMainCallback(number, OnMain);
						if (OnPlayer !is null)
							weaponHook.setPlayerCallback(number, OnPlayer);
						if (OnPlayerInput !is null)
							weaponHook.setPlayerInputCallback(number, OnPlayerInput);
						if (OnDrawAmmo !is null)
							weaponHook.setDrawingCallback(number, OnDrawAmmo);
						if (OnReceive !is null)
							@ConstructPacket = weaponHook.addPacketCallback(OnReceive);
					} else if (WeaponHookRequired)
						return false;
					
					if ((combinedTraits & se::weapon_has_ammo_pickups) != 0 && (pa >= 0 || ppa >= 0)) {
						const uint ammo3EventID = se::getAmmoPickupOfWeapon(number);
						if (ammo3EventID != 0) {
							@ammo3 = jjObjectPresets[ammo3EventID];
							if (CustomAmmo3 !is null)
								CustomAmmo3(ammo3, number);
							else {
								if (PickupSample < 0)
									PickupSample = SOUND::Sample(Samples[1-PickupSample]);
								ammo3.behavior = AmmoPickupWithCrateAndCorpseSupport(
									jjAnimations[pa >= 0 ? pa : ppa],
									jjAnimations[ppa >= 0 ? ppa : pa],
									RoundsPerPickup,
									PickupSample
								);
								ammo3.state = STATE::START;
								ammo3.curAnim = pa >= 0 ? pa : ppa;
								ammo3.frameID = 0;
								ammo3.determineCurFrame();
								ammo3.killAnim = jjAnimSets[ANIM::PICKUPS] + 86;
								ammo3.objType = HANDLING::PICKUP;
								ammo3.isBlastable = true;
								ammo3.var[3] = number - 1;
								ammo3.points = 100;
							}
							if (jjGameConnection != GAME::LOCAL && jjObjectPresets[OBJECT::FLICKERGEM].behavior == BEHAVIOR::FLICKERGEM)
								jjObjectPresets[OBJECT::FLICKERGEM].behavior = FlickerGemAmmoPickupAssigner;
						}
					}
				} {
					const uint ammo15EventID = Ammo15s !is null ? Ammo15s[number-1] : se::getAmmoCrateOfWeapon(number);
					if (ammo15EventID != 0) {
						jjOBJ@ ammo15 = @jjObjectPresets[ammo15EventID];
						if ((combinedTraits & se::weapon_has_ammo_crates) != 0 && Animations[AnimationIndices::AmmoCrateIndex] >= 0) {
							if (CustomAmmo15 !is null)
								CustomAmmo15(ammo15, number);
							else {
								ammo15.curAnim = Animations[AnimationIndices::AmmoCrateIndex];
								ammo15.frameID = 0;
								ammo15.determineCurFrame();
								ammo15.killAnim = jjAnimSets[ANIM::AMMO] + 71;
								ammo15.state = STATE::START;
								ammo15.objType = HANDLING::SPECIAL;
								ammo15.noHit = HANDLING::HURTBYBULLET;
								ammo15.direction = 1;
								ammo15.energy = 1;
								ammo15.var[3] = number - 1;
								ammo15.var[2] = se::getAmmoPickupOfWeapon(number);
								ammo15.points = 300;
								if (se::getAmmoCrateOfWeapon(number) != 0 && number != 2) { //3,4,5,6
									ammo15.behavior = BEHAVIOR::AMMO15;
									ammo15.eventID = ammo15EventID;
								} else { //no normal ammo15 object available (or in the case of bouncers, can't be trusted to use the right curAnim)
									ammo15.behavior = function(obj) { if (obj.state == STATE::DEACTIVATE) obj.eventID = obj.counterEnd; obj.behave(BEHAVIOR::AMMO15); };
									ammo15.eventID = OBJECT::ICEAMMO15;
									ammo15.counterEnd = ammo15EventID;
								}
							}
						} else if (ammo3 !is null)
							CopyObject(ammo15, ammo3);
					}
					const uint powerupEventID = se::getPowerupMonitorOfWeapon(number);
					jjOBJ@ powerup = @jjObjectPresets[powerupEventID];
					if ((combinedTraits & se::weapon_has_powerup_monitors) != 0 && (combinedTraits & se::weapon_has_distinct_powerup) != 0 && Animations[AnimationIndices::PowerupIndex] >= 0) {
						if (CustomPowerup !is null)
							CustomPowerup(powerup, number);
						else {
							powerup.curAnim = Animations[AnimationIndices::PowerupIndex];
							powerup.frameID = 0;
							powerup.determineCurFrame();
							powerup.killAnim = jjAnimSets[ANIM::AMMO] + 71;
							powerup.state = STATE::START;
							powerup.objType = HANDLING::SPECIAL;
							powerup.noHit = HANDLING::HURTBYBULLET;
							powerup.energy = 1;
							powerup.var[3] = number - 1;
							powerup.points = 1000;
							if (number != WEAPON::BLASTER) {
								powerup.behavior = BEHAVIOR::MONITOR;
								powerup.eventID = powerupEventID;
							} else { //gun1 powerup shouldn't use player-dependent animations if it's been replaced by some other weapon
								powerup.behavior = function(obj) { if (obj.state == STATE::DEACTIVATE) obj.eventID = obj.counterEnd; obj.behave(BEHAVIOR::MONITOR); };
								powerup.counterEnd = powerupEventID;
								powerup.eventID = OBJECT::BOUNCERPOWERUP;
							}
						}
					} else if (ammo3 !is null)
						CopyObject(powerup, ammo3);
				}
				
				jjOBJ@ Preset1 = jjObjectPresets[se::getBasicBulletOfWeapon(number)];
				jjOBJ@ Preset2 = jjObjectPresets[se::getPoweredBulletOfWeapon(number)];
				RegularObjectTemplate.Apply(Preset1, AnimSet.firstAnim, true);
				if (number != WEAPON::TNT)
					PowerupObjectTemplate.Apply(Preset2, AnimSet.firstAnim, true);
				else if (Preset1.xSpeed != 0) {
					Preset1.xSpeed = 1; //for determining the angle
					Preset1.ySpeed = 0;
				}
				Preset1.var[3] = Preset2.var[3] = number;
				Preset1.behavior = Preset2.behavior = this; //onBehave, below
				return true;
			}
			return false;
		}
		bool Apply(uint number, se::WeaponHook@ weaponHook = null, jjSTREAM@ parameters = null, uint8 ammo15EventID = 0) {
			if (se::isValidWeapon(number)) {
				LoadResources();
					
				if (ExtraApply !is null && !ExtraApply(
					number,
					weaponHook,
					parameters
				))
					return false;
				
				if (Ammo15s is null)
					@Ammo15s = array<uint8>(9, 0);
				if (Ammo15s[number-1] == 0) {
					if (ammo15EventID != 0)
						Ammo15s[number-1] = ammo15EventID;
					else if (number < WEAPON::TNT && number != WEAPON::BLASTER)
						Ammo15s[number-1] = se::getAmmoCrateOfWeapon(number);
				}
				
				for (int i = 0; i < AnimationIndices::_COUNT; ++i)
					if (Animations[i] >= 0 && Animations[i] < 200)
						Animations[i] += AnimSet.firstAnim;
				
				if (GenerateSupplementalAnimations)
					GenerateSupplementaryWeaponSprites(Animations, Ammo15s[number-1], PickupFrameIDToUseForAmmoCrate);
				
				return setAsWeapon(number, weaponHook);
			}
			return false;
		}
		
		void onBehave(jjOBJ@ obj) {
			bool powerup;
			if (obj.eventID == OBJECT::TNT) {
				float RFYSpeed = 0;
				if (IsRFMissile && abs(obj.xSpeed) == jjObjectPresets[OBJECT::TNT].xSpeed) {
					RFYSpeed = obj.ySpeed;
					obj.ySpeed = 0;
				}
				if (powerup = (obj.creatorType == CREATOR::PLAYER && jjPlayers[obj.creator].powerup[WEAPON::TNT])) {
					const bool firingUp = obj.counterEnd < jjObjectPresets[OBJECT::TNT].counterEnd;
					PowerupObjectTemplate.Apply(obj, AnimSet.firstAnim, false);
					if (firingUp) obj.counterEnd = obj.counterEnd * 2 / 3;
				} else
					RegularObjectTemplate.ApplyTNTSpeeds(obj);
				if (RFYSpeed != 0) obj.ySpeed = RFYSpeed;
				obj.eventID = OBJECT::BULLET; //don't use /tntdamage unless AssignBehavior requests it specifically
			} else
				powerup = obj.eventID >= OBJECT::BLASTERBULLETPU;
			if (PowerupObjectTemplate is RegularObjectTemplate) {
				if (powerup)
					obj.var[6] = obj.var[6] | 8;
				else
					obj.var[6] = obj.var[6] & ~8;
			}
			AssignBehavior(obj, powerup);
			obj.behave();
		}
		
		array<bool>@ LoadFirstAvailableSamples() {
			if (SamplesLoaded.length == getSampleCount())
				return SamplesLoaded;
			array<SOUND::Sample> samples;
			const uint sampleCount = getSampleCount();
			array<SOUND::Sample>@ prospectiveSamples = SOUND::GetAllSampleConstantsInRoughOrderOfLikelihoodToBeUsed();
			while (samples.length < sampleCount) {
				if (prospectiveSamples.length != 0) {
					SOUND::Sample prospectiveSample = prospectiveSamples[0];
					prospectiveSamples.removeAt(0);
					if (!jjSampleIsLoaded(prospectiveSample))
						samples.insertLast(prospectiveSample);
				} else
					samples.insertLast(SOUND::Sample(0));
			}
			return loadSamples(samples);
		}
		
		void LoadResources() {
			if (SetID == 0)
				loadAnims(jjAnimSets[SetID = GetFirstAvailableCustomAnimsetID()]);
			LoadFirstAvailableSamples();
		}
		
		uint getMaxDamage(bool powerup) const { return powerup ? 2 : 1; }
		
		array<int> FireBullet(float xPos, float yPos, int angle, bool powerup, uint16 creatorID, CREATOR::Type creatorType = CREATOR::PLAYER, uint number = WEAPON::BLASTER, SPREAD::Spread spread = SPREAD::Spread(-1), float additionalXSpeed = 0) const {
			//LoadResources();
			int monsterDirection; float monsterXSpeed;
			if (creatorType != CREATOR::PLAYER) { //avoid issues with some startup code in some native bullet behaviors
				jjOBJ@ monster = jjObjects[creatorID];
				monsterDirection = monster.direction;	monster.direction = 1;
				monsterXSpeed = monster.xSpeed;			monster.xSpeed = 0;
			}
			
			const uint8 eventID = se::isValidWeapon(number) ? (!powerup ? se::getBasicBulletOfWeapon(number) : se::getPoweredBulletOfWeapon(number)) : OBJECT::BULLET;
			angle &= 1023;
			array<int> angles(1, angle);
			array<int> bulletObjectIDs;
			
			if (spread == -1) spread = Spread;
			if (spread == SPREAD::RF)
				spread = powerup ? SPREAD::RFPU : SPREAD::RFNORMAL;
			else if (spread == SPREAD::ICE)
				spread = powerup ? SPREAD::ICEPU : SPREAD::NORMAL;
			else if (spread == SPREAD::GUN8)
				spread = !jjAllowsFireball ? SPREAD::PEPPERSPRAY : SPREAD::NORMAL;
			switch (spread) {
				case SPREAD::ICEPU:
					angles.insertLast((creatorType == CREATOR::PLAYER ? jjPlayers[creatorID].direction : jjObjects[creatorID].direction) >= 0 ? 0x000 : 0x200);
					break;
				case SPREAD::RFPU:
					angles.insertLast(angle);
					//fall through:
				case SPREAD::RFNORMAL:
					angles[0] -= 33;
					angles.insertLast(angle + 33);
					break;
				case SPREAD::PEPPERSPRAY: {
					angles.resize(0);
					for (int pepperSprayBulletCount = 0; pepperSprayBulletCount < 2; ++pepperSprayBulletCount)
						angles.insertLast(angle + int(jjRandom() & 31) - 16);
					if (creatorType == CREATOR::PLAYER)
						jjSample(xPos, yPos, SOUND::Sample(SOUND::AMMO_LASER2 + (jjRandom() & 1)));
					break; }
				case SPREAD::NORMAL:
				case SPREAD::TOASTER:
				default:
					//already fine
					break;
			}
			
			auto@ template = spread != SPREAD::PEPPERSPRAY ? (powerup ? PowerupObjectTemplate : RegularObjectTemplate) : MLLEWeapons::ObjectTemplate(
				xSpeed: 3 + (jjRandom() & 31) / 16.f + additionalXSpeed,
				animSpeed: powerup ? 2 : 1,
				curAnim: jjAnimSets[ANIM::AMMO].firstAnim + 10,
				lightType: (powerup && jjStrongPowerups) ? LIGHT::POINT2 : LIGHT::NONE,
				state: (powerup && jjStrongPowerups) ? STATE::FLY : STATE::START,
				var: array<int> = {0,0,0,0,0,0, (powerup && jjStrongPowerups) ? 8 : 0}
			);
			
			for (uint i = 0; i < angles.length; ++i) {
				int bulletObjectID = jjAddObject(eventID, xPos, yPos, creatorID, creatorType, BEHAVIOR::INACTIVE);
				jjOBJ@ obj = jjObjects[bulletObjectID];
				obj.xSpeed = jjCos(angles[i]);
				obj.ySpeed = jjSin(angles[i]);
				template.Apply(obj, AnimSet.firstAnim, false);
				obj.direction = (obj.xAcc < 0) ? -1 : 0;
				if (obj.special != 0 && abs(obj.ySpeed) > abs(obj.xSpeed)) { obj.curAnim = obj.special; obj.determineCurFrame(); }
				else if (obj.direction == 0) obj.direction = 1;
				obj.var[3] = number;
				if (IsRFMissile && (angle == 0 || angle == 0x200)) {
					const int dir = angle == 0 ? 1 : -1;
					obj.xSpeed = template.XSpeed * dir + additionalXSpeed;
					obj.xAcc = template.XAcc * dir;
					obj.ySpeed = (angles[i] - angle) / 33;
					obj.yAcc = 0;
					obj.var[7] = 0;
				} else {
					if (additionalXSpeed > 8) additionalXSpeed = 8;
					else if (additionalXSpeed < -8) additionalXSpeed = -8;
					obj.var[7] = int(additionalXSpeed * 65536);
					if (angle == 0x300 && !IsRFMissile) { //straight up
						obj.counterEnd = obj.counterEnd * 2 / 3;
					}
				}
				if (spread == SPREAD::TOASTER) {
					if (angle == 0x300) //straight up
						obj.yPos = (obj.yOrg -= 8);
					if (creatorType == CREATOR::PLAYER) {
						const float mult = (99 - jjPlayers[creatorID].fastfire) / 32.f;
						obj.xSpeed *= mult;
						obj.ySpeed *= mult;
					}
				}
				if (spread != SPREAD::PEPPERSPRAY) {
					if (obj.eventID == OBJECT::TNT) obj.eventID = OBJECT::BULLET;
					AssignBehavior(obj, powerup);
				} else {
					obj.behavior = BEHAVIOR::PEPPERBULLET;
					template.XSpeed = 3 + (jjRandom() & 31) / 16.f + additionalXSpeed;
				}
				if (obj.creatorType != CREATOR::PLAYER && obj.playerHandling == HANDLING::PLAYERBULLET) {
					if (obj.freeze == 0)
						obj.playerHandling = HANDLING::ENEMYBULLET;
					else {
						obj.playerHandling = HANDLING::SPECIAL;
						obj.scriptedCollisions = true;
						obj.behavior = FreezingEnemyBullet(obj.behavior);
					}
				}
				bulletObjectIDs.insertLast(bulletObjectID);
			}
			
			for (uint i = 0; i < bulletObjectIDs.length; ++i)
				jjObjects[bulletObjectIDs[i]].behave();
				
			if (creatorType != CREATOR::PLAYER) {
				jjOBJ@ monster = jjObjects[creatorID];
				monster.direction = monsterDirection;
				monster.xSpeed = monsterXSpeed;
			}
			
			return bulletObjectIDs;
		}
	}
	
	class FreezingEnemyBullet : jjBEHAVIORINTERFACE {
		private jjBEHAVIOR nativeBehavior;
		FreezingEnemyBullet(const jjBEHAVIOR &in nb) { nativeBehavior = nb; }
		void onBehave(jjOBJ@ obj) { obj.behave(nativeBehavior); }
		bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int) {
			if (bullet is null) {
				player.frozen = obj.freeze;
				obj.state = STATE::EXPLODE;
			}
			return true;
		}
	}
	
	class AmmoPickupWithCrateAndCorpseSupport : se::AmmoPickup {
		AmmoPickupWithCrateAndCorpseSupport(const ::jjANIMATION@ a, const ::jjANIMATION@ b, uint c = 3, SOUND::Sample d = SOUND::COMMON_PICKUPW1) { super(a,b,c,d); }
		void onBehave(::jjOBJ@ obj) override {
			if (obj.playerHandling == HANDLING::DELAYEDPICKUP) {
				if (obj.var[2] == 1) { //about to be available to collect
					obj.playerHandling = HANDLING::PICKUP; //native code also sets .scriptedCollisions to false, and that's not good
					obj.scriptedCollisions = true;
					obj.var[2] = 0; //probably not needed
				} else
					obj.scriptedCollisions = false; //so the native code knows to decrement var[2]
			}
			if ((obj.state == STATE::FLOAT || (obj.state == STATE::FLOATFALL && obj.ySpeed == 0)) && obj.counter > 0) { //temporary pickup (in MP)
				if (--obj.counter < 210) {
					if (obj.counter == 0) {
						obj.delete();
						return;
					}
					if (jjGameTicks & 1 == 0) //flickering
						return;
				}
			}
			se::AmmoPickup::onBehave(obj);
		}
	}
	void FlickerGemAmmoPickupAssigner(jjOBJ@ obj) {
		jjBEHAVIORINTERFACE@ intermediateCast;
		if (
			obj.eventID >= OBJECT::ICEAMMO3 &&
			obj.eventID <= OBJECT::GUN9AMMO3 &&
			(@intermediateCast = cast<jjBEHAVIORINTERFACE@>(jjObjectPresets[obj.eventID].behavior)) !is null &&
			cast<AmmoPickupWithCrateAndCorpseSupport@>(@intermediateCast) !is null
		)
			obj.behavior = jjObjectPresets[obj.eventID].behavior;
		else
			obj.behavior = BEHAVIOR::FLICKERGEM;
		obj.behave();
	}
	
	void _fitInto(jjPIXELMAP &inout dest, const jjPIXELMAP &in source) {
		const float xRatio = float(source.width) / float(dest.width);
		const float yRatio = float(source.height) / float(dest.height);
		for (uint xD = 0; xD < dest.width; ++xD)
			for (uint yD = 0; yD < dest.height; ++yD) {
				const uint8 color = source[uint(xD * xRatio), uint(yD * yRatio)];
				if (color != 0)
					dest[xD,yD] = color;
			}
	}

	uint GetFirstAvailableCustomAnimsetID() {
		uint setID;
		for (uint customAnimSetID = 0; customAnimSetID < 256; ++customAnimSetID)
			if (jjAnimSets[setID = ANIM::CUSTOM[customAnimSetID]] == 0)
				return setID;
		return ANIM::JAZZ;
	}
	
	void _recolorAnimation(const jjANIMATION@ dest, const jjANIMATION@ src, const array<uint8>@ lut) {
		for (uint i = 0; i < src.frameCount; ++i) {
			jjANIMFRAME@ destFrame = jjAnimFrames[dest + i], srcFrame = jjAnimFrames[src + i];
			jjPIXELMAP image(srcFrame);
			for (uint x = 0; x < image.width; ++x)
				for (uint y = 0; y < image.height; ++y)
					image[x,y] = lut[image[x,y]];
			image.save(destFrame);
			destFrame.hotSpotX = srcFrame.hotSpotX;
			destFrame.hotSpotY = srcFrame.hotSpotY;
		}
	}
	
		
	array<uint8>@ Ammo15s;
	
	void GenerateSupplementaryWeaponSprites(array<int>@ animations, uint8 ammoCrateEventID = 0, int pickupFrameIDToUseForAmmoCrate = 0) {
		if (animations[AnimationIndices::PickupIndex] >= 0 || animations[AnimationIndices::PoweredUpPickupIndex] >= 0) {
			array<bool> animationsToGenerate(4);
			array<uint> numberOfFramesToGenerate(0);
			
			if (animationsToGenerate[AnimationIndices::PickupIndex] = (animations[AnimationIndices::PickupIndex] < 0 && animations[AnimationIndices::PoweredUpPickupIndex] >= 0))
				numberOfFramesToGenerate.insertLast(jjAnimations[animations[AnimationIndices::PoweredUpPickupIndex]].frameCount);
			else if (animationsToGenerate[AnimationIndices::PoweredUpPickupIndex] = (animations[AnimationIndices::PoweredUpPickupIndex] < 0 && animations[AnimationIndices::PickupIndex] >= 0))
				numberOfFramesToGenerate.insertLast(jjAnimations[animations[AnimationIndices::PickupIndex]].frameCount);
			if (animationsToGenerate[AnimationIndices::AmmoCrateIndex] = (animations[AnimationIndices::AmmoCrateIndex] < 0 && ammoCrateEventID != 0))
				numberOfFramesToGenerate.insertLast(1);
			if (animationsToGenerate[AnimationIndices::PowerupIndex] = (animations[AnimationIndices::PowerupIndex] < 0))
				numberOfFramesToGenerate.insertLast(10);
			if (numberOfFramesToGenerate.length == 0) //nothing needs generating
				return;
			
			jjANIMSET@ animSet = jjAnimSets[GetFirstAvailableCustomAnimsetID()];
			animSet.allocate(numberOfFramesToGenerate);
			uint animsetAnimationIndex = 0;
			
			if ((animationsToGenerate[AnimationIndices::PickupIndex] || animationsToGenerate[AnimationIndices::PoweredUpPickupIndex]) && _static::PowerupColorConversionRandomizer is null)
				@_static::PowerupColorConversionRandomizer = jjRNG(jjLevelFileName[0] * jjLevelFileName[1] * jjLevelFileName[2] * jjLevelFileName[3] * jjLevelFileName[4]);
			if (animationsToGenerate[AnimationIndices::PickupIndex]) { //generate pickup anim from powerup pickup anim
				if (_static::PowerupToNormal is null) { //setup
					const array<uint8> OrangeTargets = { 32, 72, 88 };
					@_static::PowerupToNormal = array<uint8> = {
						48,
						(_static::PowerupColorConversionRandomizer() & 1 == 1) ? 16 : 88,
						(_static::PowerupColorConversionRandomizer() & 1 == 1) ? 24 : 40,
						OrangeTargets[_static::PowerupColorConversionRandomizer() % 3],
						80,
						0,
						72,
						64,
						32,
						(_static::PowerupColorConversionRandomizer() & 1 == 1) ? 24 : 32
					};
					_static::PowerupToNormal[5] = _static::PowerupToNormal[3];
					@_static::PowerupToNormalLUT = array<uint8>(256);
					for (uint i = 1; i < 256; ++i)
						_static::PowerupToNormalLUT[i] = (i < 16 || i >= 96) ? i : (_static::PowerupToNormal[(i >> 3) - 2] | (i & 7));
				}
				_recolorAnimation(jjAnimations[animations[AnimationIndices::PickupIndex] = (animSet + animsetAnimationIndex++)], jjAnimations[animations[AnimationIndices::PoweredUpPickupIndex]],_static::PowerupToNormalLUT);
			} else if (animationsToGenerate[AnimationIndices::PoweredUpPickupIndex]) { //generate powerup pickup anim from pickup anim
				if (_static::NormalToPowerup is null) { //setup
					@_static::NormalToPowerup = array<uint8> = {
						24,
						(_static::PowerupColorConversionRandomizer() & 1 == 1) ? 32 : 88,
						(_static::PowerupColorConversionRandomizer() & 1 == 1) ? 40 : 88,
						32,
						16,
						32,
						72,
						40,
						48,
						40
					};
					@_static::NormalToPowerupLUT = array<uint8>(256);
					for (uint i = 1; i < 256; ++i)
						_static::NormalToPowerupLUT[i] = (i < 16 || i >= 96) ? i : (_static::NormalToPowerup[(i >> 3) - 2] | (i & 7));
				}
				_recolorAnimation(jjAnimations[animations[AnimationIndices::PoweredUpPickupIndex] = (animSet + animsetAnimationIndex++)], jjAnimations[animations[AnimationIndices::PickupIndex]], _static::NormalToPowerupLUT);
			}
			
			
			if (animationsToGenerate[AnimationIndices::AmmoCrateIndex]) { //generate crate anim from pickup anim
				if (_static::CrateImage is null) { //setup
					@_static::CrateImage = jjPIXELMAP(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PICKUPS] + 59]]); //TNT crate--unlikely to have been replaced
					for (uint x = 4; x < 20; ++x)
						for (uint y = 8; y < 18; ++y)
							if (_static::CrateImage[x,y] >= 88) //get rid of existing purple
								_static::CrateImage[x,y] = 41;
				}
				
				jjPIXELMAP destImage(_static::CrateImage.width, _static::CrateImage.height);
				for (uint x = 0; x < destImage.width; ++x)
					for (uint y = 0; y < destImage.height; ++y)
						destImage[x,y] = _static::CrateImage[x,y];
						
				jjPIXELMAP resizedSourceImage(13, 13);
				jjPIXELMAP pickupImage(jjAnimFrames[jjAnimations[animations[AnimationIndices::PickupIndex]].firstFrame + pickupFrameIDToUseForAmmoCrate]);
				_fitInto(resizedSourceImage, pickupImage);
				
				for (uint x = 0; x < resizedSourceImage.width; ++x)
					for (uint y = 0; y < resizedSourceImage.height; ++y) {
						uint8 color = resizedSourceImage[x, y];
						if (color != 0) {
							if (color == 15) //white
								color = 0; //bright
							else
								color &= 7;
							destImage[5+x,7+y] = 95 - (color >> 1);
						}
				}
				
				jjANIMFRAME@ destFrame = jjAnimFrames[jjAnimations[animations[AnimationIndices::AmmoCrateIndex] = (animSet + animsetAnimationIndex++)]];
				destImage.save(destFrame);
				destFrame.hotSpotX = -13;
				destFrame.hotSpotY = -14;
			}
			
			if (animationsToGenerate[AnimationIndices::PowerupIndex]) { //generate powerup monitor anim from powerup pickup anim
				if (_static::PowerupImages is null) { //setup
					@_static::PowerupImages = array<jjPIXELMAP@>();
					const jjANIMATION@ powerupTemplate = jjAnimations[jjAnimSets[ANIM::PICKUPS] + 61]; //bouncer powerup has easy colors to work with
					jjPIXELMAP powerupFrameBlankCorner(jjAnimFrames[powerupTemplate + 2]);
					for (uint x = 3; x <= 13; ++x)
						for (uint y = 8; y <= 18; ++y) {
							const uint8 oldColor = powerupFrameBlankCorner[x,y];
							if (oldColor >= 88 || (oldColor >= 59 && oldColor <= 63)) //purple or yellow
								powerupFrameBlankCorner[x,y] = 71; //monitor black
						}
					for (uint i = 0; i < 10; ++i) { //powerupTemplate.length
						jjANIMFRAME@ srcFrame = jjAnimFrames[powerupTemplate + i];
						_static::PowerupImages.insertLast(jjPIXELMAP(srcFrame));
						jjPIXELMAP@ destImage = @_static::PowerupImages[_static::PowerupImages.length-1];
						for (uint x = 0; x < 17; ++x)
							for (uint y = 0; y < 21; ++y)
								destImage[x,y] = powerupFrameBlankCorner[x,y];
					}
				}
				
				const jjANIMATION@ poweredUpPickupAnimation = jjAnimations[animations[AnimationIndices::PoweredUpPickupIndex]];
				const jjANIMATION@ newAnim = jjAnimations[animations[AnimationIndices::PowerupIndex] = (animSet + animsetAnimationIndex++)];
				int minX = 0, minY = 0;
				int maxRight = 0, maxBottom = 0;
				array<jjPIXELMAP@> expandedPickupImages(poweredUpPickupAnimation.frameCount, null);
				for (uint i = 0; i < poweredUpPickupAnimation.frameCount; ++i) {
					const jjANIMFRAME@ frame = jjAnimFrames[poweredUpPickupAnimation + i];
					if (frame.hotSpotX < minX) minX = frame.hotSpotX;
					if (frame.hotSpotY < minY) minY = frame.hotSpotY;
					const int right = frame.width + frame.hotSpotX, bottom = frame.height + frame.hotSpotY;
					if (right > maxRight) maxRight = right;
					if (bottom > maxBottom) maxBottom = bottom;
				}
				for (uint i = 0; i < poweredUpPickupAnimation.frameCount; ++i) {
					@expandedPickupImages[i] = jjPIXELMAP(maxRight - minX, maxBottom - minY);
					const jjANIMFRAME@ sourceFrame = jjAnimFrames[poweredUpPickupAnimation + i];
					const jjPIXELMAP sourceImage(sourceFrame);
					for (uint xS = 0; xS < sourceFrame.width; ++xS)
						for (uint yS = 0; yS < sourceFrame.height; ++yS)
							expandedPickupImages[i][xS - minX + sourceFrame.hotSpotX, yS - minY + sourceFrame.hotSpotY] = sourceImage[xS, yS];
				}
				for (uint i = 0; i < 10; ++i) {
					const jjPIXELMAP@ templateImage = _static::PowerupImages[i];
					jjPIXELMAP destImage(templateImage.width, templateImage.height);
					for (uint x = 0; x < destImage.width; ++x)
						for (uint y = 0; y < destImage.height; ++y)
							destImage[x,y] = templateImage[x,y];
							
					jjPIXELMAP resizedPickupImage(12, 14); //something like that
					_fitInto(resizedPickupImage, expandedPickupImages[i % expandedPickupImages.length]);
					
					for (uint x = 0; x < resizedPickupImage.width; ++x)
						for (uint y = 0; y < resizedPickupImage.height; ++y) {
							const uint8 color = resizedPickupImage[x,y];
							if (color != 0)
								destImage[3+x, 4+y] = color;
						}
					
					jjANIMFRAME@ destFrame = jjAnimFrames[newAnim + i];
					destImage.save(destFrame);
					destFrame.hotSpotX = destFrame.hotSpotY = -14;
				}
			}
		}
	}
	
	void CopyObject(jjOBJ@ dest, const jjOBJ@ src) {
		dest.frameID =	src.frameID;
		dest.curFrame =	src.curFrame;
		dest.curAnim =	src.curAnim;
		dest.killAnim =	src.killAnim;
		dest.behavior =	src.behavior;
		dest.noHit =	src.noHit;
		dest.objType =	src.objType;
		dest.energy =	src.energy;
		dest.points =	src.points;
		dest.counterEnd=src.counterEnd;
		dest.state =	src.state;
		for (int i = 0; i < 11; ++i)
		dest.var[i] =	src.var[i];
	}
	void UpdateActiveObjectAnimations(uint number) {
		if (se::isValidWeapon(number)) {
			array<uint8> eventIDs = {se::getAmmoPickupOfWeapon(number), (Ammo15s !is null && Ammo15s[number-1] != 0) ? Ammo15s[number-1] : se::getAmmoCrateOfWeapon(number), se::getPowerupMonitorOfWeapon(number)};
			for (uint objectID = jjObjectCount; --objectID != 0;) {
				jjOBJ@ obj = jjObjects[objectID];
				if (obj.isActive && eventIDs.find(obj.eventID) >= 0)
					CopyObject(obj, jjObjectPresets[obj.eventID]);
			}
		}
	}
	
	bool WeaponHasBeenReplaced(uint number) {
		if (!se::isValidWeapon(number))
			return false;
		const array<BEHAVIOR::Behavior> behaviors = {
			BEHAVIOR::BULLET, BEHAVIOR::BULLET,
			BEHAVIOR::BOUNCERBULLET, BEHAVIOR::BOUNCERBULLETPU,
			BEHAVIOR::BULLET, BEHAVIOR::ICEBULLETPU,
			BEHAVIOR::SEEKERBULLET, BEHAVIOR::SEEKERBULLET,
			BEHAVIOR::RFBULLET, BEHAVIOR::RFBULLET,
			BEHAVIOR::TOASTERBULLET, BEHAVIOR::TOASTERBULLET,
			BEHAVIOR::TNT, BEHAVIOR::TNT,
			BEHAVIOR::BULLET, BEHAVIOR::BULLET,
			BEHAVIOR::ELECTROBULLET, BEHAVIOR::ELECTROBULLET,
		};
		const int idx = (number - 1) * 2;
		return !(
			jjObjectPresets[se::getBasicBulletOfWeapon(number)].behavior == behaviors[idx] &&
			jjObjectPresets[se::getPoweredBulletOfWeapon(number)].behavior == behaviors[idx + 1]);
	}
	
	namespace HelpfulBulletFunctions {
		int GetAngle(const jjOBJ@ obj) {
			return int(atan2(
					(obj.xSpeed >= 0) ? obj.ySpeed : -obj.ySpeed,
					abs(obj.xSpeed)
			) * -512.0 * 0.318309886142228);
		}
		int GetDirection(const jjOBJ@ obj) {
			if (obj.xSpeed >= 0)
				return 1;
			return -1;
		}
		void DrawAngled(const jjOBJ@ obj, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int layerZ = 4, int layerXY = 4, int8 playerID = -1) {
			jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, GetAngle(obj), GetDirection(obj) * xScale, yScale, mode, param, layerZ, layerXY, playerID);
		}
		bool MaskedPixel(const jjOBJ@ obj) {
			return jjMaskedPixel(int(obj.xPos), int(obj.yPos));
		}
		bool IsPowerup(const jjOBJ@ obj) {
			return (obj.var[6] & 8) == 8;
		}
		void Blast(jjOBJ@ obj, uint animID) {
			obj.killAnim = animID;
			obj.state = STATE::EXPLODE;
			obj.behavior = BEHAVIOR::RFBULLET;
			obj.behave(BEHAVIOR::DEFAULT, false);
		}
		bool PlayerIsEnemy(const jjOBJ@ obj, const jjPLAYER@ other) {
			if (obj.creatorType != CREATOR::PLAYER)
				return true;
			else
				return jjPlayers[obj.creatorID].isEnemy(other);
		}
		int GetNearestEnemyPlayer(const jjOBJ@ obj, uint maxDistance, bool canBeFlickering = false) {
			if (jjGameMode <= GAME::COOP && obj.creatorType == CREATOR::PLAYER)
				return -1;
			if (!canBeFlickering && jjGameState != GAME::STARTED && jjGameState != GAME::OVERTIME)
				return -1;
				
			int nearestPlayerID = -1;
			uint minDistance = maxDistance * maxDistance;
			for (uint i = 0; i < 32; ++i) {
				const jjPLAYER@ potentialEnemy = jjPlayers[i];
				if (!potentialEnemy.isInGame)
					continue;
				if (!canBeFlickering && potentialEnemy.blink != 0)
					continue;
				const float dx = potentialEnemy.xPos - obj.xPos, dy = potentialEnemy.yPos - obj.yPos;
				const uint distance = uint(dx * dx + dy * dy);
				if (distance < minDistance && PlayerIsEnemy(obj, potentialEnemy)) {
					minDistance = distance;
					nearestPlayerID = i;
				}
			}
			return nearestPlayerID;
		}
		int GetNearestEnemyObject(const jjOBJ@ obj, uint maxDistance) {
			if (obj.creatorType != CREATOR::PLAYER)
				return -1;
			int nearestObjectID = -1;
			uint minDistance = maxDistance * maxDistance;
			for (int i = 1; i < jjObjectCount; ++i) {
				const jjOBJ@ potentialEnemy = jjObjects[i];
				if (!potentialEnemy.isActive || !potentialEnemy.isTarget)
					continue;
				const float dx = potentialEnemy.xPos - obj.xPos, dy = potentialEnemy.yPos - obj.yPos;
				const uint distance = uint(dx * dx + dy * dy);
				if (distance < minDistance) {
					minDistance = distance;
					nearestObjectID = i;
				}
			}
			return nearestObjectID;
		}
		array<float>@ GetNearestEnemyPosition(const jjOBJ@ obj, uint maxDistance, bool canBeFlickering = false) {
			int nearestEnemyID = GetNearestEnemyPlayer(obj, maxDistance, canBeFlickering);
			if (nearestEnemyID >= 0) {
				const jjPLAYER@ target = jjPlayers[nearestEnemyID];
				return array<float> = {target.xPos, target.yPos};
			}
			nearestEnemyID = GetNearestEnemyObject(obj, maxDistance);
			if (nearestEnemyID >= 0) {
				const jjOBJ@ target = jjObjects[nearestEnemyID];
				return array<float> = {target.xPos, target.yPos};
			}
			return null;
		}
	}

	const float LevelRight = float(jjLayerWidth[4] * 32);
	const float LevelBottom = float(jjLayerHeight[4] * 32);
}

namespace _static {
	jjRNG@ PowerupColorConversionRandomizer;
	array<uint8>@ NormalToPowerupLUT, NormalToPowerup, PowerupToNormalLUT, PowerupToNormal;
	jjPIXELMAP@ CrateImage;
	array<jjPIXELMAP@>@ PowerupImages;
}

namespace DefaultWeapons {
	void DefaultSample(const jjOBJ@ obj, WEAPON::Weapon number, bool definitelyPlaySample = false) {
		const bool powerup = MLLEWeapons::HelpfulBulletFunctions::IsPowerup(obj);
		if (obj.creatorType == CREATOR::PLAYER || definitelyPlaySample)
			switch (number) {
				case WEAPON::BLASTER: {
					const bool jazz = obj.creatorType != CREATOR::PLAYER || jjPlayers[obj.creator].charCurr == CHAR::JAZZ;
					int sample;
					int frequency = 22050;
					if (!powerup) {
						if (jazz)
							sample = SOUND::AMMO_GUNJAZZ;
						else
							sample = SOUND::AMMO_GUN1;
					} else {
						if (jazz)
							sample = SOUND::AMMO_FUMP;
						else {
							sample = SOUND::AMMO_FUMP + ((jjRandom() & 1) * 3);
							frequency = 0;
						}
					}
					jjSample(obj.xPos,obj.yPos, SOUND::Sample(sample), 0, frequency);
					break;
				}
				case WEAPON::BOUNCER:
					jjSample(obj.xPos, obj.yPos, SOUND::Sample((powerup ? SOUND::AMMO_BMP1 : SOUND::AMMO_GUNFLP) + (jjRandom() % 6)), 0, (obj.creatorType != CREATOR::PLAYER || jjPlayers[obj.creator].charCurr == CHAR::JAZZ) ? 11025 : 22050);
					break;
				case WEAPON::ICE:
					if (powerup)
						jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::AMMO_ICEPU1 + (jjRandom() & 3)));
					else
						jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::AMMO_ICEGUN + (jjRandom() % 3)));
					break;
				case WEAPON::SEEKER:
					jjSample(obj.xPos, obj.yPos, SOUND::AMMO_MISSILE);
					break;
				case WEAPON::RF:
					jjSample(obj.xPos, obj.yPos, SOUND::AMMO_LAZRAYS);
					break;
				//TNT makes no noise
				case WEAPON::GUN8:
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_RINGGUN); //fireball
					break;
				case WEAPON::GUN9:
					jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::AMMO_LASER2 + (jjRandom() & 1)));
					break;
			}
	}
}

namespace SOUND {
	array<Sample>@ _allSampleConstantsInRoughOrderOfLikelihoodToBeUsed;
	array<Sample>@ GetAllSampleConstantsInRoughOrderOfLikelihoodToBeUsed() {
		if (_allSampleConstantsInRoughOrderOfLikelihoodToBeUsed is null)
			@_allSampleConstantsInRoughOrderOfLikelihoodToBeUsed = array<Sample> = {
				BONUS_BONUSBLUB, ENDING_OHTHANK, EPICLOGO_EPIC1, EPICLOGO_EPIC2, FAN_FAN, GLOVE_HIT, STONED_STONED,
				INTRO_BOEM1, INTRO_BOEM2, INTRO_BRAKE, INTRO_END, INTRO_GRAB, INTRO_GREN1, INTRO_GREN2, INTRO_GREN3, INTRO_GUNM0, INTRO_GUNM1, INTRO_GUNM2, INTRO_HELI, INTRO_HITSPAZ, INTRO_HITTURT, INTRO_IFEEL, INTRO_INHALE, INTRO_INSECT, INTRO_KATROL, INTRO_LAND, INTRO_MONSTER, INTRO_MONSTER2, INTRO_ROCK, INTRO_ROPE1, INTRO_ROPE2, INTRO_RUN, INTRO_SHOT1, INTRO_SHOTGRN, INTRO_SKI, INTRO_STRING, INTRO_SWISH1, INTRO_SWISH2, INTRO_SWISH3, INTRO_SWISH4, INTRO_UHTURT, INTRO_UP1, INTRO_UP2, INTRO_WIND_01,
				ORANGE_BOEML, ORANGE_BOEMR, ORANGE_BUBBELSL, ORANGE_BUBBELSR, ORANGE_GLAS1L, ORANGE_GLAS1R, ORANGE_GLAS2L, ORANGE_GLAS2R, ORANGE_MERGE, ORANGE_SWEEP0L, ORANGE_SWEEP0R, ORANGE_SWEEP1L, ORANGE_SWEEP1R, ORANGE_SWEEP2L, ORANGE_SWEEP2R,
				P2_CRUNCH, P2_FART, P2_FOEW1, P2_FOEW4, P2_FOEW5, P2_FROG1, P2_FROG2, P2_FROG3, P2_FROG4, P2_FROG5, P2_KISS4, P2_OPEN, P2_PINCH1, P2_PINCH2, P2_PLOPSEQ1, P2_PLOPSEQ2, P2_PLOPSEQ3, P2_PLOPSEQ4, P2_POEP, P2_PTOEI, P2_SPLUT, P2_THROW, P2_TONG,
				BILSBOSS_BILLAPPEAR, BILSBOSS_FINGERSNAP, BILSBOSS_FIRE, BILSBOSS_FIRESTART, BILSBOSS_SCARY3, BILSBOSS_THUNDER, BILSBOSS_ZIP, BUBBA_BUBBABOUNCE1, BUBBA_BUBBABOUNCE2, BUBBA_BUBBAEXPLO, BUBBA_FROG2, BUBBA_FROG3, BUBBA_FROG4, BUBBA_FROG5, BUBBA_SNEEZE2, BUBBA_TORNADOATTACK2, DEVILDEVAN_DRAGONFIRE, DEVILDEVAN_FLAP, DEVILDEVAN_FROG4, DEVILDEVAN_JUMPUP, DEVILDEVAN_LAUGH, DEVILDEVAN_PHASER2, DEVILDEVAN_STRECH2, DEVILDEVAN_STRECHTAIL, DEVILDEVAN_STRETCH1, DEVILDEVAN_STRETCH3, DEVILDEVAN_VANISH1, DEVILDEVAN_WHISTLEDESCENDING2, DEVILDEVAN_WINGSOUT, QUEEN_LADYUP, QUEEN_SCREAM, ROBOT_BIG1, ROBOT_BIG2, ROBOT_CAN1, ROBOT_CAN2, ROBOT_HYDRO, ROBOT_HYDRO2, ROBOT_HYDROFIL, ROBOT_HYDROPUF, ROBOT_IDLE1, ROBOT_IDLE2, ROBOT_JMPCAN1, ROBOT_JMPCAN10, ROBOT_JMPCAN2, ROBOT_JMPCAN3, ROBOT_JMPCAN4, ROBOT_JMPCAN5, ROBOT_JMPCAN6, ROBOT_JMPCAN7, ROBOT_JMPCAN8, ROBOT_JMPCAN9, ROBOT_METAL1, ROBOT_METAL2, ROBOT_METAL3, ROBOT_METAL4, ROBOT_METAL5, ROBOT_OPEN, ROBOT_OUT, ROBOT_POEP, ROBOT_POLE, ROBOT_SHOOT, ROBOT_STEP1, ROBOT_STEP2, ROBOT_STEP3, SONCSHIP_METAL1, SONCSHIP_MISSILE2, SONCSHIP_SCRAPE, SONCSHIP_SHIPLOOP, SONCSHIP_TARGETLOCK, TUFBOSS_CATCH, TUFBOSS_RELEASE, TUFBOSS_SWING, UTERUS_CRABCLOSE, UTERUS_CRABOPEN2, UTERUS_SCISSORS1, UTERUS_SCISSORS2, UTERUS_SCISSORS3, UTERUS_SCISSORS4, UTERUS_SCISSORS5, UTERUS_SCISSORS6, UTERUS_SCISSORS7, UTERUS_SCISSORS8, UTERUS_SCREAM1, UTERUS_STEP1, UTERUS_STEP2,
				WITCH_LAUGH, WITCH_MAGIC, BAT_BATFLY1, BUMBEE_BEELOOP, CATERPIL_RIDOE, DEMON_RUN, DOG_AGRESSIV, DOG_SNIF1, DOG_WAF1, DOG_WAF2, DOG_WAF3, DRAGFLY_BEELOOP, EVA_KISS1, EVA_KISS2, EVA_KISS3, EVA_KISS4, FATCHK_HIT1, FATCHK_HIT2, FATCHK_HIT3, FENCER_FENCE1, HATTER_CUP, HATTER_HAT, HATTER_PTOEI, HATTER_SPLIN, HATTER_SPLOUT, LABRAT_BITE, LABRAT_EYE2, LABRAT_EYE3, LABRAT_MOUSE1, LABRAT_MOUSE2, LABRAT_MOUSE3, LIZARD_LIZ1, LIZARD_LIZ2, LIZARD_LIZ4, LIZARD_LIZ6, MONKEY_SPLUT, MONKEY_THROW, MOTH_FLAPMOTH, PINBALL_BELL, PINBALL_FLIP1, PINBALL_FLIP2, PINBALL_FLIP3, PINBALL_FLIP4, RAPIER_GOSTDIE, RAPIER_GOSTLOOP, RAPIER_GOSTOOOH, RAPIER_GOSTRIP, RAPIER_HITCHAR, ROCK_ROCK1, SCIENCE_PLOPKAOS, SKELETON_BONE1, SKELETON_BONE2, SKELETON_BONE3, SKELETON_BONE5, SKELETON_BONE6, SKELETON_BONE7, SMALTREE_FALL, SMALTREE_GROUND, SMALTREE_HEAD, STEAM_STEAM, SUCKER_FART, SUCKER_PINCH1, SUCKER_PINCH2, SUCKER_PINCH3, SUCKER_PLOPSEQ1, SUCKER_PLOPSEQ2, SUCKER_PLOPSEQ3, SUCKER_PLOPSEQ4, SUCKER_UP, TURTLE_BITE3, TURTLE_HIDE, TURTLE_HITSHELL, TURTLE_IDLE1, TURTLE_IDLE2, TURTLE_NECK, TURTLE_SPK1TURT, TURTLE_SPK2TURT, TURTLE_SPK3TURT, TURTLE_SPK4TURT, TURTLE_TURN, WIND_WIND2A, BONUS_BONUS1, P2_SPLOUT, INTRO_BLOW
			};
		return @_allSampleConstantsInRoughOrderOfLikelihoodToBeUsed;
	}
}