Downloads containing towerfall.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Anniversary Bash 16 Levels Jazz2Online Multiple N/A Download file
JJ2+ Only: TowerfallFeatured Download Violet CLM Battle 9 Download file

File preview

const int ARROWCOUNT = 3;

const int TRIGNOLIMITS = 10;
const int TRIGARROWTRAILS = 11;
const int TRIGFROZENARROWS = 12;

uint8 CurrentArena = 255;

array<bool> LastTriggerStates(32, false);
array<string> SettingNames = {"Unlimited Arrows", "Glowing Trails", "Ice Arrows"};

void onLevelLoad() {
	jjPalette.gradient(157,93,165, 47,0,60);
	jjPalette.apply();
	jjSetFadeColors();
	jjTexturedBGUsed = true;
	jjTexturedBGStyle = TEXTURE::TUNNEL;
	jjTexturedBGTexture = TEXTURE::XARGON;

	jjTileSet(4, 7, 9, 61 | TILE::VFLIPPED);
	jjTileSet(4, 6, 10, 61 | TILE::VFLIPPED);
	
	jjTileSet(4, 1, 32, 525 | TILE::VFLIPPED);
	jjTileSet(4, 1, 34, 525 | TILE::VFLIPPED);
	jjTileSet(4, 1, 38, 525 | TILE::VFLIPPED);
	jjTileSet(4, 1, 40, 525 | TILE::VFLIPPED);
	jjTileSet(4, 20, 32, 525 | TILE::VFLIPPED);
	jjTileSet(4, 0, 30, 525 | TILE::VFLIPPED);
	jjTileSet(4, 1, 30, 525 | TILE::VFLIPPED);
	jjTileSet(4, 2, 30, 525 | TILE::VFLIPPED);
	jjTileSet(4, 3, 30, 525 | TILE::VFLIPPED);

	jjWeapons[WEAPON::BLASTER].replenishes = false;
	jjWeapons[WEAPON::BLASTER].infinite = false;
	
	jjObjectPresets[OBJECT::SHARD].determineCurAnim(ANIM::PICKUPS, 86);
	jjObjectPresets[OBJECT::FLICKERLIGHT].behavior = smokinghot;

	jjObjectPresets[OBJECT::BLASTERBULLET].behavior = arrow;
	jjObjectPresets[OBJECT::BLASTERBULLET].counterEnd = 45;
	jjObjectPresets[OBJECT::BLASTERBULLET].special = 0; //upAnim
	jjObjectPresets[OBJECT::BLASTERBULLET].determineCurAnim(ANIM::FLAG, 0);
	jjObjectPresets[OBJECT::BLASTERBULLET].killAnim = jjObjectPresets[OBJECT::BOUNCERAMMO3].killAnim;
	jjObjectPresets[OBJECT::BLASTERBULLET].eventID = OBJECT::SMALLTREE; //non-solid
	jjObjectPresets[OBJECT::BLASTERBULLET].var[6] = 16; //fireball-like; doesn't explode upon contact with players
	
	jjObjectPresets[OBJECT::ICEBULLET].behavior = arrow;
	jjObjectPresets[OBJECT::ICEBULLET].counterEnd = 70; //a little longer
	jjObjectPresets[OBJECT::ICEBULLET].special = 0; //upAnim
	jjObjectPresets[OBJECT::ICEBULLET].determineCurAnim(ANIM::FLAG, 0);
	jjObjectPresets[OBJECT::ICEBULLET].killAnim = jjObjectPresets[OBJECT::BOUNCERAMMO3].killAnim;
	jjObjectPresets[OBJECT::ICEBULLET].eventID = OBJECT::SMALLTREE;
	jjObjectPresets[OBJECT::ICEBULLET].var[6] = 16;
	
	jjObjectPresets[OBJECT::REDSPRING].isFreezable = false;
	jjObjectPresets[OBJECT::GREENSPRING].isFreezable = false;
	jjObjectPresets[OBJECT::BLUESPRING].isFreezable = false;
	
	jjMusicLoad("castle.j2b"); //in case you're a client and didn't download the proper music.
	jjMusicLoad("TR2-Train.xm"); //in case you did!
}

void onLevelBegin() {
	if (jjGameConnection == GAME::LOCAL || jjIsServer) {
		jjChat("/allowmouseaim on");
		jjTriggers[jjRandom() & 7] = true;
	}
	if (jjIsServer || jjIsAdmin)
		jjLocalPlayers[0].showText("@@@@@@@Trigger " + TRIGNOLIMITS + ": Players may collect infinite arrows.@Trigger " + TRIGARROWTRAILS + ": Arrows leave trails.@Trigger " + TRIGFROZENARROWS + ": Players may shoot ice arrows.");
	for (int i = 0; i < 32; ++i) {
		jjPlayers[i].score = ARROWCOUNT;
	}
}

