// Online Level NPC Editor // By fp4 (fowlplay4/Jerret) // Note: // (npcserver) requires rw access to the levels // in order to update NPC changes. // // I.e: rw levels/*.nw // // Clientside Configuration is down below. function onCreated() { // Serverside Folder Configuration this.levelfolders = { "levels/%s", "levels/insides/%s", "levels/overworld/%s", "levels/events/%s", "levels/instances/%s" }; this.removenonexistantimages = false; } function onActionServerSide() { if (params[0] == "loadlevel") { // Find Level temp.file = getfile(params[1]); // Check if Level Found if (temp.file == NULL) { player.triggerclient("weapon", this.name, "msg", format("Could not find the file %s for this level! Check Configuration.", params[1])); return; } // Check if Player Has Access to Level if (!player.hasright("r", temp.file)) { player.triggerclient("weapon", this.name, "msg", "You don't have access to this level!"); return; } // Get Level Modification Time temp.modtime = getfilemodtime(temp.file); // Check if Level Locked for (temp.pl: players) { temp.lock = temp.pl.client.olne_lock; if (temp.lock[0] == temp.file && temp.lock[1] == temp.modtime) { player.triggerclient("weapon", this.name, "msg", format("%s is currently editing this level's NPCs!", (temp.pl.communityname ? temp.pl.communityname : temp.pl.account))); return; } } // Set Level Lock Flag client.olne_lock = {temp.file, temp.modtime}; // Check if Readonly Access temp.readonly = !player.hasright("w", temp.file); // Load File temp.lines.loadlines(temp.file); // Parse NPC Sections of File for (temp.line: temp.lines) { if (temp.reading) { if (temp.line == "NPCEND") { temp.leveldata.add({ temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script }); temp.reading = false; } else { temp.npc_script.add(temp.line); } } else { if (temp.line.starts("NPC ")) { temp.tokens = temp.line.tokenize(); if (temp.tokens.size() == 4) { temp.npc_img = temp.tokens[1]; temp.npc_x = temp.tokens[2]; temp.npc_y = temp.tokens[3]; temp.npc_script = {}; temp.reading = true; } } } } // Send Data to Client player.triggerclient("weapon", this.name, "leveldata", temp.leveldata, temp.readonly); } else if (params[0] == "savelevel") { // Find Level temp.file = getfile(params[1]); // Check for Write Access to File if (!player.hasright("w", temp.file)) { player.triggerclient("weapon", this.name, "msg", "You can't make changes to this level!"); return; } // Load Level temp.lines.loadlines(temp.file); temp.newlines = {}; // Parse Level Ignore NPC Sections for (temp.line: temp.lines) { if (temp.skipping) { if (temp.line == "NPCEND") { temp.skipping = false; } } else { if (temp.line.starts("NPC ")) { temp.skipping = true; } else { temp.newlines.add(temp.line); } } } // Parse NPC Data temp.npcdata = params[2]; for (temp.data: temp.npcdata) { temp.npc_img = temp.data[0]; temp.npc_x = temp.data[1]; temp.npc_y = temp.data[2]; temp.npc_script = temp.data[3]; if ((this.removenonexistantimages && !fileexists(temp.npc_img)) || temp.npc_img.trim() == "") { temp.npc_img = "-"; } // Add NPC Sections to Level temp.newlines.add(format("NPC %s %s %s", temp.npc_img, temp.npc_x, temp.npc_y)); if (temp.npc_script != NULL) { temp.newlines.addarray(temp.npc_script); } temp.newlines.add("NPCEND"); } // Save Level temp.newlines.savelines(temp.file, 0); // Log savelog2("olne_log.txt", format("%s updated %s using OLNE!", player.account, temp.file)); // Unlock Level client.olne_lock = ""; // Send Message player.triggerclient("weapon", this.name, "msg", "NPCs Saved!"); } else if (params[0] == "resetlock") { client.olne_lock = ""; } } function getfile(filename) { for (temp.folder: this.levelfolders) { temp.path = format(temp.folder, filename); if (fileexists(temp.path)) { return temp.path; } } return NULL; } //#CLIENTSIDE // Clientside Configuration const PLACEHOLDER = "npc_placeholder.png"; const SHOWIMGPART = true; // Detects First ShowImgPart Usage (Even if commented out) const SHOWCHARS = true; // Displays Gani Character for NPCs const RIGHTMENUTIME = 0.3; const GRAALMINIMUM = 6; function onPlayerEnters() { // Wait for NPCs to Load waitfor(this, "RandomEvent", 0.05); // Hide Images hideimgs(200,20008); // Reset Variables this.updatedscripts = false; this.active = false; // Unhide NPCs visibleNPCs(true); // Reset Lock if (client.olne_lock != "") { triggerserver("gui", this.name, "resetlock"); } } function GraalControl.onKeyUp(key) { // Check if F9 Pressed if (key == VK_F9) { if (!this.active) { if (graalversion < GRAALMINIMUM) { displayMSG("You need to be using Graal" @ GRAALMINIMUM); return; } // Load Level displayMSG("Loading NPCs..."); triggerserver("gui", this.name, "loadlevel", player.level.name); } else { // Check if Update Required if (this.updatedscripts) { displayMSG("Saving NPCs..."); triggerserver("gui", this.name, "savelevel", player.level.name, getNPCData()); this.updatedscripts = false; } else { triggerserver("gui", this.name, "resetlock"); } // Unhide NPCs and Hide Images visibleNPCs(true); hideimgs(200,10000); this.active = false; } } // Check if Delete Pressed else if (key == VK_DELETE) { // Check if has selection if (this.active && this.selected > 0) { // Delete selected NPC deleteSelection(); } } } // Displays Message // Fades Message After 3 Seconds function displayMSG(msg) { with (findimg(20000)) { x = screenwidth / 2; y = screenheight - 32; text = msg; style = "bc"; textshadow = true; shadowcolor = "0 0 0"; zoom = 1; alpha = 1; layer = 4; } this.scheduleevent(3, "HideMSG", ""); } // Fades out Message function onHideMSG() { while (findimg(20000).alpha > 0) { findimg(20000).alpha -= 0.1; waitfor(this, "RandomEvent", 0.05); } hideimg(20000); } function onActionClientSide() { // Check for Level Data if (params[0] == "leveldata") { // Hide NPCs visibleNPCs(false); // Reset Images hideimgs(200,10000); // Initialize Variables temp.leveldata = params[1]; temp.i = 0; this.levelnpcs = {}; // Determine Top-Left Corner of Level if (player.gmap != NULL) { temp.tx = player.x - (player.x % 64); temp.ty = player.y - (player.y % 64); } else { temp.tx = temp.ty = 0; } // Parse NPC Data echo(temp.leveldata.size()); for (temp.data: temp.leveldata) { // Determine ID and Other Variables temp.npc_id = 200 + temp.i; temp.npc_img = temp.data[0]; temp.npc_x = temp.data[1]; temp.npc_y = temp.data[2]; temp.npc_script = temp.data[3]; // Draw NPC updateNPCImage(temp.npc_id, temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script); // Add to Level NPCs Array this.levelnpcs.add(temp.npc_id); // Increment Counter temp.i++; echo(temp.npc_script.length()); echo(temp.i); } echo("hidemsg"); // Stop Hide Message Timer this.cancelevents("HideMSG"); // Check if Read-only Access if (params[2]) { // Warn Read-only displayMSG("You don't have rights to change this level!"); } else { // Hide Loading Message this.trigger("HideMSG", ""); } // Toggle Active Flag this.active = true; setTimer(0.05); } // Check if Displaying Message else if (params[0] == "msg") { // Disable Fading this.cancelevents("HideMSG"); // Display Message displayMSG(params[1]); } } function updateNPCImage(temp.npc_id, temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script) { // Determine Top-Left Corner of Level if (player.gmap != NULL) { temp.tx = player.x - (player.x % 64); temp.ty = player.y - (player.y % 64); } else { temp.tx = temp.ty = 0; } with(findimg(temp.npc_id)) { x = temp.tx + temp.npc_x; y = temp.ty + temp.npc_y; // Check if showimgpart NPCs Enabled if (temp.npc_script.lower().pos("setimgpart ") >= 0 || temp.npc_script.lower().pos("setimgpart(") >= 0 || temp.npc_script.lower().pos("setgifpart ") >= 0) { // Check if contains showimgpart if (SHOWIMGPART) { // Isolate and Determine Parameters temp.str = temp.npc_script.substring(temp.npc_script.pos("setimgpart")); temp.str = temp.str.substring(0, temp.str.pos(";")); temp.str = temp.str.substring("setimgpart ".length()); temp.tokens = temp.str.tokenize(","); if (temp.tokens.size() == 6) temp.tokens.delete(0); if (temp.tokens.size() == 5) { image = temp.tokens[0].trim(); // Remove Quotes and Braces while (image.starts("(")) image = image.substring(1); while (image.starts('"')) image = image.substring(1); while (image.ends('"')) image = image.substring(0,image.length()-1); // Check if Image Exists if (!fileexists(image)) { // Draw NPCs Image image = (!fileexists(npc_img) ? PLACEHOLDER : npc_img); // Determine Width and Height this.width = getimgwidth(image) / 16; this.height = getimgheight(image) / 16; } else { partx = math_eval(temp.tokens[1].trim()); party = math_eval(temp.tokens[2].trim()); partw = math_eval(temp.tokens[3].trim()); parth = temp.tokens[4].trim(); // Remove End Brace while (parth.ends(")")) parth = parth.substring(0, parth.length()-1); // Calculate Part Height parth = math_eval(parth); // Minimum Width and Height if (partw < 0) partw = 16; if (parth < 0) parth = 16; this.width = partw / 16; this.height = parth / 16; } } else { // Invalid Default to Placeholder image = PLACEHOLDER; this.width = this.height = 3; } } else { // Draw Placeholder image = PLACEHOLDER; this.width = this.height = 3; } } // Check if NPC is Character else if (temp.npc_script.pos("showcharacter") >= 0) { if (SHOWCHARS) { // Set Animation ani = getCharAni(npc_script); playerlook = false; // Head temp.head = getActorValue("head", npc_script); if (temp.head) actor.head = temp.head; // Body temp.body = getActorValue("body", npc_script); if (temp.body) actor.bodyimg = temp.body; // Shield temp.shield = getActorValue("shield", npc_script); if (temp.shield) actor.shield = temp.shield; // Colors for (temp.i = 0; temp.i < 5; temp.i++) { temp.color = getActorValue("colors", npc_script, temp.i); if (temp.color) actor.colors[temp.i] = temp.color; } // Attributes for (temp.i = 0; temp.i < 30; temp.i++) { temp.attr = getActorValue("attr", npc_script, temp.i); if (temp.attr) actor.attr[temp.i] = temp.attr; } // Direction actor.dir = getVarValue("dir", temp.npc_script); } else { image = PLACEHOLDER; } this.width = this.height = 3; } // NPC Isn't Special else { // Draw NPCs Image image = (!fileexists(npc_img) ? PLACEHOLDER : npc_img); // Determine Width and Height this.width = getimgwidth(image) / 16; this.height = getimgheight(image) / 16; } } // Set Script and Image setNPCScript(temp.npc_id, temp.npc_script); setNPCImage(temp.npc_id, temp.npc_img); } function setNPCScript(npc_id, npc_script) { thiso.("npcscript_" @ npc_id) = npc_script; } function getNPCScript(npc_id) { return thiso.("npcscript_" @ npc_id); } function setNPCImage(npc_id, npc_image) { thiso.("npcimage_" @ npc_id) = npc_image; } function getNPCImage(npc_id) { return thiso.("npcimage_" @ npc_id) ? this.("npcimage_" @ npc_id) : ""; } function getActorValue(part, npc_script, extra) { switch (part) { case "head": temp.i = getCharPropValue("#3", npc_script); if (temp.i == NULL) temp.i = getVarValue("head", npc_script); if (temp.i == NULL) temp.i = getVarValue("headimg", npc_script); return fileexists(temp.i) ? temp.i : "head0.png"; break; case "body": temp.i = getCharPropValue("#8", npc_script); if (temp.i == NULL) temp.i = getVarValue("body", npc_script); if (temp.i == NULL) temp.i = getVarValue("bodyimg", npc_script); return fileexists(temp.i) ? temp.i : "body.png"; break; case "shield": temp.i = getCharPropValue("#2", npc_script); if (temp.i == NULL) temp.i = getVarValue("shield", npc_script); if (temp.i == NULL) temp.i = getVarValue("shieldimg", npc_script); return fileexists(temp.i) ? temp.i : "no-shield.png"; break; case "colors": temp.i = getCharPropValue("#C" @ extra, npc_script); if (temp.i == NULL) temp.i = getVarValue("colors[" @ extra @ "]", npc_script); return temp.i; // RED DRAGONS break; case "attr": temp.i = getCharPropValue("#P" @ extra, npc_script); if (temp.i == NULL) temp.i = getVarValue("attr[" @ extra @ "]", npc_script); return temp.i; break; } } function getCharPropValue(prop, npc_script) { temp.p = "setcharprop " @ prop @ ","; temp.a = npc_script.pos(temp.p); if (temp.a >= 0) { temp.s = npc_script.substring(temp.a + temp.p.length()); temp.s = temp.s.substring(0, temp.s.pos(";")); if (temp.s == "0") temp.s = "white"; return temp.s; } else { return NULL; } } function getVarValue(vari, npc_script) { // Find Variable temp.aa = npc_script.positions(vari); for (temp.a: temp.aa) { // Check for Assignment temp.s = npc_script.substring(temp.a); temp.e = temp.s.pos("="); temp.c = temp.s.pos(";"); // Make Sure Semi-Colon is After Assignment if (temp.e < temp.c) { // Ignore "" if (temp.s.pos("\"\"") >= 0) return ""; // Determine Value temp.s = temp.s.substring(temp.e+1); temp.sc = temp.s.pos(";"); temp.s = temp.s.substring(0, (temp.sc == -1 ? temp.s.length() : temp.sc)); temp.s = temp.s.trim(); if (temp.s.size() == 2) { temp.s.delete(0); temp.s = temp.s[0]; } // Remove Double-Quotes while (temp.s.starts('"')) temp.s = temp.s.substring(0); while (temp.s.ends('"')) temp.s = temp.s.substring(0,temp.s.length()-1); // (0) White Color Fix if (vari.starts("colors") && (temp.s == "0")) { temp.s = "white"; } // Return Value return temp.s; } } return NULL; } function getCharAni(npc_script) { temp.p = npc_script.lower().pos("setcharani "); temp.q = npc_script.lower().pos("setcharani("); if (temp.p >= 0 || temp.q >= 0) { if (temp.q == -1) temp.q = temp.p; temp.s = npc_script.substring(temp.q); temp.s = temp.s.substring(0, temp.s.pos(",")-1); temp.s = temp.s.substring("setcharani ".length()); if (temp.s.size() == 2) { temp.s.delete(0); temp.s = temp.s[0]; } // Remove Double Quotes while (temp.s.starts('"')) temp.s = temp.s.substring(1); while (temp.s.ends('"')) temp.s = temp.s.substring(0, temp.s.length()-1); // Return Value return (fileexists(temp.s @ ".gani") ? temp.s : "idle"); } return "idle"; } function getNPCData() { // Initialize Data Array temp.npcdata = {}; // Loop through NPCs for (temp.npc_id: this.levelnpcs) { // Check if has script temp.npc_script = getNPCScript(temp.npc_id); temp.no_script = (temp.npc_script.size() == 1 && temp.npc_script[0].trim() == ""); temp.npc_img = getNPCImage(temp.npc_id); with (findimg(temp.npc_id)) { // Add NPC Data to Array temp.npcdata.add({ temp.npc_img, (x % 64), (y % 64), (temp.no_script ? NULL : temp.npc_script) }); } } // Return NPC Data return temp.npcdata; } function onTimeout() { // Check if Active if (this.active) { // Right Drag Menu if (this.rightdragging) { // Check if Right Mouse Down if (!rightmousebutton) { // Close Menu this.rmorigin_x = ""; this.rmorigin_y = ""; this.rightdragging = ""; onHideContextMenu(); } else { // Check If Time to Display Menu Yet if (timevar2 > this.rightdragging_init) { // Fade in Menu if (this.rightalpha < 1) { this.rightalpha += 0.25; } with (findimg(20003)) { x = thiso.rmorigin_x; y = thiso.rmorigin_y - 0.25; text = "Mouse over the NPC to create it!"; style = "bc"; textshadow = true; shadowcolor = "0 0 0"; zoom = 0.5; alpha = thiso.rightalpha; } with (findimg(20004)) { x = thiso.rmorigin_x - 1.5; y = thiso.rmorigin_y - 5; image = PLACEHOLDER; alpha = thiso.rightalpha; } with (findimg(20005)) { x = thiso.rmorigin_x - 1.5; y = thiso.rmorigin_y + 2; ani = "idle"; playerlook = false; actor.head = "head0.png"; actor.colors[0] = "orange"; actor.colors[1] = "white"; actor.colors[2] = "blue"; actor.colors[3] = "red"; actor.colors[4] = "black"; actor.shield = "no-shield.png"; actor.dir = 2; actor.alpha = thiso.rightalpha; } temp.ydiff = mousey - this.rmorigin_y; temp.creating = false; if (temp.ydiff in |-5,-1.75|) { // Create Placeholder temp.creating = "placeholder"; } else if (temp.ydiff in |2,5|) { // Create NPC temp.creating = "npc"; } if (temp.creating) { // Hide Images onHideContextMenu(); this.rmorigin_x = ""; this.rmorigin_y = ""; this.rightdragging = ""; // Determine Next NPC ID temp.npc_id = this.levelnpcs[this.levelnpcs.size()-1] + 1; // Set NPC Variables temp.npc_x = mousex % 64; temp.npc_y = mousey % 64; temp.npc_img = ""; temp.npc_script = { "// Created by " @ player.account }; // Generate Script if (temp.creating == "npc") { temp.npc_script.addarray({ "function onCreated() {", " showcharacter();", " this.head = \"head0.png\";", " this.colors[0] = \"orange\";", " this.colors[1] = \"white\";", " this.colors[2] = \"blue\";", " this.colors[3] = \"red\";", " this.colors[4] = \"black\";", " this.shield = \"no-shield.png\";", " this.dir = 2;", " setcharani(\"idle\",NULL);", "}" }); } // Changes Made changesMade(); // Draw NPCs updateNPCImage(temp.npc_id, temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script); // Add to List this.levelnpcs.add(temp.npc_id); // Begin Dragging this.dragoffsetx = 1; this.dragoffsety = 1; this.dragging = temp.npc_id; } } else { this.rmorigin_x = mousex; this.rmorigin_y = mousey; } } } // Check if Dragging else if (this.dragging) { // Check if Mouse Button Down if (leftmousebutton || rightmousebutton) { // Draw GMAP Boundaries if (player.gmap != NULL) { drawBoundaries(); } // Determine Level Boundaries if (player.gmap != NULL) { temp.tx = player.x - (player.x % 64); temp.ty = player.y - (player.y % 64); } else { temp.tx = temp.ty = 0; } // Move NPC with (findimg(this.dragging)) { x = int(mousex - thiso.dragoffsetx); y = int(mousey - thiso.dragoffsety); // Force Boundaries if (x < temp.tx) x = temp.tx; else if (x > (temp.tx+63)) x = temp.tx + 63; if (y < temp.ty) y = temp.ty; else if (y > (temp.ty+63)) y = temp.ty + 63; // Set Alpha if (actor.head) { actor.alpha = (timevar2 > thiso.waitalpha ? 0.5 : 1); } else { alpha = (timevar2 > thiso.waitalpha ? 0.8 : 1); } } // Update Selected NPC this.selected = this.dragging; } else { // Determine Level Boundaries if (player.gmap != NULL) { temp.tx = player.x - (player.x % 64); temp.ty = player.y - (player.y % 64); } else { temp.tx = temp.ty = 0; } with (findimg(this.dragging)) { if (!(x in |temp.tx,temp.tx+64| && temp.ty in |temp.ty,temp.ty+64|)) { temp.outsidebounds = true; } // Check if Position Changed temp.updating = ((x SPC y) != this.original); // Set Alpha Back to Normal if (actor.head) { actor.alpha = 1; } else { alpha = 1; } this.original = ""; } if (temp.updating) { // Changes Made Tell Editor changesMade(); } if (temp.outsidebounds) { this.cancelevents("HideMSG"); displayMSG("Please place the NPC in the boundaries of the level!"); } // Hide Boundaries hideBoundaries(); // Unset Dragging Flag this.dragging = false; } } // Check for Selected NPC if (this.selected > 0) { // Draw Box Around Selection drawSelection(); } setTimer(0.05); } } function drawBoundaries() { with (findimg(20006)) { x = player.x - (player.x % 64); y = player.y - (player.y % 64); this.width = 64; this.height = 64; alpha = 0.5; polygon = poly_hollowbox(x,y,this.width,this.height); } } function hideBoundaries() { hideimg(20006); } function onHideContextMenu() { findimg(20003).alpha -= 0.2; findimg(20004).alpha -= 0.2; findimg(20005).actor.alpha -= 0.2; if (findimg(20003).alpha > 0) { this.scheduleevent(0.05, "HideContextMenu", ""); } else { hideimgs(20003,20005); } } // Draws Box Around Selection function drawSelection() { // Glitched in V5 if (graalversion < 6) { with (findimg(this.selected)) { alpha = 0.8; actor.alpha = 0.5; } return; } temp.selection = findimg(this.selected); with (findimg(20001)) { x = temp.selection.x; y = temp.selection.y; width = temp.selection.width; height = temp.selection.height; temp.s = 2/8; red = blue = green = 0.9; polygon = poly_hollowbox(x-temp.s,y-temp.s,width+(temp.s*1.5),height+(temp.s*1.5)); } this.last_selected = this.selection; } // poly_funcs::hollow_box() public function poly_hollowbox(qx,qy,qw,qh) { qd=1/8; return {qx,qy, qx+qw,qy, qx+qw,qy+qh, qx,qy+qh, qx,qy, qx+qd,qy+qd, qx+qd,qy+qh+qd, qx+qw+qd,qy+qh+qd, qx+qw+qd,qy+qd, qx+qd,qy+qd}; } // Deletes Selected NPC function deleteSelection() { // Remove NPC from Level NPCs Array this.levelnpcs.remove(this.selected); // Hide NPC hideimg(this.selected); // Notify Editor of Change changesMade(); // Remove Selection Box removeSelection(); } function removeSelection() { // V5 Selection just does Alpha if (graalversion < 6) { with (findimg(this.selected)) { alpha = 1; actor.alpha = 1; } } // Unset Selected NPC Value this.selected = ""; // Hide Box Image hideimg(20001); } function GraalControl.onMouseDown(a, tx, ty, clickcount) { if (!this.active) return; temp.double = false; if (clickcount == 2) { temp.dist = vectordist({mousex, mousey, 0}, this.last_click); if (temp.dist < 0.5) { temp.double = true; } } if (temp.double) { onMousePressed("double"); } else { onMousePressed("left"); } this.last_click = {mousex, mousey, 0}; } function GraalControl.onRightMouseDown() { if (!this.active) return; onMousePressed("right"); } function onMousePressed(click) { if (!this.active) return; switch (click) { case "left": // Drag if (!this.dragging) { // Find Nearest NPC this.dragging = getClosestNPCID(); if (this.dragging > 0) { // Determine Drag Offset and Original Position with (findimg(this.dragging)) { thiso.dragoffsetx = mousex - x; thiso.dragoffsety = mousey - y; this.original = x SPC y; } // Draw Selection this.selected = this.dragging; drawSelection(); // Set Time Before Turns Transparent this.waitalpha = timevar2 + 0.1; } else { // No NPC was Found this.dragging = false; // Remove Selection Box removeSelection(); } } this.lastclick = (int(mousex) SPC int(mousey)); break; case "right": // Duplicate temp.dupe = getClosestNPCID(); if (temp.dupe > 0) { // Determine Next NPC ID temp.npc_id = this.levelnpcs[this.levelnpcs.size()-1] + 1; // Find NPC to Dupe temp.duping = findimg(temp.dupe); // Create New NPC temp.npc_img = getNPCImage(temp.dupe); temp.npc_x = temp.duping.x % 64; temp.npc_y = temp.duping.y % 64; temp.npc_script = getNPCScript(temp.dupe); updateNPCImage(temp.npc_id, temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script); // Begin Dragging Duplicate NPC this.waitalpha = timevar2 + 0.10; this.dragoffsetx = mousex - temp.duping.x; this.dragoffsety = mousey - temp.duping.y; this.dragging = temp.npc_id; // Add to Level NPCs Array this.levelnpcs.add(temp.npc_id); // Notify Editor of Change Made changesMade(); // Display Duplication Message to User displayMSG("NPC Duplicated!"); } else { // Remove Selection removeSelection(); // Make sure not Draggin Right Mouse if (!this.rightdragging) { // Set Origin of Mouse this.rmorigin_x = mousex; this.rmorigin_y = mousey; // Begin Right Drag this.rightdragging = true; // Declare Time to Create Right-Context Menu this.rightdragging_init = timevar2 + RIGHTMENUTIME; this.rightalpha = 0; } } break; case "double": // Record Time of Doubleclick if (this.selected > 0) { this.doubleclick = timevar2; } break; } } function GraalControl.onMouseUp() { // Check if mouse up within recent double click if (timevar2 in |this.doubleclick,this.doubleclick+0.25|) { // Open Script of Nearest NPC to Mouse temp.closest_id = getClosestNPCID(); if (temp.closest_id != NULL) { temp.npc_script = getNPCScript(temp.closest_id); temp.npc_img = getNPCImage(temp.closest_id); with (findimg(temp.closest_id)) { createEditorWindow(temp.closest_id, temp.npc_img, x, y, temp.npc_script); } } } } // Finds Nearest NPC in Level NPCs to Mouse X and Y function getClosestNPCID() { // Loop through NPCs for (temp.npc_id: this.levelnpcs) { with (findimg(temp.npc_id)) { if (mousex in |x,x+this.width| && mousey in |y,y+this.height|) { temp.found.add({ temp.npc_id, x, y }); } } } // Check if any Found if (temp.found.size() > 0) { if (temp.found.size() == 1) { // Set Closest ID to Only One Found temp.closest_id = temp.found[0][0]; } else { // Determine Which ID is Closest temp.closest_id = temp.found[0][0]; temp.closest_dist = vectordist({mousex, mousey, 0}, {temp.found[0][1], temp.found[0][2], 0}); for (temp.i = 1; temp.i < temp.found.size(); temp.i++) { temp.dist = vectordist({mousex, mousey}, temp.found[temp.i].subarray(1)); if (temp.dist < temp.closest_dist) { temp.closest_id = temp.found[temp.i][0]; temp.closest_dist = temp.dist; } } } // Return Closest NPC ID return temp.closest_id; } else { // No NPC Found return NULL; } } function changesMade() { // Check if Not Updated if (!this.updatedscripts) { // Display 'How to Save' Message to User displayMSG("Press F9 to save changes when your ready!"); // Set Updated Flag for Editor this.updatedscripts = true; } } // For scheduling display messages function onReminder(msg) { displayMSG(msg); } // Create Editor Window function createEditorWindow(temp.npc_id, temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script) { new GuiWindowCtrl("LevelNPC_" @ temp.npc_id @ "_Window") { profile = GuiBlueWindowProfile; clientrelative = true; this.npc_id = temp.npc_id; temp.ww = clientwidth = 600; temp.wh = clientheight = 500; x = (screenwidth - temp.ww) / 2; y = (screenheight - temp.wh) / 2; isexternal = true; text = format("NPC Script Editor (X: %s Y: %s)", temp.npc_x % 64, temp.npc_y % 64); destroyonhide = true; thiso.trigger("CenterExternalWindow", this); new GuiScrollCtrl("LevelNPC_" @ temp.npc_id @ "_ScriptScroll") { profile = GuiBlueScrollProfile; scrollprofile = GuiBlueScrollProfile; useownprofile = true; profile.fillColor = "255 255 255"; profile.opaque = true; profile.border = 1; profile.bitmap = "guiblue_scroll_noback.png"; horizsizing = "width"; vertsizing = "height"; clientwidth = temp.ww; clientheight = temp.wh - 40; new GuiMLTextEditCtrl("LevelNPC_" @ temp.npc_id @ "_Script") { profile = SyntaxHighlightProfile; useownprofile = true; profile.fillcolorhl = "80 180 255"; profile.fontcolorlink = "255 0 255"; profile.fontsize = 15; horizsizing = "width"; syntaxhighlighting = true; wordwrap = false; clientheight = temp.wh; clientwidth = temp.ww - 40; width -= 4; setlines(temp.npc_script); temp.scriptctrl = this; autoindenting = true; tabspaces = 2; } } new GuiButtonCtrl("LevelNPC_" @ temp.npc_id @ "_ScriptClose") { profile = GuiBlueButtonProfile; horizsizing = "left"; vertsizing = "top"; clientwidth = 64; clientheight = 32; x = temp.ww - 68; y = temp.wh - 40 + 4; text = "Close"; thiso.catchevent(this.name, "onAction", "onCloseScript"); } new GuiButtonCtrl("LevelNPC_" @ temp.npc_id @ "_ScriptApply") { profile = GuiBlueButtonProfile; horizsizing = "left"; vertsizing = "top"; clientwidth = 64; clientheight = 32; x = temp.ww - 68 - 64 - 4; y = temp.wh - 40 + 4; text = "Apply"; thiso.catchevent(this.name, "onAction", "onApplyScript"); } new GuiTextEditCtrl("LevelNPC_" @ temp.npc_id @ "_Image") { profile = GuiBlueTextEditProfile; clientwidth = 256; clientheight = 24; x = 4; y = temp.wh - 24 - 10; text = temp.npc_img; thiso.catchevent(this.name, "onAction", "onUpdateImage"); temp.imgctrl = this; } this.scriptctrl = temp.scriptctrl; this.imgctrl = temp.imgctrl; } } function onCenterExternalWindow(obj) { with (obj.externalwindow) { x = screenbounds[0] + (screenbounds[2] - width) / 2; y = screenbounds[1] + (screenbounds[3] - height) / 2; } } // Update Image in Text Box function onUpdateImage(obj) { if (!this.active) return; temp.npc_img = obj.text.trim(); with (findimg(obj.parent.npc_id)) { image = temp.npc_img; image = fileexists(image) ? image : PLACEHOLDER; this.width = getimgwidth(image) / 16; this.height = getimgheight(image) / 16; } setNPCImage(temp.npc_id, temp.npc_img); changesMade(); } // Closes Script function onCloseScript(obj) { obj.parent.destroy(); } // Saves NPC Script and Image function onApplyScript(obj) { if (!this.active) { obj.parent.destroy(); return; } temp.npc_id = obj.parent.npc_id; temp.npc_script = obj.parent.scriptctrl.getlines(); temp.npc_img = obj.parent.imgctrl.text.trim(); obj.parent.destroy(); with (findimg(temp.npc_id)) { image = temp.npc_img; image = fileexists(image) ? image : PLACEHOLDER; temp.npc_x = x % 64; temp.npc_y = y % 64; } setNPCImage(temp.npc_id, temp.npc_img); setNPCScript(temp.npc_id, temp.npc_script); if (temp.npc_id > 0) { changesMade(); updateNPCImage(temp.npc_id, temp.npc_img, temp.npc_x, temp.npc_y, temp.npc_script); } } // Hides/Shows Level NPCs function visibleNPCs(showing) { // Determine Top-Left Position if (player.gmap != NULL) { temp.tx = player.x - (player.x % 64); temp.ty = player.y - (player.y % 64); } else { temp.tx = temp.ty = 0; } // Loop through NPCs for (temp.npc: npcs) { // Check if Showing NPCs if (showing) { // Check if Was Hidden if (temp.npc.washidden != "") { // Return Original Visible State temp.npc.visible = temp.npc.washidden; temp.npc.washidden = ""; } } else { // Check if in Current Level if (temp.npc.x in |temp.tx,temp.tx+64| && temp.npc.y in |temp.ty,temp.ty+64|) { // Hide NPC temp.npc.washidden = temp.npc.visible && !showing; temp.npc.visible = false; } } } } /* Math Eval Functions */ public function math_eval(temp.equ, temp.stack) { // Brackets temp.stack++; temp.opn = temp.equ.positions("("); temp.end = temp.equ.positions(")"); if (opn.size() != end.size()) return "Bracket Mismatch"; while (temp.opn.size() > 0) { // Isolate Inner Brackets temp.spos = temp.opn[temp.opn.size()-1]; temp.epos = temp.equ.substring(temp.spos).pos(")"); temp.nequ = temp.equ.substring(temp.spos+1, temp.epos - 1); temp.result = math_eval(temp.nequ, temp.stack); temp.equ = temp.equ.substring(0, temp.spos) @ temp.result @ temp.equ.substring(temp.spos+temp.epos+1); temp.opn = temp.equ.positions("("); } // Remove Spaces temp.equ = clearSpaces(temp.equ); // Evaluate temp.operations = {"^", "/", "*", "+", "-", "<", ">", "<=", ">=", "!=", "==", "&&", "||"}; for (temp.op: temp.operations) { while (temp.equ.pos(temp.op) > 0) { temp.fpos = temp.equ.pos(temp.op); // Find A temp.apos = ""; for (temp.j = temp.fpos-temp.op.length(); temp.j > 0 && !temp.apos; temp.j--) { temp.sop = temp.equ.charat(temp.j); temp.dop = temp.sop @ temp.equ.charat(temp.j-1); if (temp.dop in temp.operations || temp.sop in temp.operations) { temp.apos = temp.j; temp.a = temp.equ.substring(temp.apos + 1, temp.fpos - temp.apos - 1); } } if (!temp.apos) { temp.a = temp.equ.substring(0, temp.fpos); } if (hasValue(temp.a)) temp.a = getValue(temp.a); // Find B temp.bpos = ""; for (temp.k = temp.fpos+temp.op.length()+1; temp.k < temp.equ.length() && !temp.bpos; temp.k++) { temp.sop = temp.equ.charat(temp.k); temp.dop = temp.sop @ temp.equ.charat(temp.k+1); if (temp.sop in temp.operations || temp.dop in temp.operations) { temp.bpos = k; temp.b = temp.equ.substring(temp.fpos + temp.op.length(), temp.bpos - temp.fpos - 1); } } if (!temp.bpos) { temp.b = temp.equ.substring(temp.fpos+temp.op.length()); } if (hasValue(temp.b)) temp.b = getValue(temp.b); // Calculate Result temp.result = evaluate(temp.a, temp.op, temp.b); // Rebuild Equation temp.str = temp.apos ? temp.equ.substring(0, temp.apos + 1) : ""; temp.end = temp.bpos ? temp.equ.substring(temp.bpos) : ""; temp.equ = temp.str @ temp.result @ temp.end; } } temp.result = hasValue(temp.equ) ? getValue(temp.equ) : float(temp.equ); return temp.result ? temp.equ : temp.result; } function clearSpaces(temp.l) { while (temp.l.pos(" ") >= 0) { temp.l = temp.l.substring(0, temp.l.pos(" ")) @ temp.l.substring(temp.l.pos(" ")+1); } return temp.l; } function evaluate(a, op, b) { switch(op) { case "equal": case "==": case "=": return a == b || a == int(float(b)); case "&&": return a && b; case "||": return a || b; case "!=": return a != b; case ">": return a > b; case ">=": return a >= b; case "<": return a < b; case "<=": return a <= b; case "&": return a & b; case "^": return a ^ b; case "|": return a | b; case "in": // array only return a in b; case "*": return a * b; case "/": return a / b; case "+": return a + b; case "-": return a -b; } return 0; } function hasValue(temp.svar) { return getValue(temp.svar); } function getValue(temp.svar) { return this.(@temp.svar); }