mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2025-04-25 17:59:34 +00:00
1452 lines
56 KiB
JavaScript
1452 lines
56 KiB
JavaScript
// The main game canvas where everything will be drawn
|
|
"use strict";
|
|
/** @type {CanvasRenderingContext2D} */
|
|
let MainCanvas;
|
|
/** @type {CanvasRenderingContext2D} */
|
|
let TempCanvas;
|
|
/** @type {CanvasRenderingContext2D} */
|
|
let ColorCanvas;
|
|
/** @type {CanvasRenderingContext2D} */
|
|
let CharacterCanvas;
|
|
var DialogLeaveDueToItem = false;
|
|
|
|
var BlindFlash = false;
|
|
var DrawingBlindFlashTimer = 0;
|
|
|
|
// A bank of all the chached images
|
|
/** @type {Map<string, HTMLImageElement>} */
|
|
const DrawCacheImage = new Map;
|
|
let DrawCacheLoadedImages = 0;
|
|
let DrawCacheTotalImages = 0;
|
|
|
|
// Last dark factor for blindflash
|
|
var DrawLastDarkFactor = 0;
|
|
|
|
/**
|
|
* A list of the characters that are drawn every frame
|
|
* @type {Character[]}
|
|
*/
|
|
var DrawLastCharacters = [];
|
|
|
|
/**
|
|
* A list of elements to draw at the end of the drawing process.
|
|
* Mostly used for hovering button labels.
|
|
* @type {Function[]}
|
|
*/
|
|
var DrawHoverElements = [];
|
|
|
|
/**
|
|
* The last canvas position in format `[left, top, width, height]`
|
|
*/
|
|
var DrawCanvasPosition = [0, 0, 0, 0];
|
|
|
|
/**
|
|
* Converts a hex color string to a RGB color
|
|
* @param {string} color - Hex color to conver
|
|
* @returns {{ r: number, g: number, b: number }} - RGB color
|
|
*/
|
|
function DrawHexToRGB(color) {
|
|
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
color = color.replace(shorthandRegex, function (m, r, g, b) {
|
|
return r + r + g + g + b + b;
|
|
});
|
|
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
|
|
return result ? {
|
|
r: parseInt(result[1], 16),
|
|
g: parseInt(result[2], 16),
|
|
b: parseInt(result[3], 16)
|
|
} : {
|
|
r: 0,
|
|
g: 0,
|
|
b: 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Converts a RGB color to a hex color string
|
|
* @param {number[]} color - RGB color to conver
|
|
* @returns {string} - Hex color string
|
|
*/
|
|
function DrawRGBToHex(color) {
|
|
const rgb = color[2] | (color[1] << 8) | (color[0] << 16);
|
|
return '#' + (0x1000000 + rgb).toString(16).slice(1).toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Loads the canvas to draw on with its style and event listeners.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawLoad() {
|
|
|
|
// Creates the objects used in the game
|
|
MainCanvas = /** @type {HTMLCanvasElement} */ (document.getElementById("MainCanvas")).getContext("2d");
|
|
TempCanvas = document.createElement("canvas").getContext("2d");
|
|
ColorCanvas = document.createElement("canvas").getContext("2d");
|
|
CharacterCanvas = document.createElement("canvas").getContext("2d");
|
|
CharacterCanvas.canvas.width = 500;
|
|
CharacterCanvas.canvas.height = CanvasDrawHeight;
|
|
document.getElementById("MainCanvas").addEventListener("keypress", KeyDown);
|
|
document.getElementById("MainCanvas").tabIndex = 1000;
|
|
document.addEventListener("keydown", DocumentKeyDown);
|
|
|
|
// Font is fixed for now, color can be set
|
|
MainCanvas.font = CommonGetFont(36);
|
|
MainCanvas.textAlign = "center";
|
|
MainCanvas.textBaseline = "middle";
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the image file from cache or build it from the source
|
|
* @param {string} Source - URL of the image
|
|
* @returns {HTMLImageElement} - Image file
|
|
*/
|
|
function DrawGetImage(Source) {
|
|
// Search in the cache to find the image and make sure this image is valid
|
|
let Img = DrawCacheImage.get(Source);
|
|
if (!Img) {
|
|
Img = new Image;
|
|
DrawCacheImage.set(Source, Img);
|
|
// Keep track of image load state
|
|
const IsAsset = (Source.indexOf("Assets") >= 0);
|
|
if (IsAsset) {
|
|
++DrawCacheTotalImages;
|
|
Img.addEventListener("load", function () {
|
|
DrawGetImageOnLoad();
|
|
});
|
|
}
|
|
|
|
Img.addEventListener("error", function () {
|
|
DrawGetImageOnError(Img, IsAsset);
|
|
});
|
|
|
|
// Start loading
|
|
Img.src = Source;
|
|
}
|
|
|
|
// returns the final image
|
|
return Img;
|
|
}
|
|
|
|
/**
|
|
* Reloads all character canvas once all images are loaded
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawGetImageOnLoad() {
|
|
++DrawCacheLoadedImages;
|
|
if (DrawCacheLoadedImages == DrawCacheTotalImages) CharacterLoadCanvasAll();
|
|
}
|
|
|
|
/**
|
|
* Attempts to redownload an image if it previously failed to load
|
|
* @param {HTMLImageElement & { errorcount?: number }} Img - Image tag that failed to load
|
|
* @param {boolean} IsAsset - Whether or not the image is part of an asset
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawGetImageOnError(Img, IsAsset) {
|
|
if (Img.errorcount == null) Img.errorcount = 0;
|
|
Img.errorcount += 1;
|
|
if (Img.errorcount < 3) {
|
|
// eslint-disable-next-line no-self-assign
|
|
Img.src = Img.src;
|
|
} else {
|
|
// Load failed. Display the error in the console and mark it as done.
|
|
console.log("Error loading image " + Img.src);
|
|
if (IsAsset) DrawGetImageOnLoad();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the alpha of a screen flash. append to a color like "#111111" + DrawGetScreenFlash(FlashTime)
|
|
* @param {number} FlashTime - Time remaining as part of the screen flash
|
|
* @returns {string} - alpha of screen flash
|
|
*/
|
|
function DrawGetScreenFlash(FlashTime) {
|
|
let alpha = Math.max(0, Math.min(255, Math.floor(ChatRoomPinkFlashAlphaStrength * (1 - Math.exp(-FlashTime/2500))))).toString(16);
|
|
if (alpha.length < 2) alpha = "0" + alpha;
|
|
return alpha;
|
|
}
|
|
|
|
/**
|
|
* Draws the glow under the arousal meter under the screen
|
|
* @param {number} X - Position of the meter on the X axis
|
|
* @param {number} Y - Position of the meter on the Y axis
|
|
* @param {number} Zoom - Zoom factor
|
|
* @param {number} Level - Current vibration level on a scale of 0 to 4. Must be INTEGER
|
|
* @param {boolean} Animated - Whether or not animations should be played
|
|
* @param {boolean} Orgasm - Whether or not the meter is in recover from orgasm mode
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawArousalGlow(X, Y, Zoom, Level, Animated, AnimFactor, Orgasm) {
|
|
if (!Orgasm) {
|
|
let Rx = 0;
|
|
let Ry = 0;
|
|
|
|
if (Level > 0 && Animated) {
|
|
Rx = -(1 + AnimFactor * Level / 2) + (2 + AnimFactor * Level) * Math.random();
|
|
Ry = -(1 + AnimFactor * Level / 2) + (2 + AnimFactor * Level) * Math.random();
|
|
}
|
|
if (!Animated || (Level > 0 || CommonTime() % 1000 > 500))
|
|
DrawImageZoomCanvas("Screens/Character/Player/ArousalMeter_Glow_" + Math.max(0, Math.min(Math.floor(Level), 4)) + ".png", MainCanvas, 0, 0, 300, 700, X - 100 * Zoom + Rx, Y - 100 * Zoom + Ry, 300 * Zoom, 700 * Zoom);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Draws the arousal meter on screen
|
|
* @param {number} X - Position of the meter on the X axis
|
|
* @param {number} Y - Position of the meter on the Y axis
|
|
* @param {number} Zoom - Zoom factor
|
|
* @param {number} Progress - Current progress of the arousal meter
|
|
* @param {boolean} Automatic - Wheter or not the arousal is in automatic mode
|
|
* @param {boolean} Orgasm - Whether or not the meter is in recover from orgasm mode
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawArousalThermometer(X, Y, Zoom, Progress, Automatic, Orgasm) {
|
|
DrawImageZoomCanvas("Screens/Character/Player/ArousalMeter" + (Orgasm ? "Orgasm" : "") + (Automatic ? "Automatic" : "") + ".png", MainCanvas, 0, 0, 100, 500, X, Y, 100 * Zoom, 500 * Zoom);
|
|
if ((Progress > 0) && !Orgasm) DrawRect(X + (30 * Zoom), Y + (15 * Zoom) + (Math.round((100 - Progress) * 4 * Zoom)), (40 * Zoom), (Math.round(Progress * 4 * Zoom)), "#FF0000");
|
|
}
|
|
|
|
/**
|
|
* Draw the arousal meter next to the player if it is allowed by the character and visible for the player
|
|
* @param {Character} C - Character for which to potentially draw the arousal meter
|
|
* @param {number} X - Position of the meter on the X axis
|
|
* @param {number} Y - Position of the meter on the Y axis
|
|
* @param {number} Zoom - Zoom factor
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawArousalMeter(C, X, Y, Zoom) {
|
|
if (ActivityAllowed() && PreferenceArousalAtLeast(C, "Manual"))
|
|
if (C.ID == 0 || (C.ArousalSettings.Visible == "Access" && C.AllowItem) || C.ArousalSettings.Visible == "All")
|
|
if (C.ID == 0 || (Player.ArousalSettings.ShowOtherMeter == null) || Player.ArousalSettings.ShowOtherMeter) {
|
|
ActivitySetArousal(C, C.ArousalSettings.Progress);
|
|
if (Player.ArousalSettings.VFX != "VFXInactive" && C.ArousalSettings.Progress > 0 && PreferenceArousalAtLeast(C, "Hybrid")) {
|
|
let Progress = 0;
|
|
if (!(C.ArousalSettings.VibratorLevel == null || typeof C.ArousalSettings.VibratorLevel !== "number" || isNaN(C.ArousalSettings.VibratorLevel))) {
|
|
Progress = C.ArousalSettings.VibratorLevel;
|
|
}
|
|
|
|
if (Progress > 0) { // -1 is disabled
|
|
const animationTimeMax = 5000; // 5 seconds
|
|
const animationTimeLeft = Math.min(C.ArousalSettings.ChangeTime - CommonTime(), 0) + animationTimeMax;
|
|
|
|
DrawArousalGlow(
|
|
X + (C.ArousalZoom ? 50 : 90) * Zoom,
|
|
Y + (C.ArousalZoom ? 200 : 400) * Zoom,
|
|
C.ArousalZoom ? Zoom : Zoom * 0.2,
|
|
Progress,
|
|
Player.ArousalSettings.VFX == "VFXAnimated" || (Player.ArousalSettings.VFX == "VFXAnimatedTemp" && C.ArousalSettings.ChangeTime != null && animationTimeLeft > 0),
|
|
Math.max(0, animationTimeLeft / animationTimeMax),
|
|
C.ArousalSettings.OrgasmTimer != null && typeof C.ArousalSettings.OrgasmTimer === "number" && !isNaN(C.ArousalSettings.OrgasmTimer) && C.ArousalSettings.OrgasmTimer > 0);
|
|
}
|
|
}
|
|
|
|
DrawArousalThermometer(
|
|
X + (C.ArousalZoom ? 50 : 90) * Zoom,
|
|
Y + (C.ArousalZoom ? 200 : 400) * Zoom,
|
|
C.ArousalZoom ? Zoom : Zoom * 0.2,
|
|
C.ArousalSettings.Progress,
|
|
PreferenceArousalAtLeast(C, "Automatic"),
|
|
C.ArousalSettings.OrgasmTimer != null && typeof C.ArousalSettings.OrgasmTimer === "number" && !isNaN(C.ArousalSettings.OrgasmTimer) && C.ArousalSettings.OrgasmTimer > 0);
|
|
|
|
if (C.ArousalZoom && (typeof C.ArousalSettings.OrgasmCount === "number") && (C.ArousalSettings.OrgasmCount >= 0) && (C.ArousalSettings.OrgasmCount <= 9999)) {
|
|
MainCanvas.font = CommonGetFont(Math.round(36 * Zoom).toString());
|
|
DrawText(((C.ArousalSettings.OrgasmCount != null) ? C.ArousalSettings.OrgasmCount : 0).toString(), X + 100 * Zoom, Y + 655 * Zoom, "Black", "Gray");
|
|
MainCanvas.font = CommonGetFont(36);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refreshes the character if not all images are loaded and draw the character canvas on the main game screen
|
|
* @param {Character} C - Character to draw
|
|
* @param {number} X - Position of the character on the X axis
|
|
* @param {number} Y - Position of the character on the Y axis
|
|
* @param {number} Zoom - Zoom factor
|
|
* @param {boolean} [IsHeightResizeAllowed=true] - Whether or not the settings allow for the height modifier to be applied
|
|
* @param {CanvasRenderingContext2D} [DrawCanvas] - The canvas to draw to; If undefined `MainCanvas` is used
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawCharacter(C, X, Y, Zoom, IsHeightResizeAllowed, DrawCanvas) {
|
|
// Record that the character was drawn this frame
|
|
DrawLastCharacters.push(C);
|
|
|
|
if (!DrawCanvas) DrawCanvas = MainCanvas;
|
|
|
|
var OverrideDark = CurrentModule == "MiniGame" || ((Player.Effect.includes("VRAvatars") && C.Effect.includes("VRAvatars"))) || CurrentScreen == "InformationSheet";
|
|
|
|
if ((C != null) && ((C.ID == 0) || (OverrideDark || Player.GetBlindLevel() < 3 ))) {
|
|
|
|
CharacterCheckHooks(C, CurrentCharacter != null);
|
|
|
|
if (ControllerActive == true) {
|
|
setButton(X + 100, Y + 200);
|
|
}
|
|
|
|
// If there's a fixed image to draw instead of the character
|
|
if (C.FixedImage != null) {
|
|
DrawImageZoomCanvas(C.FixedImage, DrawCanvas, 0, 0, 500, 1000, X, Y, 500 * Zoom, 1000 * Zoom);
|
|
return;
|
|
}
|
|
|
|
// Run any existing asset scripts
|
|
if (C.RunScripts && C.HasScriptedAssets) {
|
|
const DynamicAssets = C.Appearance.filter(CA => CA.Asset.DynamicScriptDraw);
|
|
DynamicAssets.forEach(Item =>
|
|
CommonCallFunctionByNameWarn(`Assets${Item.Asset.Group.Name}${Item.Asset.Name}ScriptDraw`, {
|
|
C, Item, PersistentData: () => AnimationPersistentDataGet(C, Item.Asset)
|
|
})
|
|
);
|
|
|
|
// If we must rebuild the canvas due to an animation
|
|
const refreshTimeKey = AnimationGetDynamicDataName(C, AnimationDataTypes.RefreshTime);
|
|
const refreshRateKey = AnimationGetDynamicDataName(C, AnimationDataTypes.RefreshRate);
|
|
const buildKey = AnimationGetDynamicDataName(C, AnimationDataTypes.Rebuild);
|
|
const lastRefresh = AnimationPersistentStorage[refreshTimeKey] || 0;
|
|
const refreshRate = AnimationPersistentStorage[refreshRateKey] == null ? 60000 : AnimationPersistentStorage[refreshRateKey];
|
|
if (refreshRate + lastRefresh < CommonTime() && AnimationPersistentStorage[buildKey]) {
|
|
CharacterRefresh(C, false);
|
|
AnimationPersistentStorage[buildKey] = false;
|
|
AnimationPersistentStorage[refreshTimeKey] = CommonTime();
|
|
}
|
|
}
|
|
|
|
// There's 2 different canvas, one blinking and one that doesn't
|
|
let Canvas = (Math.round(CurrentTime / 400) % C.BlinkFactor == 0 && !CommonPhotoMode) ? C.CanvasBlink : C.Canvas;
|
|
|
|
// If we must dark the Canvas characters
|
|
if ((C.ID != 0) && Player.IsBlind() && !OverrideDark) {
|
|
const DarkFactor = Math.min(CharacterGetDarkFactor(Player) * 2, 1);
|
|
|
|
CharacterCanvas.globalCompositeOperation = "copy";
|
|
CharacterCanvas.drawImage(Canvas, 0, 0);
|
|
// Overlay black rectangle.
|
|
CharacterCanvas.globalCompositeOperation = "source-atop";
|
|
CharacterCanvas.fillStyle = `rgba(0,0,0,${1.0 - DarkFactor})`;
|
|
CharacterCanvas.fillRect(0, 0, Canvas.width, Canvas.height);
|
|
|
|
Canvas = CharacterCanvas.canvas;
|
|
}
|
|
|
|
// If we must flip the canvas vertically
|
|
const IsInverted = (CurrentScreen != "KinkyDungeon") ? CharacterAppearsInverted(C) : false;
|
|
|
|
// Get the height ratio and X & Y offsets based on it
|
|
const HeightRatio = (IsHeightResizeAllowed == null || IsHeightResizeAllowed == true) ? C.HeightRatio : 1;
|
|
const XOffset = CharacterAppearanceXOffset(C, HeightRatio);
|
|
const YOffset = CharacterAppearanceYOffset(C, HeightRatio);
|
|
|
|
// Calculate the vertical parameters. In certain cases, cut off anything above the Y value.
|
|
const YCutOff = YOffset >= 0 || CurrentScreen == "ChatRoom";
|
|
const YStart = CanvasUpperOverflow + (YCutOff ? -YOffset / HeightRatio : 0);
|
|
const SourceHeight = 1000 / HeightRatio + (YCutOff ? 0 : -YOffset / HeightRatio);
|
|
const DestY = (IsInverted || YCutOff) ? 0 : YOffset;
|
|
|
|
// Draw the character
|
|
DrawImageEx(Canvas, X + XOffset * Zoom, Y + DestY * Zoom, {
|
|
Canvas: DrawCanvas,
|
|
SourcePos: [0, YStart, Canvas.width, SourceHeight],
|
|
Width: 500 * HeightRatio * Zoom,
|
|
Height: (1000 - DestY) * Zoom,
|
|
Invert: IsInverted,
|
|
Mirror: IsInverted
|
|
});
|
|
|
|
// Draw the arousal meter & game images on certain conditions
|
|
if (CurrentScreen != "ChatRoom" || ChatRoomHideIconState <= 1) {
|
|
DrawArousalMeter(C, X, Y, Zoom);
|
|
OnlineGameDrawCharacter(C, X, Y, Zoom);
|
|
if (C.HasHiddenItems) DrawImageZoomCanvas("Screens/Character/Player/HiddenItem.png", DrawCanvas, 0, 0, 86, 86, X + 54 * Zoom, Y + 880 * Zoom, 70 * Zoom, 70 * Zoom);
|
|
}
|
|
|
|
// Draws the character focus zones if we need too
|
|
if ((C.FocusGroup != null) && (C.FocusGroup.Zone != null) && (CurrentScreen != "Preference") && (DialogColor == null)) {
|
|
|
|
// Draw all the possible zones in transparent colors (gray if free, yellow if occupied, red if blocker)
|
|
for (let A = 0; A < AssetGroup.length; A++)
|
|
if (AssetGroup[A].Zone != null && AssetGroup[A].Name != C.FocusGroup.Name) {
|
|
let Color = "#80808040";
|
|
if (InventoryGroupIsBlocked(C, AssetGroup[A].Name)) Color = "#88000580";
|
|
else if (InventoryGet(C, AssetGroup[A].Name) != null) Color = "#D5A30080";
|
|
DrawAssetGroupZone(C, AssetGroup[A].Zone, Zoom, X, Y, HeightRatio, Color, 5);
|
|
}
|
|
|
|
// Draw the focused zone in cyan
|
|
DrawAssetGroupZone(C, C.FocusGroup.Zone, Zoom, X, Y, HeightRatio, "cyan");
|
|
}
|
|
|
|
// Draw the character name below herself
|
|
if ((C.Name != "") && ((CurrentModule == "Room") || (CurrentModule == "Online" && !(CurrentScreen == "ChatRoom" && ChatRoomHideIconState >= 3)) || ((CurrentScreen == "Wardrobe") && (C.ID != 0))) && (CurrentScreen != "Private") && (CurrentScreen != "PrivateRansom"))
|
|
if (!Player.IsBlind() || (Player.GameplaySettings && Player.GameplaySettings.SensDepChatLog == "SensDepLight")) {
|
|
DrawCanvas.font = CommonGetFont(30);
|
|
const NameOffset = CurrentScreen == "ChatRoom" && (ChatRoomCharacter.length > 5 || (ChatRoomCharacter.length == 5 && CommonPhotoMode)) && CurrentCharacter == null ? -4 : 0;
|
|
DrawText(AsylumGGTSCharacterName(C), X + 255 * Zoom, Y + 980 * Zoom + NameOffset, (CommonIsColor(C.LabelColor)) ? C.LabelColor : "White", "Black");
|
|
DrawCanvas.font = CommonGetFont(36);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws an asset group zone outline over the character
|
|
* @param {Character} C - Character for which to draw the zone
|
|
* @param {number[][]} Zone - Zone to be drawn
|
|
* @param {number} Zoom - Height ratio of the character
|
|
* @param {number} X - Position of the character on the X axis
|
|
* @param {number} Y - Position of the character on the Y axis
|
|
* @param {number} HeightRatio - The displayed height ratio of the character
|
|
* @param {string} Color - Color of the zone outline
|
|
* @param {number} [Thickness=3] - Thickness of the outline
|
|
* @param {string} FillColor - If non-empty, the color to fill the rectangle with
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawAssetGroupZone(C, Zone, Zoom, X, Y, HeightRatio, Color, Thickness = 3, FillColor = undefined) {
|
|
for (let Z = 0; Z < Zone.length; Z++) {
|
|
let CZ = DialogGetCharacterZone(C, Zone[Z], X, Y, Zoom, HeightRatio);
|
|
|
|
if (FillColor != null) DrawRect(CZ[0], CZ[1], CZ[2], CZ[3], FillColor);
|
|
DrawEmptyRect(CZ[0], CZ[1], CZ[2], CZ[3], Color, Thickness);
|
|
|
|
if (ControllerActive == true) {
|
|
setButton(Math.round(CZ[0]), Math.round(CZ[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a semi-transparent copy of a canvas
|
|
* @param {HTMLCanvasElement} Canvas - source
|
|
* @param {number} Alpha - transparency between 0-1
|
|
* @returns {HTMLCanvasElement} - result
|
|
*/
|
|
function DrawAlpha(Canvas, Alpha) {
|
|
// If there's nothing to do simply return the original image
|
|
if ((Alpha == null) || (Alpha >= 1.0)) return Canvas;
|
|
// Copy the image to the temp canvas
|
|
TempCanvas.canvas.width = Canvas.width;
|
|
TempCanvas.canvas.height = Canvas.height;
|
|
TempCanvas.globalCompositeOperation = "copy";
|
|
TempCanvas.drawImage(Canvas, 0, 0);
|
|
// Apply the alpha
|
|
TempCanvas.globalCompositeOperation = "destination-in";
|
|
TempCanvas.fillStyle = "rgba(0,0,0," + Alpha + ")";
|
|
TempCanvas.fillRect(0, 0, Canvas.width, Canvas.height);
|
|
return TempCanvas.canvas;
|
|
}
|
|
|
|
/**
|
|
* Draws a zoomed image from a source to a specific canvas
|
|
* @param {string} Source - URL of the image
|
|
* @param {CanvasRenderingContext2D} Canvas - Canvas on which to draw the image
|
|
* @param {number} SX - The X coordinate where to start clipping
|
|
* @param {number} SY - The Y coordinate where to start clipping
|
|
* @param {number} SWidth - The width of the clipped image
|
|
* @param {number} SHeight - The height of the clipped image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {number} Width - Width of the image
|
|
* @param {number} Height - Height of the image
|
|
* @param {boolean} [Invert] - Flips the image vertically
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageZoomCanvas(Source, Canvas, SX, SY, SWidth, SHeight, X, Y, Width, Height, Invert) {
|
|
return DrawImageEx(Source, X, Y, {
|
|
Canvas,
|
|
SourcePos: [SX, SY, SWidth, SHeight],
|
|
Width,
|
|
Height,
|
|
Invert
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Draws a resized image from a source to the main canvas
|
|
* @param {string} Source - URL of the image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {number} Width - Width of the image after being resized
|
|
* @param {number} Height - Height of the image after being resized
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageResize(Source, X, Y, Width, Height) {
|
|
return DrawImageEx(Source, X, Y, { Width, Height });
|
|
}
|
|
|
|
/**
|
|
* Draws a zoomed image from a source to a specific canvas
|
|
* @param {string} Source - URL of the image
|
|
* @param {CanvasRenderingContext2D} Canvas - Canvas on which to draw the image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {number[][]} [AlphaMasks] - A list of alpha masks to apply to the asset
|
|
* @param {number} [Opacity=1] - The opacity at which to draw the image
|
|
* @param {boolean} [Rotate=false] - If the image should be rotated by 180 degrees
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageCanvas(Source, Canvas, X, Y, AlphaMasks, Opacity, Rotate) {
|
|
const Img = DrawGetImage(Source);
|
|
if (!Img.complete) return false;
|
|
if (!Img.naturalWidth) return true;
|
|
/** @type {CanvasImageSource} */
|
|
let SourceImage = Img;
|
|
if ((AlphaMasks && AlphaMasks.length) || Rotate) {
|
|
TempCanvas.canvas.width = Img.width;
|
|
TempCanvas.canvas.height = Img.height;
|
|
if (Rotate) {
|
|
TempCanvas.rotate(Math.PI);
|
|
TempCanvas.translate(-TempCanvas.canvas.width, -TempCanvas.canvas.height);
|
|
X = 500 - (X + TempCanvas.canvas.width);
|
|
Y -= TempCanvas.canvas.height;
|
|
}
|
|
TempCanvas.drawImage(Img, 0, 0);
|
|
if (AlphaMasks && AlphaMasks.length) {
|
|
AlphaMasks.forEach(([x, y, w, h]) => TempCanvas.clearRect(x - X, y - Y, w, h));
|
|
}
|
|
SourceImage = TempCanvas.canvas;
|
|
}
|
|
Opacity = typeof Opacity === "number" ? Opacity : 1;
|
|
Canvas.save();
|
|
Canvas.globalAlpha = Opacity;
|
|
Canvas.drawImage(SourceImage, X, Y);
|
|
Canvas.restore();
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Draws a canvas to a specific canvas
|
|
* @param {HTMLImageElement | HTMLCanvasElement} Img - Canvas to draw
|
|
* @param {CanvasRenderingContext2D} Canvas - Canvas on which to draw the image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {number[][]} AlphaMasks - A list of alpha masks to apply to the asset
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawCanvas(Img, Canvas, X, Y, AlphaMasks) {
|
|
if (AlphaMasks && AlphaMasks.length) {
|
|
TempCanvas.canvas.width = Img.width;
|
|
TempCanvas.canvas.height = Img.height;
|
|
TempCanvas.drawImage(Img, 0, 0);
|
|
AlphaMasks.forEach(([x, y, w, h]) => TempCanvas.clearRect(x - X, y - Y, w, h));
|
|
Canvas.drawImage(TempCanvas.canvas, X, Y);
|
|
} else {
|
|
Canvas.drawImage(Img, X, Y);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Draws a specific canvas with a zoom on the main canvas
|
|
* @param {HTMLImageElement | HTMLCanvasElement} Canvas - Canvas to draw on the main canvas
|
|
* @param {number} X - Position of the canvas on the X axis
|
|
* @param {number} Y - Position of the canvas on the Y axis
|
|
* @param {number} Zoom - Zoom factor
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawCanvasZoom(Canvas, X, Y, Zoom) {
|
|
return DrawImageEx(Canvas, X, Y, { Zoom });
|
|
}
|
|
|
|
/**
|
|
* Draws a zoomed image from a source to the canvas and mirrors it from left to right
|
|
* @param {string} Source - URL of the image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {number} Width - Width of the image
|
|
* @param {number} Height - Height of the image
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageZoomMirror(Source, X, Y, Width, Height) {
|
|
return DrawImageEx(Source, X, Y, {
|
|
Width, Height,
|
|
Mirror: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Draws an image from a source on the main canvas
|
|
* @param {string} Source - URL of the image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {boolean} [Invert] - Flips the image vertically
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImage(Source, X, Y, Invert) {
|
|
return DrawImageEx(Source, X, Y, { Invert });
|
|
}
|
|
|
|
/**
|
|
* Draws an image from a source to the specified canvas
|
|
* @param {string} Source - URL of the image
|
|
* @param {CanvasRenderingContext2D} Canvas - Canvas on which to draw the image
|
|
* @param {number} X - Position of the rectangle on the X axis
|
|
* @param {number} Y - Position of the rectangle on the Y axis
|
|
* @param {number} Zoom - Zoom factor
|
|
* @param {string} HexColor - Color of the image to draw
|
|
* @param {boolean} FullAlpha - Whether or not it is drawn in full alpha mode
|
|
* @param {number[][]} AlphaMasks - A list of alpha masks to apply to the asset
|
|
* @param {number} [Opacity=1] - The opacity at which to draw the image
|
|
* @param {boolean} [Rotate=false] - If the image should be rotated by 180 degrees
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageCanvasColorize(Source, Canvas, X, Y, Zoom, HexColor, FullAlpha, AlphaMasks, Opacity, Rotate) {
|
|
|
|
// Make sure that the starting image is loaded
|
|
const Img = DrawGetImage(Source);
|
|
if (!Img.complete) return false;
|
|
if (!Img.naturalWidth) return true;
|
|
|
|
// Variable initialization
|
|
const width = Img.width;
|
|
const height = Img.height;
|
|
|
|
// Prepares a canvas to draw the colorized image
|
|
ColorCanvas.canvas.width = width;
|
|
ColorCanvas.canvas.height = height;
|
|
ColorCanvas.globalCompositeOperation = "copy";
|
|
ColorCanvas.drawImage(Img, 0, 0);
|
|
|
|
const imageData = ColorCanvas.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
|
|
// Get the RGB color used to transform
|
|
const rgbColor = DrawHexToRGB(HexColor);
|
|
|
|
// We transform each non transparent pixel based on the RGG value
|
|
if (FullAlpha) {
|
|
for (let p = 0, len = data.length; p < len; p += 4) {
|
|
if (data[p + 3] == 0)
|
|
continue;
|
|
const trans = ((data[p] + data[p + 1] + data[p + 2]) / 383);
|
|
data[p + 0] = rgbColor.r * trans;
|
|
data[p + 1] = rgbColor.g * trans;
|
|
data[p + 2] = rgbColor.b * trans;
|
|
}
|
|
} else {
|
|
for (let p = 0, len = data.length; p < len; p += 4) {
|
|
const trans = ((data[p] + data[p + 1] + data[p + 2]) / 383);
|
|
if ((data[p + 3] == 0) || (trans < 0.8) || (trans > 1.2))
|
|
continue;
|
|
data[p + 0] = rgbColor.r * trans;
|
|
data[p + 1] = rgbColor.g * trans;
|
|
data[p + 2] = rgbColor.b * trans;
|
|
}
|
|
}
|
|
|
|
// Replace the source image with the modified canvas
|
|
ColorCanvas.putImageData(imageData, 0, 0);
|
|
if (AlphaMasks && AlphaMasks.length) {
|
|
AlphaMasks.forEach(([x, y, w, h]) => ColorCanvas.clearRect(x - X, y - Y, w, h));
|
|
}
|
|
|
|
// Rotate the image 180 degrees
|
|
if (Rotate) {
|
|
ColorCanvas.rotate(Math.PI);
|
|
ColorCanvas.translate(-ColorCanvas.canvas.width, -ColorCanvas.canvas.height);
|
|
X = 500 - (X + ColorCanvas.canvas.width);
|
|
Y -= ColorCanvas.canvas.height;
|
|
}
|
|
|
|
Opacity = typeof Opacity === "number" ? Opacity : 1;
|
|
Canvas.save();
|
|
Canvas.globalAlpha = Opacity;
|
|
Canvas.drawImage(ColorCanvas.canvas, 0, 0, Img.width, Img.height, X, Y, Img.width * Zoom, Img.height * Zoom);
|
|
Canvas.restore();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Draws the mirrored version of an image from a source on the canvas
|
|
* @param {string} Source - URL of the image
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageMirror(Source, X, Y) {
|
|
return DrawImageEx(Source, X, Y, { Mirror: true });
|
|
}
|
|
|
|
/**
|
|
* Flips an image vertically
|
|
* @param {HTMLImageElement} Img - The image to be inverted
|
|
* @returns {HTMLCanvasElement} - Canvas with the inverted image
|
|
*/
|
|
function DrawImageInvert(Img) {
|
|
TempCanvas.canvas.width = Img.width;
|
|
TempCanvas.canvas.height = Img.height;
|
|
TempCanvas.scale(1, -1);
|
|
TempCanvas.translate(0, -Img.height);
|
|
TempCanvas.drawImage(Img, 0, 0);
|
|
return TempCanvas.canvas;
|
|
}
|
|
|
|
/**
|
|
* Draws an image on canvas, applying all options
|
|
* @param {string | HTMLImageElement | HTMLCanvasElement} Source - URL of image or image itself
|
|
* @param {number} X - Position of the image on the X axis
|
|
* @param {number} Y - Position of the image on the Y axis
|
|
* @param {object} [options] - any extra options, optional
|
|
* @param {CanvasRenderingContext2D} [options.Canvas] - Canvas on which to draw the image, defaults to `MainCanvas`
|
|
* @param {number} [options.Alpha] - transparency between 0-1
|
|
* @param {[number, number, number, number]} [options.SourcePos] - Area in original image to draw in format `[left, top, width, height]`
|
|
* @param {number} [options.Width] - Width of the drawn image, defaults to width of original image
|
|
* @param {number} [options.Height] - Height of the drawn image, defaults to height of original image
|
|
* @param {boolean} [options.Invert=false] - If image should be flipped vertically
|
|
* @param {boolean} [options.Mirror=false] - If image should be flipped horizontally
|
|
* @param {number} [options.Zoom=1] - Zoom factor
|
|
* @returns {boolean} - whether the image was complete or not
|
|
*/
|
|
function DrawImageEx(
|
|
Source,
|
|
X,
|
|
Y,
|
|
{
|
|
Canvas = MainCanvas,
|
|
Alpha = 1,
|
|
SourcePos,
|
|
Width,
|
|
Height,
|
|
Invert = false,
|
|
Mirror = false,
|
|
Zoom = 1
|
|
}
|
|
) {
|
|
if (typeof Source === "string") {
|
|
Source = DrawGetImage(Source);
|
|
if (!Source.complete) return false;
|
|
if (!Source.naturalWidth) return true;
|
|
}
|
|
|
|
const sizeChanged = Width != null || Height != null;
|
|
if (Width == null) {
|
|
Width = SourcePos ? SourcePos[2] : Source.width;
|
|
}
|
|
if (Height == null) {
|
|
Height = SourcePos ? SourcePos[3] : Source.height;
|
|
}
|
|
|
|
Canvas.save();
|
|
|
|
Canvas.globalCompositeOperation = "source-over";
|
|
Canvas.globalAlpha = Alpha;
|
|
Canvas.translate(X, Y);
|
|
|
|
if (Zoom != 1) {
|
|
Canvas.scale(Zoom, Zoom);
|
|
}
|
|
|
|
if (Invert) {
|
|
Canvas.transform(1, 0, 0, -1, 0, Height);
|
|
}
|
|
|
|
if (Mirror) {
|
|
Canvas.transform(-1, 0, 0, 1, Width, 0);
|
|
}
|
|
|
|
if (SourcePos) {
|
|
Canvas.drawImage(Source, SourcePos[0], SourcePos[1], SourcePos[2], SourcePos[3], 0, 0, Width, Height);
|
|
} else if (sizeChanged) {
|
|
Canvas.drawImage(Source, 0, 0, Width, Height);
|
|
} else {
|
|
Canvas.drawImage(Source, 0, 0);
|
|
}
|
|
|
|
Canvas.restore();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reduces the font size progressively until the text fits the wrap size
|
|
* @param {string} Text - Text that will be drawn
|
|
* @param {number} Width - Width in which the text must fit
|
|
* @param {number} MaxLine - Maximum of lines the word can wrap for
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function GetWrapTextSize(Text, Width, MaxLine) {
|
|
|
|
// Don't bother if it fits on one line
|
|
if (MainCanvas.measureText(Text).width <= Width) return;
|
|
|
|
const words = Text.split(' ');
|
|
let line = '';
|
|
|
|
// Find the number of lines
|
|
let LineCount = 1;
|
|
for (let n = 0; n < words.length; n++) {
|
|
const testLine = line + words[n] + ' ';
|
|
if (MainCanvas.measureText(testLine).width > Width && n > 0) {
|
|
line = words[n] + ' ';
|
|
LineCount++;
|
|
} else line = testLine;
|
|
}
|
|
|
|
// If there's too many lines, we launch the function again with size minus 2
|
|
if (LineCount > MaxLine) {
|
|
MainCanvas.font = (parseInt(MainCanvas.font.substring(0, 2)) - 2).toString() + "px arial";
|
|
GetWrapTextSize(Text, Width, MaxLine);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a word wrapped text in a rectangle
|
|
* @param {string} Text - Text to draw
|
|
* @param {number} X - Position of the rectangle on the X axis
|
|
* @param {number} Y - Position of the rectangle on the Y axis
|
|
* @param {number} Width - Width of the rectangle
|
|
* @param {number} Height - Height of the rectangle
|
|
* @param {string} ForeColor - Foreground color
|
|
* @param {string} [BackColor] - Background color
|
|
* @param {number} [MaxLine] - Maximum of lines the word can wrap for
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawTextWrap(Text, X, Y, Width, Height, ForeColor, BackColor, MaxLine) {
|
|
if (ControllerActive == true) {
|
|
setButton(X, Y);
|
|
}
|
|
// Draw the rectangle if we need too
|
|
if (BackColor != null) {
|
|
MainCanvas.beginPath();
|
|
MainCanvas.rect(X, Y, Width, Height);
|
|
MainCanvas.fillStyle = BackColor;
|
|
MainCanvas.fillRect(X, Y, Width, Height);
|
|
MainCanvas.fill();
|
|
MainCanvas.lineWidth = 2;
|
|
MainCanvas.strokeStyle = ForeColor;
|
|
MainCanvas.stroke();
|
|
MainCanvas.closePath();
|
|
}
|
|
if (!Text) return;
|
|
|
|
// Sets the text size if there's a maximum number of lines
|
|
let TextSize;
|
|
if (MaxLine != null) {
|
|
TextSize = MainCanvas.font;
|
|
GetWrapTextSize(Text, Width, MaxLine);
|
|
}
|
|
|
|
// Split the text if it wouldn't fit in the rectangle
|
|
MainCanvas.fillStyle = ForeColor;
|
|
if (MainCanvas.measureText(Text).width > Width) {
|
|
const words = Text.split(' ');
|
|
let line = '';
|
|
|
|
// Find the number of lines
|
|
let LineCount = 1;
|
|
for (let n = 0; n < words.length; n++) {
|
|
const testLine = line + words[n] + ' ';
|
|
if (MainCanvas.measureText(testLine).width > Width && n > 0) {
|
|
line = words[n] + ' ';
|
|
LineCount++;
|
|
} else line = testLine;
|
|
}
|
|
|
|
// Splits the words and draw the text
|
|
line = '';
|
|
Y = Y - ((LineCount - 1) * 23) + (Height / 2);
|
|
for (let n = 0; n < words.length; n++) {
|
|
const testLine = line + words[n] + ' ';
|
|
if (MainCanvas.measureText(testLine).width > Width && n > 0) {
|
|
MainCanvas.fillText(line, X + Width / 2, Y);
|
|
line = words[n] + ' ';
|
|
Y += 46;
|
|
}
|
|
else {
|
|
line = testLine;
|
|
}
|
|
}
|
|
MainCanvas.fillText(line, X + Width / 2, Y);
|
|
|
|
} else MainCanvas.fillText(Text, X + Width / 2, Y + Height / 2);
|
|
|
|
// Resets the font text size
|
|
if ((MaxLine != null) && (TextSize != null))
|
|
MainCanvas.font = TextSize;
|
|
|
|
}
|
|
|
|
/**
|
|
* Draws a text element on the canvas that will fit on the specified width
|
|
* @param {string} Text - Text to draw
|
|
* @param {number} X - Position of the text on the X axis
|
|
* @param {number} Y - Position of the text on the Y axis
|
|
* @param {number} Width - Width in which the text has to fit
|
|
* @param {string} Color - Color of the text
|
|
* @param {string} [BackColor] - Color of the background
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawTextFit(Text, X, Y, Width, Color, BackColor) {
|
|
if (!Text) return;
|
|
|
|
// Get text properties
|
|
let Result = DrawingGetTextSize(Text, Width);
|
|
Text = Result[0];
|
|
MainCanvas.font = CommonGetFont(Result[1].toString());
|
|
|
|
// Draw a back color relief text if needed
|
|
if ((BackColor != null) && (BackColor != "")) {
|
|
MainCanvas.fillStyle = BackColor;
|
|
MainCanvas.fillText(Text, X + 1, Y + 1);
|
|
}
|
|
|
|
// Restores the font size
|
|
MainCanvas.fillStyle = Color;
|
|
MainCanvas.fillText(Text, X, Y);
|
|
MainCanvas.font = CommonGetFont(36);
|
|
}
|
|
|
|
/**
|
|
* Gets the text size needed to fit inside a given width according to the current font.
|
|
* This function is memoized because <code>MainCanvas.measureText(Text)</code> is a major resource hog.
|
|
* @param {string} Text - Text to draw
|
|
* @param {number} Width - Width in which the text has to fit
|
|
* @returns {[string, number]} - Text to draw and its font size
|
|
*/
|
|
const DrawingGetTextSize = CommonMemoize((Text, Width) => {
|
|
// If it doesn't fit, test with smaller and smaller fonts until it fits
|
|
let S;
|
|
for (S = 36; S >= 10; S = S - 2) {
|
|
MainCanvas.font = CommonGetFont(S.toString());
|
|
const metrics = MainCanvas.measureText(Text);
|
|
if (metrics.width <= Width)
|
|
return [Text, S];
|
|
}
|
|
|
|
// Cuts the text if it would go over the box
|
|
while (Text.length > 0) {
|
|
Text = Text.substr(1);
|
|
const metrics = MainCanvas.measureText(Text);
|
|
if (metrics.width <= Width)
|
|
return [Text, S];
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Draws a text element on the canvas
|
|
* @param {string} Text - Text to draw
|
|
* @param {number} X - Position of the text on the X axis
|
|
* @param {number} Y - Position of the text on the Y axis
|
|
* @param {string} Color - Color of the text
|
|
* @param {string} [BackColor] - Color of the background
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawText(Text, X, Y, Color, BackColor) {
|
|
if (!Text) return;
|
|
|
|
// Draw a back color relief text if needed
|
|
if ((BackColor != null) && (BackColor != "")) {
|
|
MainCanvas.fillStyle = BackColor;
|
|
MainCanvas.fillText(Text, X + 1, Y + 1);
|
|
}
|
|
|
|
// Split the text on two lines if there's a |
|
|
MainCanvas.fillStyle = Color;
|
|
MainCanvas.fillText(Text, X, Y);
|
|
|
|
}
|
|
|
|
/**
|
|
* Draws a button component
|
|
* @param {number} Left - Position of the component from the left of the canvas
|
|
* @param {number} Top - Position of the component from the top of the canvas
|
|
* @param {number} Width - Width of the component
|
|
* @param {number} Height - Height of the component
|
|
* @param {string} Label - Text to display in the button
|
|
* @param {string} BackgroundColor - Color of the component
|
|
* @param {string} [Image] - URL of the image to draw inside the button, if applicable
|
|
* @param {string} [HoveringText] - Text of the tooltip, if applicable
|
|
* @param {boolean} [Disabled] - Disables the hovering options if set to true
|
|
* @param {string} [TextColor] - Foreground Color of the component
|
|
* @param {string} [HoverColor] - Hovered state color of the component
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawButton(Left, Top, Width, Height, Label, BackgroundColor, Image, HoveringText, Disabled, TextColor, HoverColor) {
|
|
|
|
if (ControllerActive == true) {
|
|
setButton(Left, Top);
|
|
}
|
|
|
|
const foregroundColor = TextColor || "black";
|
|
const accentColor = HoverColor || "Cyan";
|
|
const isHovered = (MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height);
|
|
const hoverEnabled = HoveringText != null;
|
|
// Draw the button rectangle (makes the background color cyan if the mouse is over it)
|
|
MainCanvas.beginPath();
|
|
MainCanvas.rect(Left, Top, Width, Height);
|
|
MainCanvas.fillStyle = (isHovered && hoverEnabled && !CommonIsMobile && !Disabled) ? accentColor : BackgroundColor;
|
|
MainCanvas.fillRect(Left, Top, Width, Height);
|
|
MainCanvas.fill();
|
|
MainCanvas.lineWidth = 2;
|
|
MainCanvas.strokeStyle = foregroundColor;
|
|
MainCanvas.stroke();
|
|
MainCanvas.closePath();
|
|
|
|
// Draw the text or image
|
|
DrawTextFit(Label, Left + Width / 2, Top + (Height / 2) + 1, Width - 4, foregroundColor);
|
|
if ((Image != null) && (Image != "")) DrawImage(Image, Left + 2, Top + 2);
|
|
|
|
// Draw the hovering text
|
|
if (hoverEnabled && isHovered && !CommonIsMobile) {
|
|
DrawHoverElements.push(() => DrawButtonHover(Left, Top, Width, Height, HoveringText));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a checkbox component
|
|
* @param {number} Left - Position of the component from the left of the canvas
|
|
* @param {number} Top - Position of the component from the top of the canvas
|
|
* @param {number} Width - Width of the component
|
|
* @param {number} Height - Height of the component
|
|
* @param {string} Text - Label associated with the checkbox
|
|
* @param {boolean} IsChecked - Whether or not the checkbox is checked
|
|
* @param {boolean} [Disabled] - Disables the hovering options if set to true
|
|
* @param {string} [TextColor] - Color of the text
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawCheckbox(Left, Top, Width, Height, Text, IsChecked, Disabled = false, TextColor = "Black", CheckImage = "Icons/Checked.png") {
|
|
DrawText(Text, Left + 100, Top + 33, TextColor, "Gray");
|
|
DrawButton(Left, Top, Width, Height, "", Disabled ? "#ebebe4" : "White", IsChecked ? CheckImage : "", null, Disabled);
|
|
}
|
|
|
|
/**
|
|
* Draw a back & next button component
|
|
* @param {number} Left - Position of the component from the left of the canvas
|
|
* @param {number} Top - Position of the component from the top of the canvas
|
|
* @param {number} Width - Width of the component
|
|
* @param {number} Height - Height of the component
|
|
* @param {string} Label - Text inside the component
|
|
* @param {string} Color - Color of the component
|
|
* @param {string} [Image] - Image URL to draw in the component
|
|
* @param {() => string} [BackText] - Text for the back button tooltip
|
|
* @param {() => string} [NextText] - Text for the next button tooltip
|
|
* @param {boolean} [Disabled] - Disables the hovering options if set to true
|
|
* @param {number} [ArrowWidth] - How much of the button the previous/next sections cover. By default, half each.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawBackNextButton(Left, Top, Width, Height, Label, Color, Image, BackText, NextText, Disabled, ArrowWidth) {
|
|
// Set the widths of the previous/next sections to be colored cyan when hovering over them
|
|
// By default each covers half the width, together covering the whole button
|
|
if (ArrowWidth == null || ArrowWidth > Width / 2) ArrowWidth = Width / 2;
|
|
const LeftSplit = Left + ArrowWidth;
|
|
const RightSplit = Left + Width - ArrowWidth;
|
|
|
|
if (ControllerActive == true) {
|
|
setButton(Left, Top);
|
|
setButton(Left + Width - ArrowWidth, Top);
|
|
}
|
|
|
|
// Draw the button rectangle
|
|
MainCanvas.beginPath();
|
|
MainCanvas.rect(Left, Top, Width, Height);
|
|
MainCanvas.fillStyle = Color;
|
|
MainCanvas.fillRect(Left, Top, Width, Height);
|
|
if (MouseIn(Left, Top, Width, Height) && !CommonIsMobile && !Disabled) {
|
|
MainCanvas.fillStyle = "Cyan";
|
|
if (MouseX > RightSplit) {
|
|
MainCanvas.fillRect(RightSplit, Top, ArrowWidth, Height);
|
|
}
|
|
else if (MouseX <= LeftSplit) {
|
|
MainCanvas.fillRect(Left, Top, ArrowWidth, Height);
|
|
} else {
|
|
MainCanvas.fillRect(Left + ArrowWidth, Top, Width - ArrowWidth * 2, Height);
|
|
}
|
|
}
|
|
else if (CommonIsMobile && ArrowWidth < Width / 2 && !Disabled) {
|
|
// Fill in the arrow regions on mobile
|
|
MainCanvas.fillStyle = "lightgrey";
|
|
MainCanvas.fillRect(Left, Top, ArrowWidth, Height);
|
|
MainCanvas.fillRect(RightSplit, Top, ArrowWidth, Height);
|
|
}
|
|
MainCanvas.lineWidth = 2;
|
|
MainCanvas.strokeStyle = 'black';
|
|
MainCanvas.stroke();
|
|
MainCanvas.closePath();
|
|
|
|
// Draw the text or image
|
|
DrawTextFit(Label, Left + Width / 2, Top + (Height / 2) + 1, (CommonIsMobile) ? Width - 6 : Width - 36, "Black");
|
|
if ((Image != null) && (Image != "")) DrawImage(Image, Left + 2, Top + 2);
|
|
if (ControllerActive == true) {
|
|
setButton(Left + Width / 2, Top);
|
|
}
|
|
|
|
// Draw the back arrow
|
|
MainCanvas.beginPath();
|
|
MainCanvas.fillStyle = "black";
|
|
MainCanvas.moveTo(Left + 15, Top + Height / 5);
|
|
MainCanvas.lineTo(Left + 5, Top + Height / 2);
|
|
MainCanvas.lineTo(Left + 15, Top + Height - Height / 5);
|
|
MainCanvas.stroke();
|
|
MainCanvas.closePath();
|
|
|
|
// Draw the next arrow
|
|
MainCanvas.beginPath();
|
|
MainCanvas.fillStyle = "black";
|
|
MainCanvas.moveTo(Left + Width - 15, Top + Height / 5);
|
|
MainCanvas.lineTo(Left + Width - 5, Top + Height / 2);
|
|
MainCanvas.lineTo(Left + Width - 15, Top + Height - Height / 5);
|
|
MainCanvas.stroke();
|
|
MainCanvas.closePath();
|
|
|
|
// Draw the hovering text on the PC
|
|
if (CommonIsMobile) return;
|
|
if (BackText == null) BackText = () => "MISSING VALUE FOR: BACK TEXT";
|
|
if (NextText == null) NextText = () => "MISSING VALUE FOR: NEXT TEXT";
|
|
if ((MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height) && !Disabled)
|
|
DrawHoverElements.push(() => { DrawButtonHover(Left, Top, Width, Height, MouseX < LeftSplit ? BackText() : MouseX >= RightSplit ? NextText() : "") });
|
|
|
|
}
|
|
|
|
/**
|
|
* Draw the hovering text tooltip
|
|
* @param {number} Left - Position of the tooltip from the left of the canvas
|
|
* @param {number} Top - Position of the tooltip from the top of the canvas
|
|
* @param {number} Width - Width of the tooltip
|
|
* @param {number} Height - Height of the tooltip
|
|
* @param {string} HoveringText - Text to display in the tooltip
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawButtonHover(Left, Top, Width, Height, HoveringText) {
|
|
if ((HoveringText != null) && (HoveringText != "")) {
|
|
Left = (MouseX > 1000) ? Left - 475 : Left + Width + 25;
|
|
Top = Top + (Height - 65) / 2;
|
|
MainCanvas.beginPath();
|
|
MainCanvas.rect(Left, Top, 450, 65);
|
|
MainCanvas.fillStyle = "#FFFF88";
|
|
MainCanvas.fillRect(Left, Top, 450, 65);
|
|
MainCanvas.fill();
|
|
MainCanvas.lineWidth = 2;
|
|
MainCanvas.strokeStyle = 'black';
|
|
MainCanvas.stroke();
|
|
MainCanvas.closePath();
|
|
DrawTextFit(HoveringText, Left + 225, Top + 33, 444, "black");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a basic empty rectangle with a colored outline
|
|
* @param {number} Left - Position of the rectangle from the left of the canvas
|
|
* @param {number} Top - Position of the rectangle from the top of the canvas
|
|
* @param {number} Width - Width of the rectangle
|
|
* @param {number} Height - Height of the rectangle
|
|
* @param {string} Color - Color of the rectangle outline
|
|
* @param {number} [Thickness=3] - Thickness of the rectangle line
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawEmptyRect(Left, Top, Width, Height, Color, Thickness = 3) {
|
|
MainCanvas.beginPath();
|
|
MainCanvas.rect(Left, Top, Width, Height);
|
|
MainCanvas.lineWidth = Thickness;
|
|
MainCanvas.strokeStyle = Color;
|
|
MainCanvas.stroke();
|
|
}
|
|
|
|
/**
|
|
* Draws a basic rectangle filled with a given color
|
|
* @param {number} Left - Position of the rectangle from the left of the canvas
|
|
* @param {number} Top - Position of the rectangle from the top of the canvas
|
|
* @param {number} Width - Width of the rectangle
|
|
* @param {number} Height - Height of the rectangle
|
|
* @param {string} Color - Color of the rectangle
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawRect(Left, Top, Width, Height, Color) {
|
|
MainCanvas.beginPath();
|
|
MainCanvas.fillStyle = Color;
|
|
MainCanvas.fillRect(Left, Top, Width, Height);
|
|
MainCanvas.fill();
|
|
}
|
|
|
|
/**
|
|
* Draws a basic circle
|
|
* @param {number} CenterX - Position of the center of the circle on the X axis
|
|
* @param {number} CenterY - Position of the center of the circle on the Y axis
|
|
* @param {number} Radius - Radius of the circle to draw
|
|
* @param {number} LineWidth - Width of the line
|
|
* @param {string} LineColor - Color of the circle's line
|
|
* @param {string} [FillColor] - Color of the space inside the circle
|
|
* @param {CanvasRenderingContext2D} [Canvas] - The canvas element to draw onto, defaults to MainCanvas
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawCircle(CenterX, CenterY, Radius, LineWidth, LineColor, FillColor, Canvas) {
|
|
if (!Canvas) Canvas = MainCanvas;
|
|
Canvas.beginPath();
|
|
Canvas.arc(CenterX, CenterY, Radius, 0, 2 * Math.PI, false);
|
|
if (FillColor) {
|
|
Canvas.fillStyle = FillColor;
|
|
Canvas.fill();
|
|
}
|
|
Canvas.lineWidth = LineWidth;
|
|
Canvas.strokeStyle = LineColor;
|
|
Canvas.stroke();
|
|
}
|
|
|
|
/**
|
|
* Draws a progress bar with color
|
|
* @param {number} x - Position of the bar on the X axis
|
|
* @param {number} y - Position of the bar on the Y axis
|
|
* @param {number} w - Width of the bar
|
|
* @param {number} h - Height of the bar
|
|
* @param {number} value - Current progress to display on the bar
|
|
* @param {string} [foreground="#66FF66"] - Color of the first part of the bar
|
|
* @param {string} [background="red"] - Color of the bar background
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawProgressBar(x, y, w, h, value, foreground = "#66FF66", background = "red") {
|
|
if (value < 0) value = 0;
|
|
if (value > 100) value = 100;
|
|
DrawRect(x, y, w, h, "white");
|
|
DrawRect(x + 2, y + 2, Math.floor((w - 4) * value / 100), h - 4, foreground);
|
|
DrawRect(Math.floor(x + 2 + (w - 4) * value / 100), y + 2, Math.floor((w - 4) * (100 - value) / 100), h - 4, background);
|
|
}
|
|
|
|
/**
|
|
* Draws two lines, from one point to a second point then to a third point
|
|
* @param {number} x0 - X co-ordinate of starting point
|
|
* @param {number} y0 - Y co-ordinate of starting point
|
|
* @param {number} x1 - X co-ordinate of mid point
|
|
* @param {number} y1 - Y co-ordinate of mid point
|
|
* @param {number} x2 - X co-ordinate of end point
|
|
* @param {number} y2 - Y co-ordinate of end point
|
|
* @param {number} lineWidth - The width of the lines
|
|
* @param {string} color - The color of the lines
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawLineCorner(x0, y0, x1, y1, x2, y2, lineWidth = 2, color = "black") {
|
|
MainCanvas.beginPath();
|
|
MainCanvas.lineWidth = lineWidth;
|
|
MainCanvas.moveTo(x0, y0);
|
|
MainCanvas.lineTo(x1, y1);
|
|
MainCanvas.lineTo(x2, y2);
|
|
MainCanvas.strokeStyle = color;
|
|
MainCanvas.stroke();
|
|
}
|
|
|
|
/**
|
|
* Gets the player's custom background based on type
|
|
* @returns {string} - Custom background if applicable, otherwise ""
|
|
*/
|
|
function DrawGetCustomBackground() {
|
|
const blindfold = InventoryGet(Player, "ItemHead");
|
|
const hood = InventoryGet(Player, "ItemHood");
|
|
let customBG = "";
|
|
|
|
if (blindfold && blindfold.Property && blindfold.Property.CustomBlindBackground) {
|
|
customBG = blindfold.Property.CustomBlindBackground;
|
|
} else if (hood && hood.Property && hood.Property.CustomBlindBackground) {
|
|
customBG = hood.Property.CustomBlindBackground;
|
|
}
|
|
|
|
return customBG;
|
|
}
|
|
|
|
function DrawBlindFlash(intensity) {
|
|
DrawingBlindFlashTimer = CurrentTime + 2000 * intensity;
|
|
BlindFlash = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Constantly looping draw process. Draws beeps, handles the screen size, handles the current blindfold state and draws the current screen.
|
|
* @param {number} time - The current time for frame
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawProcess(time) {
|
|
// Clear the list of characters that were drawn last frame
|
|
DrawLastCharacters = [];
|
|
|
|
// Gets the current screen background and draw it, it becomes darker in dialog mode or if the character is blindfolded
|
|
let B = window[CurrentScreen + "Background"];
|
|
|
|
if ((B != null) && (B != "")) {
|
|
let DarkFactor = 1.0;
|
|
if ((CurrentModule != "Character") && (B != "Sheet")) {
|
|
DarkFactor = CharacterGetDarkFactor(Player) * CurrentDarkFactor;
|
|
if (DarkFactor == 1 && (CurrentCharacter != null || ShopStarted) && !CommonPhotoMode) DarkFactor = 0.5;
|
|
}
|
|
const Invert = Player.GraphicsSettings && Player.GraphicsSettings.InvertRoom && Player.IsInverted();
|
|
|
|
let customBG = DrawGetCustomBackground();
|
|
|
|
if (customBG != "" && (CurrentModule != "Character") && (B != "Sheet")) {
|
|
B = customBG;
|
|
if (DarkFactor == 0)
|
|
DarkFactor = CharacterGetDarkFactor(Player, true);
|
|
}
|
|
|
|
if (DarkFactor > 0.0) {
|
|
if (!DrawImage("Backgrounds/" + B + ".jpg", 0, 0, Invert)) {
|
|
// Draw empty background to overdraw old content if background image isn't ready
|
|
DrawRect(0, 0, 2000, 1000, "#000");
|
|
}
|
|
}
|
|
if (DarkFactor < 1.0) DrawRect(0, 0, 2000, 1000, "rgba(0,0,0," + (1.0 - DarkFactor) + ")");
|
|
}
|
|
|
|
// Draws the dialog screen or current screen if there's no loaded character
|
|
if (CurrentCharacter != null) DialogDraw();
|
|
else CurrentScreenFunctions.Run(time);
|
|
|
|
// Draw Hovering text so they can be above everything else
|
|
DrawProcessHoverElements();
|
|
|
|
// Draws beep from online player sent by the server
|
|
ServerDrawBeep();
|
|
|
|
|
|
// Checks for screen resize/position change and calls appropriate function
|
|
const newCanvasPosition = [MainCanvas.canvas.offsetLeft, MainCanvas.canvas.offsetTop, MainCanvas.canvas.clientWidth, MainCanvas.canvas.clientHeight];
|
|
if (!CommonArraysEqual(newCanvasPosition, DrawCanvasPosition)) {
|
|
DrawCanvasPosition = newCanvasPosition;
|
|
if (CurrentScreenFunctions.Resize) {
|
|
CurrentScreenFunctions.Resize(false);
|
|
}
|
|
}
|
|
|
|
// Leave dialogs AFTER drawing everything
|
|
// If needed
|
|
// Used to support items that remove you from the dialog during the draw phase
|
|
if (DialogLeaveDueToItem) {
|
|
DialogLeaveDueToItem = false;
|
|
DialogLeave();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Draws every element that is considered a "hover" element such has button tooltips.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawProcessHoverElements() {
|
|
for (let E = 0; E < DrawHoverElements.length; E++)
|
|
if (typeof DrawHoverElements[0] === "function")
|
|
(DrawHoverElements.shift())();
|
|
}
|
|
|
|
/**
|
|
* Draws an asset's preview box
|
|
* @param {number} X - Position of the preview box on the X axis
|
|
* @param {number} Y - Position of the preview box on the Y axis
|
|
* @param {Asset} A - The asset to draw the preview for
|
|
* @param {object} [Options] - Additional optional drawing options
|
|
* @param {Character} [Options.C] - The character using the item (used to calculate dynamic item descriptions/previews)
|
|
* @param {string} [Options.Description] - The preview box description
|
|
* @param {string} [Options.Background] - The background color to draw the preview box in - defaults to white
|
|
* @param {string} [Options.Foreground] - The foreground (text) color to draw the description in - defaults to black
|
|
* @param {boolean} [Options.Vibrating] - Whether or not to add vibration effects to the item - defaults to false
|
|
* @param {boolean} [Options.Border] - Whether or not to draw a border around the preview box
|
|
* @param {boolean} [Options.Hover] - Whether or not the button should enable hover behavior (background color change)
|
|
* @param {string} [Options.HoverBackground] - The background color that should be used on mouse hover, if any
|
|
* @param {boolean} [Options.Disabled] - Whether or not the element is disabled (prevents hover functionality)
|
|
* @param {string[]} [Options.Icons] - A list of small icons to display in the top-left corner
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawAssetPreview(X, Y, A, Options) {
|
|
let { C, Description, Background, Foreground, Vibrating, Border, Hover, HoverBackground, Disabled, Icons} = (Options || {});
|
|
const DynamicPreviewImage = C ? A.DynamicPreviewImage(C) : "";
|
|
const Path = `${AssetGetPreviewPath(A)}/${A.Name}${DynamicPreviewImage}.png`;
|
|
if (Description == null) Description = C ? A.DynamicDescription(C) : A.Description;
|
|
DrawPreviewBox(X, Y, Path, Description, { Background, Foreground, Vibrating, Border, Hover,
|
|
HoverBackground, Disabled, Icons });
|
|
}
|
|
|
|
/**
|
|
* Draws an item preview box for the provided image path
|
|
* @param {number} X - Position of the preview box on the X axis
|
|
* @param {number} Y - Position of the preview box on the Y axis
|
|
* @param {string} Path - The path of the image to draw
|
|
* @param {string} Description - The preview box description
|
|
* @param {object} [Options] - Additional optional drawing options
|
|
* @param {string} [Options.Background] - The background color to draw the preview box in - defaults to white
|
|
* @param {string} [Options.Foreground] - The foreground (text) color to draw the description in - defaults to black
|
|
* @param {boolean} [Options.Vibrating] - Whether or not to add vibration effects to the item - defaults to false
|
|
* @param {boolean} [Options.Border] - Whether or not to draw a border around the preview box
|
|
* @param {boolean} [Options.Hover] - Whether or not the button should enable hover behavior (background color change)
|
|
* @param {string} [Options.HoverBackground] - The background color that should be used on mouse hover, if any
|
|
* @param {boolean} [Options.Disabled] - Whether or not the element is disabled (prevents hover functionality)
|
|
* @param {string[]} [Options.Icons] - A list of images to draw in the top-left of the preview box
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawPreviewBox(X, Y, Path, Description, Options) {
|
|
let {Background, Foreground, Vibrating, Border, Hover, HoverBackground, Disabled, Icons} = (Options || {});
|
|
const Height = Description ? 275 : 225;
|
|
Background = Background || "#fff";
|
|
Foreground = Foreground || "#000";
|
|
if (Disabled === true) Background = "#888";
|
|
else if (Hover && MouseHovering(X, Y, 225, Height)) Background = (HoverBackground || "cyan");
|
|
DrawRect(X, Y, 225, Height, Background);
|
|
setButton(X, Y);
|
|
if (Border) DrawEmptyRect(X, Y, 225, Height, Foreground);
|
|
const ImageX = Vibrating ? X + 1 + Math.floor(Math.random() * 3) : X + 2;
|
|
const ImageY = Vibrating ? Y + 1 + Math.floor(Math.random() * 3) : Y + 2;
|
|
if (Path !== "") DrawImageResize(Path, ImageX, ImageY, 221, 221);
|
|
DrawPreviewIcons(Icons, X, Y);
|
|
if (Description) DrawTextFit(Description, X + 110, Y + 250, 221, Foreground);
|
|
}
|
|
|
|
/**
|
|
* Draws a list of small icons over a preview box
|
|
* @param {string[]} icons - An array of icon names
|
|
* @param {number} X - The X co-ordinate to start drawing from
|
|
* @param {number} Y - The Y co-ordinate to start drawing from
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawPreviewIcons(icons, X, Y) {
|
|
if (icons && icons.length) {
|
|
const iconsPerCol = 4;
|
|
const iconSize = 50;
|
|
icons.forEach((icon, index) => {
|
|
const iconX = X + (iconSize + 5) * Math.floor(index / iconsPerCol);
|
|
const iconY = Y + (iconSize + 5) * (index % iconsPerCol);
|
|
DrawImageResize(`Icons/Previews/${icon}.png`, iconX, iconY, iconSize, iconSize);
|
|
if (MouseIn(iconX, iconY, iconSize * 0.9, iconSize * 0.9)) {
|
|
DrawHoverElements.push(() => DrawButtonHover(iconX, iconY, 100, 65, DialogFindPlayer("PreviewIcon" + icon)));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws an item preview box using the provided canvas
|
|
* @param {number} X - Position of the preview box on the X axis
|
|
* @param {number} Y - Position of the preview box on the Y axis
|
|
* @param {HTMLCanvasElement} Canvas - The canvas element containing the image to draw
|
|
* @param {string} Description - The preview box description
|
|
* @param {object} Options - Additional optional drawing options
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DrawCanvasPreview(X, Y, Canvas, Description, Options) {
|
|
DrawPreviewBox(X, Y, "", Description, Options);
|
|
MainCanvas.drawImage(Canvas, X + 2, Y + 2, 221, 221);
|
|
}
|
|
|
|
/**
|
|
* Returns a rectangular subsection of a canvas
|
|
* @param {HTMLCanvasElement} Canvas - The source canvas to take a section of
|
|
* @param {number} Left - The starting X co-ordinate of the section
|
|
* @param {number} Top - The starting Y co-ordinate of the section
|
|
* @param {number} Width - The width of the section to take
|
|
* @param {number} Height - The height of the section to take
|
|
* @returns {HTMLCanvasElement} - The new canvas containing the section
|
|
*/
|
|
function DrawCanvasSegment(Canvas, Left, Top, Width, Height) {
|
|
TempCanvas.canvas.width = Width;
|
|
TempCanvas.canvas.height = Height;
|
|
TempCanvas.clearRect(0, 0, Width, Height);
|
|
TempCanvas.drawImage(Canvas, Left, Top, Width, Height, 0, 0, Width, Height);
|
|
return TempCanvas.canvas;
|
|
}
|
|
|
|
/**
|
|
* Returns a rectangular subsection of the character image
|
|
* @param {Character} C - The character to copy part of
|
|
* @param {number} Left - The starting X co-ordinate of the section
|
|
* @param {number} Top - The starting Y co-ordinate of the section
|
|
* @param {number} Width - The width of the section to take
|
|
* @param {number} Height - The height of the section to take
|
|
* @returns {HTMLCanvasElement} - The new canvas containing the section
|
|
*/
|
|
function DrawCharacterSegment(C, Left, Top, Width, Height) {
|
|
return DrawCanvasSegment(C.Canvas, Left, Top + CanvasUpperOverflow, Width, Height);
|
|
}
|