void onMain() {
	jjTexturedBGFadePositionX = jjSin(jjGameTicks << 1) / 2 + .5;
	jjTexturedBGFadePositionY = jjCos(jjGameTicks << 1) / 2 + .5;
	
	if (CurrentArena == 255) {
		for (int i = 0; i < 8; ++i)
			if (jjTriggers[i]) {
				CurrentArena = i;
				if (i == 7) jjLayerYOffset[3] = -16; //adjust the spikes a little
				break;
			}
	} else for (int i = 1; i < jjObjectCount; ++i) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.eventID == OBJECT::SMALLTREE && obj.behavior == BEHAVIOR::EXPLOSION2) { //arrow collected by non-local player
			obj.eventID = 0; //already been looked at
			int playerID = obj.findNearestPlayer(75*75);
			if (playerID >= 0 && !jjPlayers[playerID].isLocal)
				++jjPlayers[playerID].score; //try to keep a running tally of everyone's ammo counts
		}
	}
	
	for (int i = 0; i < 32; ++i) {
		jjPLAYER@ play = jjPlayers[i];
		if (play.isActive && play.health > 0) {
			int ammo = play.score;
			play.ammo[WEAPON::BLASTER] = ammo;
			play.ammo[WEAPON::ICE] = (jjTriggers[TRIGFROZENARROWS]) ? ammo : 0;
			float yPos = play.yPos - 25;
			uint8 shift = 0;
			if (jjGameMode == GAME::CTF) {
				if (play.teamRed) shift = 248;
			} else if (!play.isLocal) shift = 248;
			if (jjTriggers[TRIGNOLIMITS]) { //limitless potential storage
				float xPos = play.xPos - (ammo-1)*14/2;
				while (ammo-- > 0) {
					jjDrawSprite(xPos, yPos, ANIM::BRIDGE, 5, 0, 0, SPRITE::PALSHIFT, shift, 1);
					xPos += 14;
				}
			} else {
				float xPos = play.xPos - (ARROWCOUNT-1)*14/2;
				for (int i = 0; i < ARROWCOUNT; ++i) {
					if (ammo-- > 0)
						jjDrawSprite(xPos, yPos, ANIM::BRIDGE, 5, 0, 0, SPRITE::PALSHIFT, shift, 1);
					else
						jjDrawSprite(xPos, yPos, ANIM::BRIDGE, 5, 0, 0, SPRITE::SINGLECOLOR, 0, 1);
					xPos += 14;
				}
			}
			//jjDrawString(play.xPos, play.yPos + 10, "" + play.ammo[WEAPON::BLASTER]);
			//jjDrawString(play.xPos, play.yPos + 20, "" + play.ammo[WEAPON::ICE]);
		}
	}
	
	if (jjIsServer) {
		for (int i = TRIGNOLIMITS; i <= TRIGFROZENARROWS; ++i) {
			if (jjTriggers[i] != LastTriggerStates[i]) {
				LastTriggerStates[i] = jjTriggers[i];
				jjChat("\xA71||||" + SettingNames[i - TRIGNOLIMITS] + " have been |||||" + ((jjTriggers[i]) ? "ENABLED" : "|DISABLED"));
			}
		}
	}
}

