#pragma require "SEweapon.asc"
#pragma require "SEroller.j2a"
#pragma offer "SEroller.wav"
#include "SEweapon.asc"
namespace se {
shared class RollerWeapon : WeaponInterface {
	private ::jjANIMSET@ m_animSet;
	private SOUND::Sample m_sample = SOUND::COMMON_FLAP;
	protected ::jjANIMSET@ getAnimSet() const {
		return @m_animSet;
	}
	protected SOUND::Sample getSample() const {
		return m_sample;
	}
	protected bool loadAnimSet(::jjANIMSET@ animSet, const ::string &in filename, uint setID) {
		if (animSet !is null && !::jjSTREAM(filename).isEmpty()) {
			@m_animSet = @animSet.load(setID, filename);
			return true;
		}
		return false;
	}
	protected bool loadSample(SOUND::Sample sample, const ::string &in filename) {
		if (::jjSampleLoad(sample, filename)) {
			m_sample = sample;
			return true;
		}
		return false;
	}
	protected bool move(::jjOBJ@ obj, int mask, float verticalTolerance, float maxSpeed) const {
		::jjANIMFRAME@ maskFrame = @::jjAnimFrames[::jjAnimations[m_animSet + mask]];
		float xRadius = maskFrame.width / 2.f, yRadius = maskFrame.height / 2.f;
		float xCenter = obj.xPos + maskFrame.hotSpotX + xRadius, yCenter = obj.yPos + maskFrame.hotSpotY + yRadius;
		float xAvg = 0.f, yAvg = 0.f;
		bool noRoom = true;
		bool overWater = obj.yPos < ::jjWaterLevel;
		bool canSkipOverWater = overWater && obj.ySpeed > 1.5f && ::abs(obj.xSpeed) > obj.ySpeed;
		bool skippingOverWater = false;
		for (int i = 0; i < 1024; i += 16) {
			float cosine = ::jjCos(i);
			float sine = ::jjSin(i);
			float x = xCenter + cosine * xRadius;
			float y = yCenter + sine * yRadius;
			if (::jjMaskedPixel(int(x + 0.5f), int(y + 0.5f))) {
				xAvg += cosine;
				yAvg += sine;
			} else if (canSkipOverWater && y >= ::jjWaterLevel) {
				skippingOverWater = true;
				xAvg += cosine;
				yAvg += sine;
			} else {
				noRoom = false;
			}
		}
		float avg = sqrt(xAvg * xAvg + yAvg * yAvg);
		float xSpeed = obj.xSpeed, ySpeed = obj.ySpeed + obj.yAcc;
		if (avg != 0.f) {
			xAvg /= avg;
			yAvg /= avg;
			if (::abs(xAvg) <= verticalTolerance) {
				xSpeed += yAvg * obj.xAcc;
				ySpeed -= xAvg * obj.xAcc;
			}
			float projection = xSpeed * xAvg + ySpeed * yAvg;
			if (projection > 0.f) {
				if (skippingOverWater)
					projection *= 1.8f;
				xSpeed -= projection * xAvg;
				ySpeed -= projection * yAvg;
			}
		}
		float speed = sqrt(xSpeed * xSpeed + ySpeed * ySpeed);
		if (speed > maxSpeed) {
			float factor = maxSpeed / speed;
			xSpeed *= factor;
			ySpeed *= factor;
		}
		obj.xSpeed = xSpeed;
		obj.ySpeed = ySpeed;
		obj.xPos += xSpeed;
		obj.yPos += ySpeed;
		if (skippingOverWater || overWater && obj.yPos >= ::jjWaterLevel) {
			int id = ::jjAddObject(OBJECT::EXPLOSION, obj.xPos, ::jjWaterLevel, obj.objectID);
			if (id != 0) {
				::jjOBJ@ splash = @::jjObjects[id];
				splash.determineCurAnim(ANIM::COMMON, 3);
				::jjSample(obj.xPos, ::jjWaterLevel, SOUND::COMMON_WATER, 32);
			}
		}
		return noRoom;
	}
	protected void behaveCommon(::jjOBJ@ obj, int mask, float verticalTolerance, float maxSpeed) const {
		switch (obj.state) {
			case STATE::START:
				if (obj.creatorType == CREATOR::PLAYER && ::jjPlayers[obj.creatorID].isLocal)
					::jjSample(obj.xPos, obj.yPos, m_sample);
				if (obj.xSpeed == 0.f) {
					if (obj.var[7] != 0)
						obj.direction = obj.var[7] < 0 ? -1 : 1;
					else if (::jjGameConnection != GAME::LOCAL)
						obj.direction = 1;
					else if (obj.creatorType == CREATOR::PLAYER)
						obj.direction = ::jjPlayers[obj.creatorID].direction;
					else if (obj.creatorType == CREATOR::OBJECT)
						obj.direction = ::jjObjects[obj.creatorID].direction;
				}
				obj.xAcc = ::jjObjectPresets[obj.eventID].xAcc;
				if (obj.direction < 0)
					obj.xAcc = -obj.xAcc;
				obj.yAcc = ::jjObjectPresets[obj.eventID].yAcc;
				obj.xSpeed += obj.var[7] / 1e5f;
				obj.state = STATE::ROTATE;
			case STATE::ROTATE:
				if (move(obj, mask, verticalTolerance, maxSpeed) || obj.counter++ > int(obj.counterEnd))
					obj.state = STATE::EXPLODE;
				if (::jjGameTicks % 3 == 0) {
					obj.frameID++;
					if (obj.frameID >= int(::jjAnimations[obj.curAnim].frameCount))
						obj.frameID = 0;
				}
				obj.determineCurFrame();
				obj.draw();
				break;
			case STATE::EXPLODE:
				obj.curAnim = obj.killAnim;
				if (obj.curAnim == 0) {
					obj.delete();
				} else {
					if (obj.playerHandling != HANDLING::EXPLOSION) {
						obj.frameID = obj.freeze = 0;
						obj.isTarget = obj.triggersTNT = false;
						obj.playerHandling = HANDLING::EXPLOSION;
					}
					if (::jjGameTicks & 3 == 0)
						obj.frameID++;
					if (obj.frameID < int(::jjAnimations[obj.curAnim].frameCount)) {
						obj.determineCurFrame();
						obj.draw();
					} else {
						obj.delete();
					}
				}
				break;
			case STATE::KILL:
			case STATE::DEACTIVATE:
				obj.delete();
				break;
		}
	}
	protected void behaveBasic(::jjOBJ@ obj) const {
		behaveCommon(obj, 8, 0.9f, obj.yPos < ::jjWaterLevel ? 14.f : 8.f);
	}
	protected void behavePowered(::jjOBJ@ obj) const {
		if (obj.yPos >= ::jjWaterLevel) {
			obj.behavior = @::jjVOIDFUNCOBJ(behaveBasic);
			obj.counterEnd = 90;
			obj.curFrame = ::jjAnimations[obj.curAnim = obj.special = m_animSet + 4];
			obj.killAnim = m_animSet + 6;
			obj.var[6] = 0;
			obj.behave();
			::jjANIMFRAME@ maskFrame = @::jjAnimFrames[::jjAnimations[m_animSet + 9]];
			float xRadius = maskFrame.width / 2.f, yRadius = maskFrame.height / 2.f;
			for (int i = 0; i < 1024; i += 64) {
				::jjAddObject(OBJECT::BUBBLE, obj.xPos + ::jjSin(i) * xRadius, obj.yPos + ::jjCos(i) * yRadius, obj.objectID);
			}
		} else {
			behaveCommon(obj, 9, 2.f, 14.f);
		}
	}
	protected void behaveTNT(::jjOBJ@ obj) const {
		if (obj.creatorType == CREATOR::PLAYER && ::jjPlayers[obj.creatorID].powerup[WEAPON::TNT]) {
			obj.behavior = @::jjVOIDFUNCOBJ(behavePowered);
			obj.counterEnd = 120;
			obj.curFrame = ::jjAnimations[obj.curAnim = obj.special = m_animSet + 5];
			obj.killAnim = m_animSet + 7;
			obj.var[6] = 26;
		} else {
			obj.behavior = @::jjVOIDFUNCOBJ(behaveBasic);
			obj.counterEnd = 90;
			obj.curFrame = ::jjAnimations[obj.curAnim = obj.special = m_animSet + 4];
			obj.killAnim = m_animSet + 6;
			obj.var[6] = 0;
		}
		obj.behave();
	}
	protected void prepareWeaponProfile(::jjWEAPON@ weapon) const {
		weapon.defaultSample = false;
		weapon.gradualAim = false;
		weapon.multiplier = 1;
		weapon.replacedByBubbles = false;
		weapon.spread = SPREAD::NORMAL;
		weapon.style = WEAPON::NORMAL;
	}
	protected void prepareBulletPresets(::jjOBJ@ basic, ::jjOBJ@ powerup, uint number) const {
		if (basic is powerup) {
			basic.behavior = @::jjVOIDFUNCOBJ(behaveTNT);
		} else {
			basic.behavior = @::jjVOIDFUNCOBJ(behaveBasic);
			powerup.behavior = @::jjVOIDFUNCOBJ(behavePowered);
		}
		basic.animSpeed = powerup.animSpeed = 2;
		basic.counterEnd = 90;
		powerup.counterEnd = 120;
		basic.curFrame = ::jjAnimations[basic.curAnim = basic.special = m_animSet + 4];
		powerup.curFrame = ::jjAnimations[powerup.curAnim = powerup.special = m_animSet + 5];
		basic.direction = powerup.direction = 1;
		basic.energy = powerup.energy = basic.freeze = powerup.freeze = 0;
		basic.frameID = powerup.frameID = 0;
		basic.killAnim = m_animSet + 6;
		powerup.killAnim = m_animSet + 7;
		basic.lightType = powerup.lightType = LIGHT::POINT;
		basic.playerHandling = powerup.playerHandling = HANDLING::PLAYERBULLET;
		basic.var[3] = powerup.var[3] = number;
		basic.var[6] = 0;
		powerup.var[6] = 26;
		basic.xAcc = powerup.xAcc = 0.5f;
		basic.yAcc = powerup.yAcc = 0.25f;
		basic.xSpeed = powerup.xSpeed = 5.f;
		basic.ySpeed = powerup.ySpeed = 0.f;
	}
	protected void preparePickupPresets(::jjOBJ@ ammo3, ::jjOBJ@ ammo15, ::jjOBJ@ powerup) const {
		if (ammo3 !is null) {
			ammo3.behavior = @AmmoPickup(::jjAnimations[m_animSet], ::jjAnimations[m_animSet + 1]);
			ammo3.curAnim = m_animSet;
			ammo3.frameID = 0;
			ammo3.determineCurFrame();
		}
		if (ammo15 !is null) {
			ammo15.curAnim = m_animSet + 2;
			ammo15.frameID = 0;
			ammo15.determineCurFrame();
		}
		if (powerup !is null) {
			powerup.curAnim = m_animSet + 3;
			powerup.frameID = 0;
			powerup.determineCurFrame();
		}
	}
	::jjANIMSET@ loadAnims(::jjANIMSET@ animSet) override {
		loadAnimSet(animSet, "SEroller.j2a", 0);
		return @animSet;
	}
	::array<bool>@ loadSamples(const ::array<SOUND::Sample>& samples) override {
		return @::array<bool>(1, samples.length() == 1 && loadSample(samples[0], "SEroller.wav"));
	}
	uint getSampleCount() const override {
		return 1;
	}
	uint getTraits(bool powerup) const override {
		uint result = weapon_default_traits;
		if (powerup)
			result |= weapon_melts_ice;
		return result;
	}
	uint getMaxDamage(bool powerup) const override {
		return powerup ? 2 : 1;
	}
	bool setAsWeapon(uint number, WeaponHook@ weaponHook = null) override {
		if (m_animSet !is null && isValidWeapon(number)) {
			uint basic = getBasicBulletOfWeapon(number);
			uint powered = getPoweredBulletOfWeapon(number);
			uint ammo3 = getAmmoPickupOfWeapon(number);
			uint ammo15 = getAmmoCrateOfWeapon(number);
			uint powerup = getPowerupMonitorOfWeapon(number);
			if (weaponHook !is null) {
				weaponHook.resetCallbacks(number);
				weaponHook.setWeaponSprite(number, false, ::jjAnimations[m_animSet]);
				weaponHook.setWeaponSprite(number, true, ::jjAnimations[m_animSet + 1]);
			}
			prepareWeaponProfile(@::jjWeapons[number]);
			prepareBulletPresets(@::jjObjectPresets[basic], @::jjObjectPresets[powered], number);
			preparePickupPresets(ammo3 != 0 ? @::jjObjectPresets[ammo3] : null, ammo15 != 0 ? @::jjObjectPresets[ammo15] : null, @::jjObjectPresets[powerup]);
			return true;
		}
		return false;
	}
}
RollerWeapon roller;
}
