Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Anniversary Bash 20 Levels | Jazz2Online | Multiple | N/A |
#pragma require "SEweapon.asc"
#pragma require "SEminimirv.j2a"
#pragma offer "SEminimirv1.wav"
#pragma offer "SEminimirv2.wav"
#include "SEweapon.asc"
namespace se {
namespace detail {
shared class MiniMirvData {
private int m_objectID, m_warheadCount;
private ::array<float> m_warheadXSpeed, m_warheadYSpeed;
MiniMirvData(int id) {
m_objectID = id;
m_warheadCount = 0;
}
bool opEquals(const MiniMirvData@ other) const {
return m_objectID == other.m_objectID;
}
bool isEmpty() const {
return m_warheadCount == 0;
}
int get_objectID() const {
return m_objectID;
}
int get_warheadCount() const {
return m_warheadCount;
}
float get_xSpeed(int index) const {
return m_warheadXSpeed[index];
}
float get_ySpeed(int index) const {
return m_warheadYSpeed[index];
}
void clear() {
m_warheadCount = 0;
m_warheadXSpeed.resize(0);
m_warheadYSpeed.resize(0);
}
void addWarhead(float xSpeed, float ySpeed) {
m_warheadCount++;
m_warheadXSpeed.insertLast(xSpeed);
m_warheadYSpeed.insertLast(ySpeed);
}
}
shared class MiniMirvPolynomial {
private ::array<double> m_coefficients;
MiniMirvPolynomial() {}
MiniMirvPolynomial(const ::array<double> &in coefficients) {
m_coefficients = coefficients;
}
double opCall(double x) const {
double y = 0.0;
for (int i = m_coefficients.length(); i-- != 0;) {
y = y * x + m_coefficients[i];
}
return y;
}
}
}
shared class MiniMirvWeapon : WeaponInterface {
private ::jjANIMSET@ m_animSet;
private ::array<SOUND::Sample> m_samples = {SOUND::AMMO_MISSILE, SOUND::AMMO_BOEM1};
private PacketConstructor@ m_packetConstructor;
private ::array<se::detail::MiniMirvData@> m_data;
protected ::jjANIMSET@ getAnimSet() const {
return @m_animSet;
}
protected const array<SOUND::Sample>& getSamples() const {
return m_samples;
}
protected PacketConstructor@ getPacketConstructor() const {
return @m_packetConstructor;
}
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, int index) {
if (::jjSampleLoad(sample, filename)) {
m_samples[index] = sample;
return true;
}
return false;
}
protected int getPlayerTeam(const ::jjPLAYER@ player) const {
if (::jjGameMode == GAME::SP || ::jjGameMode == GAME::COOP)
return 0;
if (::jjGameMode == GAME::CTF)
return player.team;
if (::jjGameCustom == GAME::PEST)
return player.isZombie ? 1 : 0;
if (::jjGameCustom == GAME::RT) {
if (player is ::jjTokenOwner)
return 1;
if (player is ::jjBottomFeeder)
return 2;
return 0;
}
return player.playerID;
}
protected void behaveExplosion(::jjOBJ@ obj) const {
switch (obj.state) {
case STATE::START:
{
float xAvg = 0.f, yAvg = 0.f;
for (int i = 0; i < 1024; i += 16) {
float cosine = ::jjCos(i);
float sine = ::jjSin(i);
float x = obj.xPos + cosine * 10.f;
float y = obj.yPos + sine * 10.f;
if (::jjMaskedPixel(int(x + 0.5f), int(y + 0.5f))) {
xAvg += cosine;
yAvg += sine;
}
}
obj.special = int(::atan2(xAvg, yAvg) * 162.975f);
}
::jjSample(obj.xPos, obj.yPos, m_samples[1]);
obj.state = STATE::EXPLODE;
break;
case STATE::EXPLODE:
obj.counter++;
if (obj.counter & 3 == 0) {
obj.frameID++;
if (obj.counter == 4 && (::jjIsServer || ::jjGameConnection == GAME::LOCAL)) {
const int maxPlayers = 32;
for (int i = 0; i < maxPlayers; i++) {
::jjPLAYER@ player = @::jjPlayers[i];
if (player.isInGame && !player.isJailed && obj.var[1] != getPlayerTeam(player)) {
float x = player.xPos - obj.xPos;
float y = player.yPos - obj.yPos;
if (::abs(::jjCos(obj.special) * x + ::jjSin(obj.special) * y) < 280.f && x * x + y * y < 589824.f)
player.hurt(5, true, obj.creatorType == CREATOR::PLAYER ? @::jjPlayers[obj.creatorID] : null);
}
}
}
}
if (obj.frameID < int(::jjAnimations[obj.curAnim].frameCount)) {
obj.determineCurFrame();
::jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.special, 4.f, 4.f, SPRITE::TRANSLUCENTTILE);
} else {
obj.state = STATE::KILL;
}
break;
case STATE::KILL:
case STATE::DEACTIVATE:
obj.delete();
break;
}
}
protected void explode(::jjOBJ@ obj) const {
obj.state = STATE::KILL;
int id = ::jjAddObject(obj.eventID, obj.xPos, obj.yPos, obj.creatorID, obj.creatorType, @::jjVOIDFUNCOBJ(behaveExplosion));
if (id != 0) {
::jjOBJ@ explosion = @::jjObjects[id];
explosion.animSpeed = obj.animSpeed;
explosion.curAnim = obj.killAnim;
explosion.curFrame = ::jjAnimations[explosion.curAnim];
explosion.playerHandling = HANDLING::EXPLOSION;
explosion.var[1] = obj.var[1];
}
}
protected void draw(::jjOBJ@ obj) const {
if (::jjGameTicks & 7 == 0) {
obj.frameID++;
if (obj.frameID >= int(::jjAnimations[obj.curAnim].frameCount))
obj.frameID = 0;
}
obj.determineCurFrame();
float dir = obj.xSpeed < 0.f ? -1.f : 1.f;
int angle = int(::atan2(-obj.ySpeed, obj.xSpeed) * 162.975f);
if (dir < 0.f)
angle += 512;
::jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, angle, dir);
}
protected void behaveWarhead(::jjOBJ@ obj) const {
switch (obj.state) {
case STATE::START:
obj.state = STATE::FALL;
break;
case STATE::FALL:
obj.xSpeed += obj.xAcc;
obj.ySpeed += obj.yAcc;
{
float width = ::jjLayerWidth[4] << 5, height = ::jjLayerHeight[4] << 5;
float xSpeed = ::abs(obj.xSpeed), ySpeed = ::abs(obj.ySpeed);
float maxSpeed = xSpeed > ySpeed ? xSpeed : ySpeed;
float steps = ::ceil(maxSpeed / 8.f);
float xStep = obj.xSpeed / steps, yStep = obj.ySpeed / steps;
for (int i = int(steps + 0.5f); i-- != 0;) {
obj.xPos += xStep;
obj.yPos += yStep;
if (obj.yPos >= height || obj.xPos >= 0.f && obj.yPos >= 0.f && obj.xPos < width && ::jjMaskedPixel(int(obj.xPos), int(obj.yPos))) {
obj.state = STATE::EXPLODE;
break;
}
}
}
draw(obj);
break;
case STATE::EXPLODE:
explode(obj);
break;
case STATE::KILL:
case STATE::DEACTIVATE:
obj.delete();
break;
}
}
protected float findReasonableArrivalTime(float xDist, float yDist, float xSpeed, float ySpeed, float yAcc) const {
::array<double> coefficients = {
xDist * xDist + yDist * yDist,
-2.f * (xSpeed * xDist + ySpeed * yDist),
-3.f * (xSpeed * xSpeed + ySpeed * ySpeed) - yDist * yAcc,
ySpeed * yAcc,
0.25f * yAcc * yAcc
};
se::detail::MiniMirvPolynomial f(coefficients);
for (int i = 1; i < 5; i++) {
coefficients[i] *= i;
}
coefficients.removeAt(0);
se::detail::MiniMirvPolynomial df(coefficients);
double left = 0.0, right = 20.0;
double fl = f(left), fr = f(right);
while (fl * fr >= 0.0) {
left = right;
fl = fr;
right += 20.0;
if (right > 420.0)
return -1.f;
fr = f(right);
}
double dfl = df(left), dfr = df(right);
if (dfl * dfr > 0.0) {
double x = (dfr - dfl) * dfl < 0.0 ? left : right;
double fx = f(x);
bool ok = true;
for (int i = 40; !::closeTo(fx, 0.0, 1e-7); i--) {
x -= fx / df(x);
if (i == 0 || x < left || x > right) {
ok = false;
break;
}
fx = f(x);
}
if (ok)
return float(x);
}
double x = (left + right) / 2.0;
double fx = f(x);
for (int i = 40; !::closeTo(fx, 0.0, 1e-7); i--) {
if (fx * fl < 0.0) {
right = x;
fr = f(right);
} else {
left = x;
fl = f(left);
}
if (i == 0)
break;
fx = f(x);
}
return float(x);
}
protected void predictPlayer(const ::jjPLAYER@ player, int time, float xAcc, float &out x, float &out y) const {
x = player.xPos;
y = player.yPos;
float xSpeed = player.xSpeed, ySpeed = player.ySpeed;
for (int i = 0; i < time; i++) {
if (ySpeed < 0.f) {
ySpeed += 0.25f;
} else {
ySpeed += 0.125f;
if (ySpeed > 12.f)
ySpeed = 12.f;
}
float effectiveYSpeed = ySpeed;
if (effectiveYSpeed < -8.f)
effectiveYSpeed = -8.f;
y += effectiveYSpeed;
if (ySpeed > 0.f) {
if (::jjMaskedHLine(int(x) - 12, 24, int(y) + 20)) {
while (::jjMaskedHLine(int(x) - 12, 24, int(y) + 19)) {
y--;
}
ySpeed = 0.f;
}
} else {
if (::jjMaskedHLine(int(x) - 12, 24, int(y) - 4)) {
while (::jjMaskedHLine(int(x) - 12, 24, int(y) - 3)) {
y++;
}
ySpeed = 0.f;
}
}
xSpeed += xAcc;
if (xSpeed > 16.f)
xSpeed = 16.f;
else if (xSpeed < -16.f)
xSpeed = -16.f;
float effectiveXSpeed = xSpeed;
if (effectiveXSpeed > 8.f)
effectiveXSpeed = 8.f;
else if (effectiveXSpeed < -8.f)
effectiveXSpeed = -8.f;
x += effectiveXSpeed;
if (xSpeed > 0.f) {
int mask = 25 - ::jjMaskedTopVLine(int(x) + 12, int(y) - 4, 24);
if (mask != 0) {
if (mask - 1 < xSpeed) {
y -= mask;
ySpeed = 0.f;
} else {
xSpeed = 0.f;
}
}
} else {
int mask = 25 - ::jjMaskedTopVLine(int(x) - 12, int(y) - 4, 24);
if (mask != 0) {
if (mask - 1 < -xSpeed) {
y -= mask;
ySpeed = 0.f;
} else {
xSpeed = 0.f;
}
}
}
}
}
protected void selectTargets(const ::jjOBJ@ obj, se::detail::MiniMirvData@ data) const {
if (::jjIsServer || ::jjGameConnection == GAME::LOCAL) {
const int minWarheads = 3, maxWarheads = 5;
const int maxPlayers = 32;
float timeLeft = obj.state == STATE::ACTION ? 0.f : obj.counterEnd - obj.counter + 1;
float xReal = obj.xPos + timeLeft * obj.xSpeed;
float yReal = obj.yPos + timeLeft * obj.ySpeed - timeLeft * timeLeft * 0.0125f;
float xSpeedReal = obj.xSpeed;
float ySpeedReal = obj.ySpeed + timeLeft * obj.yAcc;
float speed = ::sqrt(xSpeedReal * xSpeedReal + ySpeedReal * ySpeedReal) * 2.f;
{
::array<float> interest, times, targetsX, targetsY;
int targetCount = 0;
for (int i = 0; i < maxPlayers; i++) {
const ::jjPLAYER@ target = @::jjPlayers[i];
if (target.isInGame && !target.isJailed && obj.var[1] != getPlayerTeam(target)) {
float thisInterest = 1.f;
if (target.flag != 0 && ::jjObjects[target.flag].var[0] == target.playerID + 0x8000 || ::jjGameCustom == GAME::RT && target is ::jjTokenOwner)
thisInterest = 5.f;
if (::jjGameMode == GAME::TREASURE || ::jjGameCustom == GAME::HEAD)
thisInterest += target.gems[GEM::RED];
if (target.lrsLives >= 0)
thisInterest += ::sqrt(float(target.lrsLives));
else if (target.deaths >= 0)
thisInterest += 1.f / (target.deaths + 1);
bool inMovement = !::closeTo(target.xSpeed, 0.f, 0.01f);
float xAcc = inMovement ? 375.f / 1024.f : 0.f;
for (int j = inMovement ? 2 : 1; j-- != 0; xAcc = -xAcc) {
float xPos, yPos;
float time = timeLeft, prevTime = -128.f;
for (int k = 20; k != 0 && time >= 0.f && !::closeTo(time, prevTime, 1.f); k--) {
predictPlayer(@target, int(time), xAcc, xPos, yPos);
yPos += 20.f;
prevTime = time;
time = findReasonableArrivalTime(xPos - xReal, yPos - yReal, xSpeedReal, ySpeedReal, obj.yAcc);
}
if (time > 0.f) {
interest.insertLast(thisInterest / time);
times.insertLast(time);
targetsX.insertLast(xPos);
targetsY.insertLast(yPos);
targetCount++;
}
}
}
}
while (targetCount > 0 && data.warheadCount < maxWarheads) {
float maxInterest = interest[0];
int targetID = 0;
for (int i = 1; i < targetCount; i++) {
if (interest[i] > maxInterest)
targetID = i;
}
float xPos = targetsX[targetID];
float yPos = targetsY[targetID];
float xSpeed = (xPos - xReal) / times[targetID];
float ySpeed = (yPos - yReal) / times[targetID] - obj.yAcc * times[targetID] / 2.f;
data.addWarhead(xSpeed, ySpeed);
interest.removeAt(targetID);
times.removeAt(targetID);
targetsX.removeAt(targetID);
targetsY.removeAt(targetID);
targetCount--;
}
}
if (data.warheadCount < maxWarheads) {
::array<float> interest, times;
::array<const ::jjOBJ@> potentialTargets;
int targetCount = 0;
for (int i = 0; i < ::jjObjectCount; i++) {
const ::jjOBJ@ target = @::jjObjects[i];
if (target.isActive && target.isTarget) {
float time = findReasonableArrivalTime(target.xPos - xReal, target.yPos - yReal, xSpeedReal, ySpeedReal, obj.yAcc);
if (time > 0.f) {
interest.insertLast((target.points + 100) / time);
times.insertLast(time);
potentialTargets.insertLast(@target);
targetCount++;
}
}
}
while (targetCount > 0 && data.warheadCount < maxWarheads) {
float maxInterest = interest[0];
int targetID = 0;
for (int i = 1; i < targetCount; i++) {
if (interest[i] > maxInterest)
targetID = i;
}
const ::jjOBJ@ target = potentialTargets[targetID];
float xSpeed = (target.xPos - xReal) / times[targetID];
float ySpeed = (target.yPos - yReal) / times[targetID] - obj.yAcc * times[targetID] / 2.f;
data.addWarhead(xSpeed, ySpeed);
interest.removeAt(targetID);
times.removeAt(targetID);
potentialTargets.removeAt(targetID);
targetCount--;
}
}
while (data.warheadCount < minWarheads) {
float angle = ::jjRandom() * 6.28318531f / 0x100000000;
data.addWarhead(xSpeedReal + ::sin(angle) * speed, ySpeedReal + ::cos(angle) * speed);
}
if (::jjIsServer) {
::jjSTREAM packet = m_packetConstructor();
packet.push(uint8(obj.creatorID));
packet.push(uint8(data.warheadCount));
for (int i = 0; i < data.warheadCount; i++) {
packet.push(float(data.xSpeed[i]));
packet.push(float(data.ySpeed[i]));
}
::jjSendPacket(packet);
}
}
}
protected void detach(::jjOBJ@ obj, const se::detail::MiniMirvData@ data) const {
obj.state = STATE::KILL;
int damage = data.warheadCount == 0 ? 0 : obj.animSpeed / data.warheadCount;
for (int i = 0; i < data.warheadCount; i++) {
int id = ::jjAddObject(obj.eventID, obj.xPos, obj.yPos, obj.creatorID, obj.creatorType, @::jjVOIDFUNCOBJ(behaveWarhead));
if (id != 0) {
::jjOBJ@ warhead = @::jjObjects[id];
warhead.animSpeed = damage;
warhead.curAnim = warhead.special = obj.curAnim + 1;
warhead.curFrame = ::jjAnimations[warhead.curAnim];
warhead.killAnim = obj.killAnim;
warhead.var[1] = obj.var[1];
warhead.xAcc = 0.f;
warhead.yAcc = obj.yAcc;
warhead.xSpeed = data.xSpeed[i];
warhead.ySpeed = data.ySpeed[i];
}
}
}
protected void behave(::jjOBJ@ obj) {
se::detail::MiniMirvData tempData(obj.objectID);
int dataID = m_data.find(@tempData);
if (dataID == -1) {
dataID = m_data.length();
m_data.insertLast(@tempData);
}
switch (obj.state) {
case STATE::START:
m_data[dataID].clear();
if (obj.creatorType == CREATOR::PLAYER) {
obj.var[1] = getPlayerTeam(@::jjPlayers[obj.creatorID]);
if (::jjGameMode == GAME::CTF)
obj.curAnim += (obj.var[1] + 1) << 1;
if (::jjPlayers[obj.creatorID].isLocal)
::jjSample(obj.xPos, obj.yPos, m_samples[0]);
} else {
obj.var[1] = -1;
}
obj.xAcc = 0.f;
obj.yAcc = ::jjObjectPresets[obj.eventID].yAcc;
obj.state = STATE::ROCKETFLY;
case STATE::ROCKETFLY:
obj.xPos += obj.xSpeed;
obj.yPos += obj.ySpeed -= 0.025f;
obj.counter++;
if (::jjMaskedPixel(int(obj.xPos), int(obj.yPos)))
obj.state = STATE::EXPLODE;
else if (obj.counter > int(obj.counterEnd))
obj.state = STATE::ACTION;
else if (obj.counter > int(obj.counterEnd) - 35 && m_data[dataID].isEmpty())
selectTargets(obj, @m_data[dataID]);
draw(obj);
break;
case STATE::ACTION:
if (m_data[dataID].isEmpty())
selectTargets(obj, @m_data[dataID]);
else
detach(obj, @m_data[dataID]);
obj.counter--;
if (obj.counter < 0)
obj.state = STATE::KILL;
draw(obj);
break;
case STATE::EXPLODE:
explode(obj);
break;
case STATE::KILL:
case STATE::DEACTIVATE:
m_data.removeAt(dataID);
obj.delete();
break;
}
}
protected void prepareWeaponProfile(::jjWEAPON@ weapon) const {
weapon.comesFromGunCrates = false;
weapon.defaultSample = false;
weapon.gradualAim = false;
weapon.maximum = 1;
weapon.multiplier = 1;
weapon.replacedByBubbles = false;
weapon.spread = SPREAD::NORMAL;
weapon.style = WEAPON::MISSILE;
}
protected void prepareBulletPreset(::jjOBJ@ preset, uint number) const {
preset.behavior = @::jjVOIDFUNCOBJ(behave);
preset.animSpeed = 50;
preset.counterEnd = 140;
preset.curAnim = preset.special = m_animSet + 2;
preset.curFrame = ::jjAnimations[preset.curAnim];
preset.deactivates = false;
preset.direction = 1;
preset.energy = preset.freeze = 0;
preset.frameID = 0;
preset.killAnim = m_animSet + 1;
preset.lightType = LIGHT::POINT;
preset.playerHandling = HANDLING::PARTICLE;
preset.var[3] = number;
preset.var[6] = 24;
preset.xAcc = 0.f;
preset.yAcc = 0.125f;
preset.xSpeed = 11.f;
preset.ySpeed = 0.f;
}
protected void preparePickupPreset(::jjOBJ@ preset, uint number) const {
preset.behavior = @AmmoPickup(::jjAnimations[m_animSet], ::jjAnimations[m_animSet], 1);
preset.curAnim = m_animSet;
preset.direction = 1;
preset.energy = 0;
preset.frameID = 0;
preset.killAnim = ::jjAnimSets[ANIM::PICKUPS] + 86;
preset.playerHandling = HANDLING::PICKUP;
preset.points = 1000;
preset.var[2] = 0;
preset.var[3] = number - 1;
preset.determineCurFrame();
}
protected void processPacket(::jjSTREAM& packet, int) const {
if (!::jjIsServer) {
uint8 creatorID, warheadCount;
if (packet.pop(creatorID) && packet.pop(warheadCount)) {
int dataID = -1, maxCounter = -1;
for (int i = m_data.length(); i-- != 0;) {
const ::jjOBJ@ obj = @::jjObjects[m_data[i].objectID];
if (m_data[i].isEmpty() && obj.isActive && obj.creatorID == creatorID && obj.counter > maxCounter) {
maxCounter = obj.counter;
dataID = i;
}
}
if (dataID != -1) {
while (warheadCount-- != 0) {
float xSpeed, ySpeed;
if (packet.pop(xSpeed) && packet.pop(ySpeed))
m_data[dataID].addWarhead(xSpeed, ySpeed);
}
}
}
}
}
::jjANIMSET@ loadAnims(::jjANIMSET@ animSet) override {
loadAnimSet(animSet, "SEminimirv.j2a", 0);
return @animSet;
}
::array<bool>@ loadSamples(const ::array<SOUND::Sample>& samples) override {
if (samples.length() != 2)
return @::array<bool>(2, false);
::array<bool> result = {loadSample(samples[0], "SEminimirv1.wav", 0), loadSample(samples[1], "SEminimirv2.wav", 1)};
return @result;
}
uint getSampleCount() const override {
return 1;
}
uint getTraits(bool) const override {
return weapon_deals_damage | weapon_causes_splash_damage | weapon_is_super_weapon | weapon_is_effective_against_all_targets | weapon_works_in_all_modes | weapon_has_ammo_pickups;
}
uint getMaxDamage(bool) const override {
return 5;
}
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]);
@m_packetConstructor = @weaponHook.addPacketCallback(@PacketCallback(processPacket));
}
prepareWeaponProfile(@::jjWeapons[number]);
prepareBulletPreset(@::jjObjectPresets[basic], number);
if (basic != powered)
prepareBulletPreset(@::jjObjectPresets[powered], number);
if (ammo3 != 0)
preparePickupPreset(@::jjObjectPresets[ammo3], number);
if (ammo15 != 0)
preparePickupPreset(@::jjObjectPresets[ammo15], number);
preparePickupPreset(@::jjObjectPresets[powerup], number);
return true;
}
return false;
}
}
MiniMirvWeapon miniMirv;
}
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.