array<SOUND::Sample> RicochetNoises = {SOUND::AMMO_BULFL1, SOUND::AMMO_BULFL2, SOUND::AMMO_BULFL3};
void arrow(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		jjPLAYER@ play = jjPlayers[obj.creatorID];
		bool local = play.isLocal;
		
		//do a wee bit of hackery to determine what the firing player's ammo count is
		if (local) --play.score;
		else if (jjTriggers[TRIGFROZENARROWS] && play.ammo[WEAPON::ICE] < play.ammo[WEAPON::BLASTER]) play.score = play.ammo[WEAPON::ICE];
		else if (play.ammo[WEAPON::ICE] == 1 && play.ammo[WEAPON::BLASTER] == 1) play.score = 0; //JJ2+ bug
		else play.score = play.ammo[WEAPON::BLASTER];
		
		if (jjGameMode == GAME::CTF) {
			if (play.teamRed) obj.frameID = 1;
		} else if (!local) obj.frameID = 1;
		obj.determineCurFrame();
		obj.state = STATE::SLEEP;
		if (obj.xSpeed < 0 == obj.var[7] < 0)
			obj.xSpeed = obj.xSpeed + obj.var[7] / 65536.;
		if (!jjIsServer && !local)
			obj.playerHandling = HANDLING::PICKUP; //increase chances of accepting packets telling you that other players picked up this arrow
	} else if (obj.state == STATE::SLEEP) {
		float angle = atan2(obj.xSpeed, obj.ySpeed);
		if (!obj.scriptedCollisions) {
			if (obj.lightType == LIGHT::BRIGHT) { //on fire
				jjPARTICLE@ fire = jjAddParticle(PARTICLE::FIRE);
				if (fire !is null) {
					fire.xPos = obj.xPos;
					fire.yPos = obj.yPos;
					fire.fire.size = jjGameTicks & 3;
				}
				if ((jjGameTicks & 1) == 0)
					jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.objectID)].lightType = LIGHT::POINT2;
			} else if (jjTriggers[TRIGARROWTRAILS] && (jjGameTicks & 1) == 0)
				jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, invisibru);
			obj.xOrg = obj.xOrg + obj.xSpeed;
			obj.yOrg = obj.yOrg + obj.ySpeed;
			if (obj.xOrg < 32) {
				obj.xOrg = obj.xOrg + 640;
				if (CurrentArena == 6) {
					if (obj.yOrg < 3200)
						obj.yOrg = obj.yOrg + 9*32;
					else
						obj.yOrg = obj.yOrg - 9*32;
				}
			} else if (obj.xOrg >= 672) {
				obj.xOrg = obj.xOrg - 640;
				if (CurrentArena == 6) {
					if (obj.yOrg < 3200)
						obj.yOrg = obj.yOrg + 9*32;
					else
						obj.yOrg = obj.yOrg - 9*32;
				}
			}
			else if (obj.yOrg >= jjLayerHeight[4] * 32) obj.yOrg = obj.yOrg - 32*15;
			obj.xPos = obj.xOrg - sin(angle) * 26;
			obj.yPos = obj.yOrg - cos(angle) * 26;
			if (jjMaskedPixel(obj.xOrg, obj.yOrg)) {
				if (jjEventGet(obj.xOrg / 32, obj.yOrg / 32) == AREA::RICOCHET && obj.var[9] < 5) {
					//don't want to use the normal .ricochet() method because it's too random and not y-oriented enough
					jjSample(obj.xPos, obj.yPos, RicochetNoises[jjGameTicks % 3]);
					jjPARTICLE@ spark = jjAddParticle(PARTICLE::SPARK);
					if (spark !is null) {
						spark.xPos = obj.xOrg;
						spark.yPos = obj.yOrg;
						spark.xSpeed = obj.xSpeed / 4;
						spark.ySpeed = obj.ySpeed / 4;
					}
					obj.xSpeed = -obj.xSpeed;
					obj.ySpeed = -obj.ySpeed;
					obj.var[9] = obj.var[9] + 1; //can't ricochet foreverrrr
				} else {
					jjSample(obj.xPos, obj.yPos, SOUND::AMMO_BUL1);
					for (int i = 0; i < 7; ++i) {
						jjPARTICLE@ debris = jjAddParticle(PARTICLE::PIXEL);
						if (debris !is null) {
							debris.xPos = obj.xPos;
							debris.yPos = obj.yPos;
							uint rand = jjRandom();
							debris.xSpeed = (rand & 1023) / 256. - 2;
							debris.ySpeed = ((rand >> 10) & 1023) / 256. - 2;
							debris.pixel.size = 2; //3x3
							for (int j = 0; j < 9; ++j)
								debris.pixel.color[j] = (jjRandom() & 15) + 96;
						}
					}
					obj.var[3] = obj.var[2];
					obj.playerHandling = HANDLING::PICKUP;
					obj.scriptedCollisions = true;
				}
			}
			else {
				if (obj.light != 12 && jjEventGet(obj.xOrg / 32, obj.yOrg / 32) == OBJECT::FLICKERLIGHT) { //torch
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
					obj.freeze = 0; //duh
					obj.light = 12;
					if (jjMaxHealth > 1) { //not instagib
						obj.var[6] = obj.var[6] | 8; //powerup
						obj.lightType = LIGHT::BRIGHT;
					}
				}
				obj.ySpeed = obj.ySpeed + .15;
				if (obj.ySpeed > 20) obj.ySpeed = 20;
				obj.var[2] = (angle - 2) * 1024 * 40; //#codethatjustworks
			}
		} else {
			if (obj.counterEnd > 0) {
				if (--obj.counterEnd == 0 && obj.freeze == 0) { obj.lightType = LIGHT::NONE; obj.light = 0; }
				else {
					obj.var[2] = obj.var[3] + jjSin((jjGameTicks + obj.objectID) << 6) * obj.counterEnd * 512;
					float angle = (obj.var[2] + 81920) / (40. * 1024.); //#codethatjustworksinreverse
					obj.xPos = obj.xOrg - sin(angle) * 26;
					obj.yPos = obj.yOrg - cos(angle) * 26;
				}
			} else
				obj.yPos = obj.yOrg - cos(angle) * 26 + jjSin(((obj.objectID * 8 + jjGameTicks) + int(obj.xOrg) + int(obj.yOrg) * 256)*16) * 4;
		}
		obj.behave(BEHAVIOR::POLE); //Draw curFrame rotated at an angle based on var[2]. Because state is STILL and eventID is SMALLTREE, this behavior does absolutely nothing besides draw the sprite.
		if (obj.lightType == LIGHT::BRIGHT)
			jjDrawSprite(obj.xPos, obj.yPos, ANIM::AMMO, /*obj.frameID * */ 13, jjGameTicks % 7);
		//jjDrawSprite(obj.xPos, obj.yPos, ANIM::AMMO, 2, 0);
		//jjDrawSprite(obj.xOrg, obj.yOrg, ANIM::AMMO, 2, 0, 0, SPRITE::PALSHIFT, 8);
	}
}
void invisibru(jjOBJ@ obj) {
	if (obj.state == STATE::START) {
		obj.counterEnd = 0;
		obj.lightType = LIGHT::POINT;
		obj.light = 1;
		obj.state = STATE::EXPLODE;
	} else if (obj.counterEnd++ >= 20) obj.delete();
}
void smokinghot(jjOBJ@ obj) {
	uint rand = jjRandom();
	if ((rand & 3) == 0) {
		rand >>= 2;
		jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
		if (smoke !is null) {
			smoke.xPos = obj.xOrg + (rand & 31) - 16;
			rand >>= 5;
			smoke.yPos = obj.yOrg + (rand & 31) - 16;
		}
	}
}
void onObjectHit(jjOBJ@ object, jjOBJ@ bullet, jjPLAYER@ player, int force) {
	if (bullet is null && object.eventID == OBJECT::SMALLTREE) {
		int ammo = player.score;
		if (!player.isLocal || (object.counterEnd == 0 && (ammo < ARROWCOUNT || jjTriggers[TRIGNOLIMITS]) && player.health > 0)) {
			object.state = STATE::EXPLODE; //this one may not actually be needed
			object.scriptedCollisions = false;
			object.behavior = BEHAVIOR::EXPLOSION2;
			object.eventID = 0;
			++player.score;
			if (object.freeze > 0) {
				player.frozen = object.freeze;
				object.unfreeze(0);
			} else
				jjSample(object.xPos, object.yPos, SOUND::COMMON_PICKUPW1);
		}
	}
}

