Downloads containing academy_drawing.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Anniversary Bash 21 Levels Jazz2Online Multiple N/A Download file

File preview

namespace acDrawing {

	void drawAreaOfEffect(jjPLAYER@ play, float radius) {
		Player@ asPlayer = players[play.playerID];
		Spell@ selectedSpell = cast<Spell@>(spells[asPlayer.selectedSpellKey]);
		float radiusInTiles = radius * TILE;
		jjANIMFRAME@ sprite = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::FLARE].firstAnim].firstFrame + 5];
		float scaledRadius = float(sprite.width * radius / 2);
		jjTEXTAPPEARANCE centeredText();
		centeredText.align = STRING::CENTER;
		centeredText.pipe = STRING::SPECIALSIGN;
		jjDrawResizedSprite(play.xPos, play.yPos, ANIM::FLARE, 0, 5, radius, radius, SPRITE::TRANSLUCENTCOLOR, AREA_OF_EFFECT_COLOR, 1);
		jjDrawString(play.xPos, play.yPos-64, "||Channeling " + selectedSpell.name, STRING::SMALL, centeredText, 0, SPRITE::PALSHIFT,
				0, 1);
		int8[] targets = acUtils::getTargets(play, selectedSpell, scaledRadius);
		for (uint i = 0; i < targets.length; i++) {
			jjDrawResizedSprite(jjPlayers[targets[i]].xPos, jjPlayers[targets[i]].yPos, ANIM::VINE, 0, 0, 2, 3, SPRITE::TRANSLUCENTCOLOR, AREA_OF_EFFECT_TARGET_COLOR, 1);
		}
	}
	
	void drawBulletPointer(jjPLAYER@ play, Player@ asPlayer) {
		if (asPlayer.selectedSpellKey != "") {
			Spell@ spell = cast<Spell@>(spells[asPlayer.selectedSpellKey]);
			switch (spell.enumValue) {
				case SPELL_MAGIC_ARROW:
				{
					int angle = play.direction >= 0 ? 950 : 425;
					int distance = BULLET_POINTER_MAX_DISTANCE*TILE;
					drawBulletPointerTrail(play, distance, play.direction);
					distance = play.direction >= 0 ? distance : -distance;
					jjDrawRotatedSprite(play.xPos+distance, play.yPos, ANIM::FLAG, 0, 0, angle, 1.5, 1.5, SPRITE::TRANSLUCENTCOLOR, AREA_OF_EFFECT_COLOR, 1);
				}
				break;
				case SPELL_ICE_BOLT:
				{
					int angle = play.direction >= 0 ? 0 : 540;
					int distance = BULLET_POINTER_MAX_DISTANCE*TILE;
					drawBulletPointerTrail(play, distance, play.direction);
					distance = play.direction >= 0 ? distance : -distance;
					jjDrawRotatedSprite(play.xPos+distance, play.yPos, ANIM::AMMO, 11, 0, angle, 1.5, 1.5, SPRITE::TRANSLUCENTCOLOR, AREA_OF_EFFECT_COLOR, 1);
				}
				break;
				case SPELL_FIREBALL:
				{
					int angle = play.direction >= 0 ? 0 : 540;
					int distance = acUtils::getBulletPointerDistance(play.xPos, play.yPos, play.direction);
					drawBulletPointerTrail(play, distance, play.direction);
					distance = play.direction >= 0 ? distance : -distance;
					jjDrawRotatedSprite(play.xPos+distance, play.yPos, ANIM::AMMO, 14, 0, angle, 1.5, 1.5, SPRITE::TRANSLUCENTCOLOR, AREA_OF_EFFECT_COLOR, 1);
				}
				break;
			}
		}
	}

	void drawBulletPointerTrail(jjPLAYER@ play, int distance, int direction) {
		if (direction >= 0) {
			for (float x = play.xPos; x < play.xPos+distance; x += 16) {
				jjDrawRectangle(x-1, play.yPos-1, 3, 3, AREA_OF_EFFECT_COLOR, SPRITE::TRANSLUCENT, 1);
			}
		} else {
			for (float x = play.xPos; x > play.xPos-distance; x -= 16) {
				jjDrawRectangle(x-1, play.yPos-1, 3, 3, AREA_OF_EFFECT_COLOR, SPRITE::TRANSLUCENT, 1);
			}
		}
	}
	
	// If a player is already "chained" from one target, it cannot be chained from another anymore,
	// thus additional targets in the same group may appear "loose" from the chain
	void drawChain(ChainLightningTarget@ target, ChainLightningTarget@ parentTarget) {
		jjPLAYER@ targetPlayer = jjPlayers[target.playerID];
		jjPLAYER@ parentTargetPlayer = jjPlayers[parentTarget.playerID];
		float xStart = targetPlayer.xPos <= parentTargetPlayer.xPos ? targetPlayer.xPos : parentTargetPlayer.xPos;
		float xEnd = targetPlayer.xPos <= parentTargetPlayer.xPos ? parentTargetPlayer.xPos : targetPlayer.xPos;
		float yStart = targetPlayer.yPos <= parentTargetPlayer.yPos ? targetPlayer.yPos : parentTargetPlayer.yPos;
		float yEnd = targetPlayer.yPos <= parentTargetPlayer.yPos ? parentTargetPlayer.yPos : targetPlayer.yPos;
		float xDist = abs(xEnd - xStart);
		float yDist = abs(yEnd - yStart);
		if (xDist >= yDist) {
			uint yDim = 0;
			for (float x = xStart; x < xEnd; x += 8) {
				float percentage = (x - xStart) / xDist;
				yDim = uint(percentage * 255);
				float yDiff = 0;
				if ((targetPlayer.xPos <= parentTargetPlayer.xPos && targetPlayer.yPos <= parentTargetPlayer.yPos) ||
						(targetPlayer.xPos > parentTargetPlayer.xPos && targetPlayer.yPos > parentTargetPlayer.yPos)) {
					yDiff = jjSin(yDim) * yDist;
				} else {
					yDiff = jjCos(yDim) * yDist;
				}
				jjDrawRotatedSprite(x, yStart + yDiff, ANIM::AMMO, 4, 0, jjRandom() % 1024, 1, 1, SPRITE::NORMAL, 0, 1);
			}
		}
		else {
			uint xDim = 0;
			for (float y = yStart; y < yEnd; y += 8) {
				float percentage = (y - yStart) / yDist;
				xDim = uint(percentage * 255);
				float xDiff = 0;
				if ((targetPlayer.xPos <= parentTargetPlayer.xPos && targetPlayer.yPos <= parentTargetPlayer.yPos) ||
						(targetPlayer.xPos > parentTargetPlayer.xPos && targetPlayer.yPos > parentTargetPlayer.yPos)) {
					xDiff = jjSin(xDim) * xDist;
				} else {
					xDiff = jjCos(xDim) * xDist;
				}
				jjDrawRotatedSprite(xStart + xDiff, y, ANIM::AMMO, 4, 0, jjRandom() % 1024, 1, 1, SPRITE::NORMAL, 0, 1);
			}
		}
	}
	
	void drawChainLightnings() {
		int newIndexGroups = 0;
		int chainLightningTargetGroupsLength = chainLightningTargetGroups.length();
		for (int i = 0; i < chainLightningTargetGroupsLength; i++) {
			array<ChainLightningTarget@> chainLightningTargets = chainLightningTargetGroups[i];
			int newIndexTargets = 0;
			int chainLightningTargetsLength = chainLightningTargets.length();
			for (int j = 0; j < chainLightningTargetsLength; j++) {
				ChainLightningTarget@ target = chainLightningTargets[j];
				if (target.elapsed > 0) {
					jjPLAYER@ play = jjPlayers[target.playerID];
					jjDrawRotatedSprite(play.xPos, play.yPos, ANIM::AMMO, 4, 0, jjRandom() % 1024,
							CHAIN_LIGHTNING_TARGET_SCALE, CHAIN_LIGHTNING_TARGET_SCALE, SPRITE::NORMAL, 0, 1);
					if (target.parentID >= 0) {
						ChainLightningTarget@ parentTarget = acUtils::findParentTarget(
								chainLightningTargets, target.parentID);
						drawChain(target, parentTarget);
					}
					target.elapsed--;
					@chainLightningTargets[newIndexTargets] = target;
					newIndexTargets++;
				}
			}
			chainLightningTargets.removeRange(newIndexTargets, chainLightningTargetsLength - newIndexTargets);
			if (chainLightningTargetsLength > 0) {
				chainLightningTargetGroups[newIndexGroups] = chainLightningTargets;
				newIndexGroups++;
			}
		}
		chainLightningTargetGroups.removeRange(newIndexGroups, chainLightningTargetGroupsLength - newIndexGroups);
	}

	void drawChainLightningTargets(jjPLAYER@ play) {
		Spell@ spell = cast<Spell@>(spells["L"]);
		float scaledRadius = acUtils::getScaledRadius(spell.radius, ANIM::FLARE, 5);
		int8[] targets = acUtils::getTargets(play, spell, scaledRadius);
		for (uint i = 0; i < targets.length; i++) {
			int8 targetPlayerID = targets[i];
			int8[] newTargets = acUtils::getTargets(jjPlayers[targetPlayerID], spell, scaledRadius);
			for (uint j = 0; j < newTargets.length; j++) {
				if (targets.find(newTargets[j]) < 0) {
					targets.insertLast(newTargets[j]);
				}
			}
			for (uint j = 0; j < targets.length; j++) {
				jjDrawResizedSprite(jjPlayers[targets[j]].xPos, jjPlayers[targets[j]].yPos, ANIM::VINE, 0, 0, 2, 3, SPRITE::TRANSLUCENTCOLOR, AREA_OF_EFFECT_TARGET_COLOR, 1);
			}
		}
	}
	
	void drawChannelingBar(jjPLAYER@ play, jjCANVAS@ canvas) {
		Spell@ spell = cast<Spell@>(spells[players[play.playerID].selectedSpellKey]);
		int channelingBarXOrigin = jjSubscreenWidth / CHANNELING_BAR_ORIGIN_LEFT -
			int(spell.channelingTime * SECOND);
		int channelingBarYOrigin = jjSubscreenHeight / CHANNELING_BAR_ORIGIN_TOP;
		int channelingBarWidth = int(spell.channelingTime * SECOND * 2);
		int channelingPercent = channelingElapsed * 2;
		acDrawingGeneral::drawBox(canvas, channelingBarXOrigin, channelingBarYOrigin, channelingBarWidth, CHANNELING_BAR_HEIGHT,
			CHANNELING_BAR_BODY_COLOR, SPRITE::TRANSLUCENT, true, CHANNELING_BAR_BORDER_COLOR);
		acDrawingGeneral::drawBox(canvas, channelingBarXOrigin, channelingBarYOrigin, channelingPercent, CHANNELING_BAR_HEIGHT,
			CHANNELING_COLOR, SPRITE::TRANSLUCENT);
	}
	
	bool drawHUDObjects(jjPLAYER@ play, jjCANVAS@ canvas) {
		Player@ asPlayer = players[play.playerID];
		jjTEXTAPPEARANCE spinning(STRING::SPIN);
		spinning.xAmp = 0;
		spinning.yAmp = -1;
		jjTEXTAPPEARANCE centeredText();
		centeredText.align = STRING::CENTER;
		centeredText.pipe = STRING::SPECIALSIGN;
		if (treeState == 2) {
			acDrawing::drawTreePopup(canvas, centeredText);
		}
		if (wizardState == 2) {
			acDrawing::drawWizardPopup(asPlayer, canvas, centeredText);
		}
		if (keyMenuState == 2) {
			acDrawing::drawKeyMenu(canvas, centeredText);
		}
		if (infoOpen) {
			acDrawing::drawInfo(canvas, asPlayer, centeredText);
		} else if (skillsOpen) {
			acDrawing::drawSkills(asPlayer, canvas, centeredText);
		}
		if (players[play.playerID].isChanneling && play.isLocal) {
			acDrawing::drawChannelingBar(play, canvas);
		}
		if (spellBookOpen && play.isLocal) {
			acDrawing::drawSpellBook(play, canvas, spinning);
		} else if (!infoOpen && asPlayer.cooldown > 0) {
			string spellbookText = "Cooldown - " + int(asPlayer.cooldown/70) + " seconds";
			canvas.drawString(jjSubscreenWidth - jjGetStringWidth(spellbookText, STRING::SMALL, centeredText) / 2 - 4,
					jjSubscreenHeight / SPELLBOOK_ORIGIN_TOP + 16, spellbookText, STRING::SMALL, centeredText);
		} else {
			acDrawing::drawSpellCastHints(canvas, play, asPlayer, jjSubscreenWidth - SPELL_CAST_HINT_DEFAULT_WIDTH,
					jjSubscreenHeight / SPELLBOOK_ORIGIN_TOP + 16, SPELL_CAST_HINT_DEFAULT_WIDTH - 32);
		}
		if (!infoOpen) {
			acDrawing::drawMana(asPlayer, canvas);
			acDrawing::drawSelectedSpell(asPlayer, canvas);
			if (showResources) acDrawing::drawResources(play, canvas, centeredText);
			if (keyMenuState >= 1) acDrawing::drawKeyMenuTab(canvas);
			
			return false;
		}
		return true;
	}
	
	void drawInfo(jjCANVAS@ canvas, Player@ asPlayer, jjTEXTAPPEARANCE centeredText) {
		array<string> spellKeys = acInit::loadSpells();
		acSpells::updateSpellDescriptions(spellKeys, asPlayer);
		for (uint i = 0; i < spellKeys.length; i++) {
			string key = spellKeys[i];
			Spell@ spell = cast<Spell@>(spells[key]);
			int originX = i % 2 == 0 ? 0 : jjSubscreenWidth / 2;
			int originY = SPELL_BOXES_INIT_Y + INFO_BOX_HEIGHT * int(floor(i / 2)) + infoScrollY;
			acDrawingGeneral::drawBox(canvas, originX, originY, jjSubscreenWidth / 2, INFO_BOX_HEIGHT,
					TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
			drawSpellIcon(asPlayer, canvas, originX + 32, originY + 16, spell.enumValue);
			canvas.drawString(originX + 4, originY + 16, "" + spell.tier);
			canvas.drawString(originX + 70, originY + 38, "||||" + spell.name);
			canvas.drawString(originX + 270, originY + 16, "|Numpad", STRING::SMALL, centeredText);
			canvas.drawString(originX + 270, originY + 40, "|" + spell.numpad, STRING::SMALL, centeredText);
			canvas.drawString(originX + 344, originY + 16, "|||Mana", STRING::SMALL, centeredText);
			drawManaIcon(canvas, originX + 386, originY + 16);
			canvas.drawString(originX + 344, originY + 40, "|||" + spell.baseManaCost,
					STRING::SMALL, centeredText);
			canvas.drawString(originX + jjSubscreenWidth / 4, originY + 80,
					acUtils::splitText(spell.description, jjSubscreenWidth / 2 - INFO_BOX_TEXT_MARGIN), STRING::SMALL, centeredText);
		}
		int spellsLength = spellKeys.length;
		if (spellsLength% 2 == 1) {
			spellsLength++;
			int originX = jjSubscreenWidth / 2;
			int originY = SPELL_BOXES_INIT_Y + INFO_BOX_HEIGHT * int(floor(spellKeys.length / 2)) + infoScrollY;
			acDrawingGeneral::drawBox(canvas, originX, originY, jjSubscreenWidth / 2, INFO_BOX_HEIGHT,
					TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		}
		if (infoScrollY < SPELL_BOXES_INIT_Y) {
			drawScaledArrow(canvas, jjSubscreenWidth / 2, 32, 196, 1);
		}
		if (acUtils::infoViewIsAboveBottom()) {
			drawScaledArrow(canvas, jjSubscreenWidth / 2, jjSubscreenHeight - 32, 708, 1);
		}
		acDrawingGeneral::drawBox(canvas, 0, 0 + infoScrollY, jjSubscreenWidth, SPELL_BOXES_INIT_Y,
				TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		canvas.drawString(jjSubscreenWidth / 2, 16 + infoScrollY, "||||ACADEMY", STRING::SMALL, centeredText);
		canvas.drawString(jjSubscreenWidth / 2, 48 + infoScrollY, acUtils::splitText(ACADEMY_INFO_TEXT, jjSubscreenWidth - 32), STRING::SMALL, centeredText);
		canvas.drawString(jjSubscreenWidth / 2, SPELL_BOXES_INIT_Y - 12 + infoScrollY, "||||List of all available spells", STRING::SMALL, centeredText);
		hintBoxY = SPELL_BOXES_INIT_Y + INFO_BOX_HEIGHT / 2 * spellsLength + infoScrollY;
		acDrawingGeneral::drawBox(canvas, 0, hintBoxY, jjSubscreenWidth, HINT_BOX_HEIGHT,
				TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		canvas.drawString(jjSubscreenWidth / 2, hintBoxY + 64, acUtils::splitText(infoBoxHints[infoBoxHintPage],
				jjSubscreenWidth / 2 - INFO_BOX_TEXT_MARGIN), STRING::SMALL, centeredText);
		canvas.drawString(jjSubscreenWidth - 256, hintBoxY + HINT_BOX_HEIGHT - 8, "Click here for next hint..");
	}

	void drawKeyMenu(jjCANVAS@ canvas, jjTEXTAPPEARANCE centeredText) {
		acDrawingGeneral::drawBox(canvas, jjSubscreenWidth / 4, jjSubscreenHeight / 4, jjSubscreenWidth / 2, jjSubscreenHeight / 2,
				TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 16,
				"||||Key menu", STRING::SMALL, centeredText);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 32,
				"Left mouse click on a\nkey binding to change it.", STRING::SMALL, centeredText);
		for (uint i = 0; i < keyBindings.length; i++) {
			if (selectionHotkey == int(i)) {
				SPRITE::Mode mode = SPRITE::PALSHIFT;
				jjTEXTAPPEARANCE appearance = jjTEXTAPPEARANCE(STRING::SPIN);
				appearance.align = STRING::CENTER;
				appearance.xAmp = 1;
				appearance.yAmp = 0;
				canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 112 + i*16, keyBindings[i], STRING::SMALL, appearance, 0, mode, SELECTED_SPELL_COLOR);
			} else {
				canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 112 + i*16, keyBindings[i], STRING::SMALL, centeredText);
			}
		}
		if (hotkeyInUse) {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 * 3 - 32, HOTKEY_IN_USE, STRING::SMALL, centeredText);
		}
	}
	
	void drawKeyMenuTab(jjCANVAS@ canvas) {
		string keyString = getNameByKeyCode(hotkeyKeyMenu) + "-Key menu";
		acDrawingGeneral::drawBox(canvas, jjSubscreenWidth / KEY_MENU_TAB_LEFT, 0,
				jjGetStringWidth(keyString, STRING::SMALL, jjTEXTAPPEARANCE()) + 8,
				RESOURCE_BAR_HEIGHT, TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		canvas.drawString(jjSubscreenWidth / KEY_MENU_TAB_LEFT + 4, 10, keyString);
	}
	
	void drawLayer4(jjPLAYER@ play, jjCANVAS@ canvas) {
		jjTEXTAPPEARANCE centeredText();
		centeredText.align = STRING::CENTER;
		centeredText.pipe = STRING::SPECIALSIGN;
		acDrawing::drawScaledArrow(canvas, WIZARD_X + 16, WIZARD_Y + 24, 196, 0.7);
		acDrawing::drawScaledArrow(canvas, ELEVATOR_TILE_X * TILE + 16, ELEVATOR_TILE_Y * TILE + 16, 196, 0.7);
		acDrawing::drawScaledArrow(canvas, TREE_OF_KNOWLEDGE_EYES_X + 16, TREE_OF_KNOWLEDGE_EYES_Y + 24, 196, 0.7);
		canvas.drawSprite(TREE_OF_KNOWLEDGE_EYES_X, TREE_OF_KNOWLEDGE_EYES_Y, ANIM::WITCH, 1, 55);
		if (players[play.playerID].activeSkills.length < skills.length) {
			string costString = "Asks for ||||" + treeCost + "||x";
			canvas.drawString(TREE_OF_KNOWLEDGE_EYES_X + 16, TREE_OF_KNOWLEDGE_EYES_Y + 48, costString, STRING::SMALL, centeredText);
			int gemX = TREE_OF_KNOWLEDGE_EYES_X + 28 + jjGetStringWidth(costString, STRING::SMALL, centeredText) / 2;
			canvas.drawResizedSprite(gemX, TREE_OF_KNOWLEDGE_EYES_Y + 46, ANIM::PICKUPS, 34, 0, 0.3, 0.3, SPRITE::GEM, 3);
		} else {
			canvas.drawString(TREE_OF_KNOWLEDGE_EYES_X + 16, TREE_OF_KNOWLEDGE_EYES_Y + 64, MAXED,
					STRING::SMALL, centeredText);
		}
		string firstLearnableSpell = acUtils::getFirstLearnableSpell();
		if (firstLearnableSpell.length > 0) {
			Spell@ spell = cast<Spell@>(spells[firstLearnableSpell]);
			int spellPrice = acUtils::getSpellPriceByTier(spell.tier);
			string priceString = "Asks for ||||" + spellPrice + "||x";
			canvas.drawString(WIZARD_X + 16, WIZARD_Y + 48, priceString, STRING::SMALL, centeredText);
			int coinX = WIZARD_X + 24 + jjGetStringWidth(priceString, STRING::SMALL, centeredText) / 2;
			canvas.drawResizedSprite(coinX, WIZARD_Y + 48, ANIM::PICKUPS, 84, 0, 0.6, 0.6);
		} else {
			canvas.drawString(WIZARD_X + 16, WIZARD_Y + 48, MAXED, STRING::SMALL, centeredText);
		}
		if (jjGameTicks % 5 == 0) {
			jjPARTICLE@ particle = jjAddParticle(PARTICLE::PIXEL);
			if (particle !is null) {
				uint randomX = jjRandom() % (8*TILE);
				particle.xPos = 60*TILE + randomX;
				particle.yPos = 86*TILE;
				particle.ySpeed = -7.45;
				particle.pixel.color[1] = 34;
				particle.pixel.size = 1;
			}
		}
		for (uint i = 0; i < visualGems.length; i++) {
			VisualGem@ gem = visualGems[i];
			gem.draw(canvas);
		}
		gemMine.draw(canvas);
	}
	
	void drawMana(Player@ asPlayer, jjCANVAS@ canvas) {
		string manaString = "|||" + asPlayer.mana + "/" + asPlayer.maxMana;
		int stringWidth = jjGetStringWidth(manaString, STRING::SMALL, STRING::NORMAL);
		drawManaIcon(canvas, jjSubscreenWidth - stringWidth - 16, jjSubscreenHeight / 8 + 18);
		canvas.drawString(jjSubscreenWidth - stringWidth - 8, jjSubscreenHeight / 8 + 24, manaString);
	}

	void drawManaIcon(jjCANVAS@ canvas, int x, int y) {
		canvas.drawResizedSprite(x, y, ANIM::PICKUPS, 22, 0, 0.7, 0.7, SPRITE::GEM, 12);
	}
	
	void drawResources(jjPLAYER@ play, jjCANVAS@ canvas, jjTEXTAPPEARANCE centeredText) {
		acDrawingGeneral::drawBox(canvas, 0, jjSubscreenHeight - RESOURCE_BAR_HEIGHT, RESOURCE_BAR_WIDTH, RESOURCE_BAR_HEIGHT,
				TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		acDrawingGeneral::drawBox(canvas, RESOURCE_BAR_WIDTH, jjSubscreenHeight - RESOURCE_BAR_HEIGHT, RESOURCE_BAR_WIDTH, RESOURCE_BAR_HEIGHT,
				TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		canvas.drawResizedSprite(16, jjSubscreenHeight - 7, ANIM::PICKUPS, 34, 0, 0.3, 0.3, SPRITE::GEM, 3);
		canvas.drawResizedSprite(RESOURCE_BAR_WIDTH + 16, jjSubscreenHeight - 7, ANIM::PICKUPS, 84, 0, 0.6, 0.6);
		canvas.drawString(48, jjSubscreenHeight - 5, "" + play.gems[GEM::PURPLE], STRING::SMALL, centeredText);
		canvas.drawString(RESOURCE_BAR_WIDTH + 48, jjSubscreenHeight - 5, "" + play.coins, STRING::SMALL, centeredText);
	}
	
	void drawScaledArrow(jjCANVAS@ canvas, int x, int y, int angle, float scale) {
		canvas.drawRotatedSprite(x, y, ANIM::FLAG, 0, 0, angle, scale, scale, SPRITE::SINGLECOLOR, 15);
	}
	
	void drawSelectedSpell(Player@ asPlayer, jjCANVAS@ canvas) {
		int baseX = jjSubscreenWidth - 32;
		int baseY = jjSubscreenHeight / 8 + 40;
		canvas.drawResizedSprite(baseX, baseY, ANIM::BOLLPLAT, 0, 0, 1.2, 1.2, SPRITE::TRANSLUCENTCOLOR, 1);
		SPELL selectedSpell = SPELL_NONE;
		if (asPlayer.selectedSpellKey != "") {
			selectedSpell = cast<Spell@>(spells[asPlayer.selectedSpellKey]).enumValue;
		}
		drawSpellIcon(asPlayer, canvas, baseX, baseY, selectedSpell, true);
	}
	
	void drawSkills(Player@ asPlayer, jjCANVAS@ canvas, jjTEXTAPPEARANCE centeredText) {
		acDrawingGeneral::drawBox(canvas, jjSubscreenWidth / 8, jjSubscreenHeight / 4, jjSubscreenWidth / 4 * 3, jjSubscreenHeight / 2,
				TEXT_BOX_BODY_COLOR, SPRITE::NORMAL, true, TEXT_BOX_BORDER_COLOR);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 16, "||||Skills", STRING::SMALL, centeredText);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 32, acUtils::splitText(SKILLS_TIP, jjSubscreenWidth / 4 * 3 - 64), STRING::SMALL, centeredText);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 112, "||||Active Skills", STRING::SMALL, centeredText);
		if (asPlayer.activeSkills.length == 0) {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 128, acUtils::splitText(EMPTY_SKILLS, jjSubscreenWidth / 4 * 3 - 64), STRING::SMALL, centeredText);
		}
		for (uint i = 0; i < asPlayer.activeSkills.length; i++) {
			int index = skills.find(Skill(asPlayer.activeSkills[i]));
			if (index >= 0) {
				Skill@ skill = skills[index];
				canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + i*16 + 128, skill.name + " (" + skill.description + ")", STRING::SMALL, centeredText);
			}
		}
	}

	void drawSpellBook(jjPLAYER@ play, jjCANVAS@ canvas, jjTEXTAPPEARANCE spinning) {
		Player@ asPlayer = players[play.playerID];
		array<Spell@> spellArray;
		for (uint i = 0; i < ownSpells.length; i++) {
			spellArray.insertLast(cast<Spell@>(spells[ownSpells[i]]));
		}
		spellArray.sortAsc();
		int spellBookWidth = TEXT_MARGIN_LEFT * 2 + jjGetStringWidth(
				acSpells::getLongestSpellName(), STRING::SMALL, STRING::NORMAL) + 64;
		int spellBookHeight = HEADER_MARGIN_TOP * 4 + (ownSpells.length - 1) * ROW_SPACING;
		int spellBookXOrigin = jjSubscreenWidth - spellBookWidth - SPELLBOOK_MARGIN_RIGHT;
		int spellBookYOrigin = jjSubscreenHeight / SPELLBOOK_ORIGIN_TOP + 16;
		if (ownSpells.length == 0) {
			spellBookHeight = TEXT_MARGIN_TOP * 2 + 3 * 16;
		}
		acDrawingGeneral::drawBox(canvas, spellBookXOrigin, spellBookYOrigin, spellBookWidth, spellBookHeight, SPELLBOOK_BODY_COLOR, SPRITE::TRANSLUCENT, true, SPELLBOOK_BORDER_COLOR);
		canvas.drawResizedSprite(spellBookXOrigin + TEXT_MARGIN_LEFT + 16, spellBookYOrigin + HEADER_MARGIN_TOP, ANIM::PICKUPS, 22, 0, 0.7, 0.7, SPRITE::GEM, 12);
		canvas.drawString(spellBookXOrigin + TEXT_MARGIN_LEFT, spellBookYOrigin + HEADER_MARGIN_TOP, "     |Numpad |||||Spell");
		if (ownSpells.length == 0) {
			jjTEXTAPPEARANCE withNewLine(STRING::NORMAL);
			withNewLine.newline = STRING::SPECIALSIGN;
			canvas.drawString(spellBookXOrigin + TEXT_MARGIN_LEFT + 48, spellBookYOrigin + TEXT_MARGIN_TOP, EMPTY_SPELLBOOK);
			canvas.drawString(spellBookXOrigin + TEXT_MARGIN_LEFT + 48, spellBookYOrigin + TEXT_MARGIN_TOP + 24, SPELLBOOK_TIP,
			STRING::SMALL, withNewLine);
		}
		for (uint i = 0; i < spellArray.length; i++) {
			Spell@ spell = spellArray[i];
			if (spellArray[i].key == asPlayer.selectedSpellKey) {
				SPRITE::Mode mode = SPRITE::PALSHIFT;
				jjTEXTAPPEARANCE appearance = jjTEXTAPPEARANCE(STRING::SPIN);
				appearance.xAmp = 1;
				appearance.yAmp = 0;
				string spellString = "" + spell.baseManaCost + "   " + spellArray[i].numpad + "   " + spell.name;
				canvas.drawString(spellBookXOrigin + TEXT_MARGIN_LEFT,
						spellBookYOrigin + TEXT_MARGIN_TOP + i * ROW_SPACING,
						spellString, STRING::SMALL, appearance, 0, mode, SELECTED_SPELL_COLOR);
			} else {
				jjTEXTAPPEARANCE appearance = jjTEXTAPPEARANCE();
				appearance.pipe = STRING::SPECIALSIGN;
				string spellString = "|||" + spell.baseManaCost + "   ||||||" + spellArray[i].numpad + "   |||||" + spell.name;
				canvas.drawString(spellBookXOrigin + TEXT_MARGIN_LEFT,
						spellBookYOrigin + TEXT_MARGIN_TOP + i * ROW_SPACING,
						spellString, STRING::SMALL, appearance, 0);
			}
		}
		drawSpellCastHints(canvas, play, asPlayer, spellBookXOrigin, spellBookYOrigin + spellBookHeight + HEADER_MARGIN_TOP, spellBookWidth - 64);
	}

	void drawSpellCastHints(jjCANVAS@ canvas, jjPLAYER@ play, Player@ asPlayer, int x, int y, int rowWidth) {
		jjTEXTAPPEARANCE appearance = jjTEXTAPPEARANCE();
		appearance.newline = STRING::SPECIALSIGN;
		if (asPlayer.selectedSpellKey == "I" && activeWallsOfFire.length >= MAX_ACTIVE_WALLS_OF_FIRE) {
			string hint = "There are too many active walls of fire. Max: ||||" + MAX_ACTIVE_WALLS_OF_FIRE;
			canvas.drawString(x, y, acUtils::splitText(hint, rowWidth), STRING::SMALL, appearance);
		} else if (asPlayer.selectedSpellKey != "") {
			Spell@ selectedSpell = cast<Spell@>(spells[asPlayer.selectedSpellKey]);
			string hint = acSpells::canCastSpell(players[play.playerID], selectedSpell) ? selectedSpell.channelingTime == 0 ?
				"Press fire to cast instantly" : "Hold fire to channel" : "Not enough mana to cast that spell";
			canvas.drawString(x, y, acUtils::splitText(hint, rowWidth), STRING::SMALL, appearance);
		}
	}
	
	void drawSpellIcon(Player@ asPlayer, jjCANVAS@ canvas, int baseX, int baseY, SPELL spellEnum,
			bool darkenUncastableSpell = false) {
		SPRITE::Mode spriteMode = SPRITE::NORMAL;
		uint8 spriteParam = 0;
		switch (spellEnum) {
			case SPELL_MAGIC_ARROW:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::MENUPLAYER;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX + 12, baseY + 20, ANIM::FLAG, 0, 0, 425, 1, 1,
						spriteMode, spriteParam);
				break;
			case SPELL_STONE_SKIN:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLEHUE;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 65;
				canvas.drawRotatedSprite(baseX - 4, baseY + 16, ANIM::JAZZ, RABBIT::IDLE3, 0, 0, 0.7, 0.7,
						spriteMode, spriteParam);
						
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX + 8, baseY + 28, ANIM::BIGROCK, 0, 0, 0, 0.3, 0.3,
						spriteMode, spriteParam);
				break;
			case SPELL_BLESS:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX + 2, baseY + 2, ANIM::AMMO, 4, 0, 0, 1.5, 1.5,
						spriteMode, spriteParam);
				
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLEHUE;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 80;
				canvas.drawResizedSprite(baseX + 2, baseY + 20, ANIM::LORI, RABBIT::JUMPING3, 0, 0.8, 0.8,
						spriteMode, spriteParam);
				break;
			case SPELL_BLOOD_LUST:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLECOLOR;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 24;
				canvas.drawResizedSprite(baseX, baseY + 16, ANIM::AMMO, 2, 4, 1, 1,
						spriteMode, spriteParam);
				
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLEHUE;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 24;
				canvas.drawResizedSprite(baseX - 3, baseY + 18, ANIM::SPAZ2, 1, 25, 0.8, 0.8,
						spriteMode, spriteParam);
				break;
			case SPELL_DISPEL:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawResizedSprite(baseX + 2, baseY + 8, ANIM::AMMO, 82, 5, 0.8, 0.8,
						spriteMode, spriteParam);
				canvas.drawResizedSprite(baseX - 3, baseY + 19, ANIM::LORI, RABBIT::HURT, 7, 0.8, 0.8,
						spriteMode, spriteParam);
				break;
			case SPELL_SLOW:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX, baseY + 4, ANIM::PICKUPS, 42, 0, 0, 0.7, 0.7,
						spriteMode, spriteParam);
				canvas.drawRotatedSprite(baseX, baseY + 18, ANIM::TURTLE, 0, 0, 0, 0.7, 0.7,
						spriteMode, spriteParam);
				break;
			case SPELL_VISIONS:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawString(baseX - 10, baseY + 16, "V", STRING::MEDIUM, jjTEXTAPPEARANCE(STRING::NORMAL), 0,
						spriteMode, spriteParam);
				break;
			case SPELL_CURE:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLEHUE;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 32;
				canvas.drawResizedSprite(baseX, baseY + 26, ANIM::PICKUPS, 78, 0, 1, 1,
						spriteMode, spriteParam);
				
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawResizedSprite(baseX + 2, baseY + 8, ANIM::AMMO, 8, 0, 0.8, 0.8,
						spriteMode, spriteParam);
				canvas.drawResizedSprite(baseX + 2, baseY + 2, ANIM::AMMO, 8, 0, 0.5, 0.5,
						spriteMode, spriteParam);
				break;
			case SPELL_DISRUPTING_RAY:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLEHUE;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 10;
				canvas.drawResizedSprite(baseX - 2, baseY + 20, ANIM::JAZZ, RABBIT::FALLLAND, 0, 0.8, 0.8,
						spriteMode, spriteParam);
				canvas.drawRotatedSprite(baseX + 6, baseY + 24, ANIM::AMMO, 30, 0, 511, 1, 1,
						spriteMode, spriteParam);
				break;
			case SPELL_WEAKNESS:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX - 5, baseY + 9, ANIM::SPAZ, RABBIT::STATIONARYJUMPSTART, 0, 0, 0.8, 0.8,
						spriteMode, spriteParam);
				break;
			case SPELL_ICE_BOLT:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::TINTED;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 35;
				canvas.drawRotatedSprite(baseX - 2, baseY + 18, ANIM::AMMO, 11, 0, 540, 1, 1,
						spriteMode, spriteParam);
				break;
			case SPELL_DEATH_RIPPLE:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX + 2, baseY + 16, ANIM::SKELETON, 0, 1, 0, 2, 2,
						spriteMode, spriteParam);
				canvas.drawRotatedSprite(baseX + 2, baseY + 16, ANIM::SKELETON, 0, 3, 0, 2, 2,
						spriteMode, spriteParam);
				canvas.drawRotatedSprite(baseX, baseY + 14, ANIM::SKELETON, 1, 6, 0, 1.2, 1.2,
						spriteMode, spriteParam);
				break;
			case SPELL_PRECISION:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX, baseY + 17, ANIM::SONCSHIP, 7, 0, 0, 1.3, 1.3,
						spriteMode, spriteParam);
				break;
			case SPELL_WALL_OF_FIRE:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawResizedSprite(baseX + 2, baseY + 24, ANIM::AMMO, 13, 1, 1.8, 1.8,
						spriteMode, spriteParam);
				break;
			case SPELL_FORGETFULNESS:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawResizedSprite(baseX - 2, baseY + 18, ANIM::SPAZ, RABBIT::STONED, 0, 0.8, 0.8,
						spriteMode, spriteParam);
				canvas.drawString(baseX + 4, baseY + 14, "?", STRING::SMALL, jjTEXTAPPEARANCE(STRING::NORMAL), 0,
						spriteMode, spriteParam);
				break;
			case SPELL_FROST_RING:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::TINTED;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 32;
				canvas.drawRotatedSprite(baseX - 2, baseY + 16, ANIM::AMMO, 82, 3, 0, 1.2, 1.2,
						spriteMode, spriteParam);
				break;
			case SPELL_FIREBALL:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX - 2, baseY + 16, ANIM::AMMO, 14, 1, 540, 1.2, 1.2,
						spriteMode, spriteParam);
				break;
			case SPELL_FRENZY:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::SINGLEHUE;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 24;
				canvas.drawResizedSprite(baseX, baseY + 14, ANIM::SPAZ, RABBIT::STATIONARYJUMP, 0, 0.8, 0.8,
						spriteMode, spriteParam);
				break;
			case SPELL_CHAIN_LIGHTNING:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawRotatedSprite(baseX, baseY + 2, ANIM::AMMO, 4, 0, 0, 1, 1,
						spriteMode, spriteParam);
				for (int i = 6; i < 28; i += 4) {
					canvas.drawRotatedSprite(baseX, baseY + i, ANIM::AMMO, 4, 0, 0, 0.5, 0.5,
							spriteMode, spriteParam);
				}
				canvas.drawRotatedSprite(baseX, baseY + 30, ANIM::AMMO, 4, 0, 0, 1, 1,
						spriteMode, spriteParam);
				break;
			case SPELL_ARMAGEDDON:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::TINTED;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 24;
				canvas.drawRotatedSprite(baseX + 1, baseY + 18, ANIM::ROCK, 0, 0, 0, 0.4, 0.4,
						spriteMode, spriteParam);
				break;
			case SPELL_IMPLOSION:
				spriteMode = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteModeUncastable : SPRITE::NORMAL;
				spriteParam = isDrawUncastable(asPlayer, spellEnum, darkenUncastableSpell) ?
						spriteParamUncastable : 0;
				canvas.drawResizedSprite(baseX + 2, baseY + 16, ANIM::SPAZ, RABBIT::DIE, 1, 0.5, 0.5,
						spriteMode, spriteParam);
				break;
			case SPELL_NONE:
			default:
				//Draw nothing
				break;
		}
	}
	
	void drawTreePopup(jjCANVAS@ canvas, jjTEXTAPPEARANCE centeredText) {
		acDrawingGeneral::drawBox(canvas, jjSubscreenWidth / 4, jjSubscreenHeight / 4, jjSubscreenWidth / 2, jjSubscreenHeight / 2,
				TEXT_BOX_BODY_COLOR, SPRITE::TRANSLUCENT, true, TEXT_BOX_BORDER_COLOR);
		if (treeCost == TREE_BASE_COST) {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 3,
					"Ahh, an adventurer!\nI will be happy to teach you\na little of what I have learned\nover the ages for\na mere ||||"
					+ treeCost + " ||(purple) gems.\n(Just bury it around my roots.)", STRING::SMALL, centeredText);
		} else {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 3,
				"Hello again adventurer!\nI will be happy to continue\nmy teachings for \na little more gems.\nI only ask for \na mere "
				+ treeCost + " (purple) gems.", STRING::SMALL, centeredText);
		}
		canvas.drawString(jjSubscreenWidth / 2 - 32, jjSubscreenHeight / 10 * 6, "||||" + treeCost + "||x");
		canvas.drawResizedSprite(jjSubscreenWidth / 2 + 16, jjSubscreenHeight / 10 * 6, ANIM::PICKUPS, 34, 0, 0.5, 0.5, SPRITE::GEM, 3);
		canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 10 * 7, TREE_OF_KNOWLEDGE_TIP, STRING::SMALL, centeredText);
	}

	void drawWallOfFire(WallOfFire@ wallOfFire) {
		for (float y = wallOfFire.yOrigin; y > wallOfFire.yOrigin - wallOfFire.height*TILE; y -= 32) {
			for (float x = wallOfFire.xOrigin; x < wallOfFire.xOrigin + WALL_OF_FIRE_WIDTH*TILE; x += 32) {
				jjDrawResizedSprite(x, y, ANIM::AMMO, 13, wallOfFire.frame, 2.5, 2.5);
			}
		}
		if (jjGameTicks % 10 == 0) {
			if (wallOfFire.frame < 2) {
				wallOfFire.frame = 2;
			} else if (wallOfFire.frame < 3) {
				wallOfFire.frame = 3;
			} else {
				wallOfFire.frame = 1;
			}
		}
		jjTEXTAPPEARANCE centeredText();
		centeredText.align = STRING::CENTER;
		centeredText.pipe = STRING::SPECIALSIGN;
		wallOfFire.channel = jjSampleLooped(wallOfFire.xOrigin + WALL_OF_FIRE_WIDTH*TILE/2, wallOfFire.yOrigin,
				SOUND::COMMON_BURNIN, wallOfFire.channel);
		jjDrawString(wallOfFire.xOrigin + WALL_OF_FIRE_WIDTH*TILE/2 - 20, wallOfFire.yOrigin + 32,
					"||||" + int(wallOfFire.elapsed / SECOND), STRING::SMALL, centeredText, 0, SPRITE::PALSHIFT,
					0, 1);
	}

	void drawWallOfFireTarget(jjPLAYER@ play) {
		int direction = play.direction;
		float xOrigin = play.xPos + direction * WALL_OF_FIRE_CASTING_RANGE;
		float yOrigin = play.yPos + 16;
		int height = acUtils::getWallOfFireHeightInTiles(xOrigin, yOrigin);
		jjDrawRectangle(xOrigin - 16, yOrigin-height*TILE, WALL_OF_FIRE_WIDTH*TILE, height*TILE, AREA_OF_EFFECT_COLOR, SPRITE::TRANSLUCENT, 50, 1);
	}
	
	void drawWizardPopup(Player@ asPlayer, jjCANVAS@ canvas, jjTEXTAPPEARANCE centeredText) {
		acDrawingGeneral::drawBox(canvas, jjSubscreenWidth / 6, jjSubscreenHeight / 4, jjSubscreenWidth / 3 * 2, jjSubscreenHeight / 2,
				TEXT_BOX_BODY_COLOR, SPRITE::TRANSLUCENT, true, TEXT_BOX_BORDER_COLOR);
		if (wizardFirstEncounter) {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 16,
					acUtils::splitText(WIZARD_TEXT, jjSubscreenWidth / 3 * 2 - 64), STRING::SMALL, centeredText);
		} else {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 16,
					acUtils::splitText(WIZARD_TEXT_RETURN, jjSubscreenWidth / 3 * 2 - 64), STRING::SMALL, centeredText);
		}
		string firstLearnableSpell = acUtils::getFirstLearnableSpell();
		if (firstLearnableSpell.length > 0) {
			Spell@ spell = cast<Spell@>(spells[firstLearnableSpell]);
			int spellPrice = acUtils::getSpellPriceByTier(spell.tier);
			string priceString = "Cost: ||||||" + spellPrice + "||x";
			drawSpellIcon(asPlayer, canvas, jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 120, spell.enumValue);
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 172,
					"||||" + spell.name + "\n||Tier " + spell.tier + "\n" + priceString,
					STRING::SMALL, centeredText);
			canvas.drawResizedSprite(
					jjSubscreenWidth / 2 + jjGetStringWidth(priceString, STRING::SMALL, centeredText) / 2 + 16,
					jjSubscreenHeight / 4 + 210, ANIM::PICKUPS, 84, 0, 0.8, 0.8);
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 10 * 7, WIZARD_TIP,
					STRING::SMALL, centeredText);
		} else {
			canvas.drawString(jjSubscreenWidth / 2, jjSubscreenHeight / 4 + 16,
					acUtils::splitText("Placeholder", jjSubscreenWidth / 3 * 2 - 64), STRING::SMALL, centeredText);
		}
	}
	
	bool isDrawUncastable(Player@ asPlayer, SPELL spellEnum, bool darkenUncastableSpell) {
		return darkenUncastableSpell && !acSpells::canCastSpell(asPlayer, spellEnum);
	}
}