bool onDrawScore(jjPLAYER@ play, jjCANVAS@ screen) { return true; }
bool onDrawAmmo(jjPLAYER@ play, jjCANVAS@ screen) { return !jjTriggers[TRIGFROZENARROWS]; }

void onPlayer(jjPLAYER@ play) {
	if (play.yPos < 16 && CurrentArena != 255) {
		//play.warpToID(CurrentArena, true);
		jjEventSet(0,0, AREA::WARP); //by waiting until the player can process a warp event, we avoid bizarre level-initial deaths
		jjParameterSet(0,0, 0,8, CurrentArena);
		//if (jjGameConnection != GAME::LOCAL) //local camera freezing results in shaking for some reason, but fixed by making level bigger
		play.cameraFreeze(play.subscreenX+32, play.subscreenY + CurrentArena*480, false, true);
	}
	else if (play.xPos < 32) {
		play.xPos = play.xPos + 640;
		if (CurrentArena == 6) {
			if (play.yPos < 3200)
				play.yPos = play.yPos + 9*32;
			else
				play.yPos = play.yPos - 9*32;
		}
	} else if (play.xPos >= 672) {
		play.xPos = play.xPos - 640;
		if (CurrentArena == 6) {
			if (play.yPos < 3200)
				play.yPos = play.yPos + 9*32;
			else
				play.yPos = play.yPos - 9*32;
		}
	}
	if (!(int(play.yPos / 32) == 84 && play.ySpeed == 0)) //not on vine
		play.keyDown = false;
	if (play.score == 0 || play.yPos < 16 || play.frozen > 0) { play.keyFire = false; play.currWeapon = WEAPON::BLASTER; }
}