// This file is way too big. Fix later... // ------------------------------------------------------------------- //Selection data arrays $NS[obj, type{, ...}] // $NS[%s, "B", %i ] - Brick object // $NS[%s, "I", %b ] - Index of brick in array // $NS[%s, "N", %i ] - Number of connected bricks // $NS[%s, "C", %i, %j] - Index of connected brick // $NS[%s, "D", %i] - Datablock // $NS[%s, "P", %i] - Position // $NS[%s, "R", %i] - Rotation // $NS[%s, "NT", %i] - Brick name // $NS[%s, "HN", %n] - Name exists in selection // $NS[%s, "PR", %i] - Print // $NS[%s, "CO", %i] - Color id // $NS[%s, "CF", %i] - Color Fx id // $NS[%s, "SF", %i] - Shape Fx id // $NS[%s, "NRC", %i] - No ray casting // $NS[%s, "NR", %i] - No rendering // $NS[%s, "NC", %i] - No colliding // $NS[%s, "LD", %i] - Light datablock // $NS[%s, "ED", %i] - Emitter datablock // $NS[%s, "ER", %i] - Emitter rotation // $NS[%s, "ID", %i] - Item datablock // $NS[%s, "IP", %i] - Item position // $NS[%s, "IR", %i] - Item rotation // $NS[%s, "IT", %i] - Item respawn time // $NS[%s, "VD", %i] - Vehicle datablock // $NS[%s, "VC", %i] - Vehicle color // $NS[%s, "MD", %i] - Music datablock // $NS[%s, "EN", %i] - Number of events on the brick // $NS[%s, "EE", %i, %j] - Whether event is enabled // $NS[%s, "ED", %i, %j] - Event delay // $NS[%s, "EI", %i, %j] - Event input name // $NS[%s, "EII", %i, %j] - Event input idx // $NS[%s, "EO", %i, %j] - Event output name // $NS[%s, "EOI", %i, %j] - Event output idx // $NS[%s, "EOC", %i, %j] - Event output append client // $NS[%s, "ET", %i, %j] - Event target name // $NS[%s, "ETI", %i, %j] - Event target idx // $NS[%s, "ENT", %i, %j] - Event brick named target // $NS[%s, "EP", %i, %j, %k] - Event output parameter //Mirror error lists $NS[client, type{, ...}] // $NS[%c, "MXC", ] - Count of mirror errors on x // $NS[%c, "MXE", %i] - Error datablock // $NS[%c, "MXK", %d] - Index of datablock in list // $NS[%c, "MZC", ] - Count of mirror errors on z // $NS[%c, "MZE", %i] - Error datablock // $NS[%c, "MZK", %d] - Index of datablock in list //General ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Create selection function ND_Selection(%client) { ND_ServerGroup.add( %this = new ScriptObject(ND_Selection) { client = %client; } ); return %this; } //Delete all the selection variables, allowing re-use of object function ND_Selection::deleteData(%this) { //If count isn't at least 1, assume there is no data if(%this.queueCount >= 1 || %this.brickCount >= 1) { //Variables follow the pattern $NS[object]_[type]_[...], allowing a single iteration to remove all deleteVariables("$NS" @ %this @ "_*"); } %this.rootPosition = "0 0 0"; %this.queueCount = 0; %this.brickCount = 0; %this.targetGroup = ""; %this.targetBlid = ""; %this.deHighlight(); %this.deleteHighlightBox(); %this.deleteGhostBricks(); if(isObject(%this.saveFile)) %this.saveFile.delete(); } //Remove data when selection is deleted function ND_Selection::onRemove(%this) { %this.deleteData(); if(isEventPending(%this.plantSchedule)) %this.cancelPlanting(); } //Stack Selection ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Begin stack selection function ND_Selection::startStackSelection(%this, %brick, %direction, %limited) { //Clear previous selection %this.deleteData(); //Create new highlight group %highlightGroup = ndNewHighlightGroup(); %this.brickLimitReached = false; if(%this.client.isAdmin) %brickLimit = $Pref::Server::ND::MaxBricksAdmin; else %brickLimit = $Pref::Server::ND::MaxBricksPlayer; //Root position is position of the first selected brick %this.rootPosition = %brick.getPosition(); //Process first brick %queueCount = 1; %brickCount = 1; $NS[%this, "B", 0] = %brick; $NS[%this, "I", %brick] = 0; %this.recordBrickData(0); ndHighlightBrick(%highlightGroup, %brick); //Variables for trust checks %admin = %this.client.isAdmin; %group = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; //Add bricks connected to the first brick to queue (do not register connections yet) if(%direction == 1) { //Set lower height limit %heightLimit = %this.minZ - 0.01; %upCount = %brick.getNumUpBricks(); for(%i = 0; %i < %upCount; %i++) { %nextBrick = %brick.getUpBrick(%i); //If the brick is not in the list yet, add it to the queue if($NS[%this, "I", %nextBrick] $= "") { if(%queueCount >= %brickLimit) continue; //Check trust if(!ndTrustCheckSelect(%nextBrick, %group, %bl_id, %admin)) { %trustFailCount++; continue; } $NS[%this, "B", %queueCount] = %nextBrick; $NS[%this, "I", %nextBrick] = %queueCount; %queueCount++; } } } else { //Set upper height limit %heightLimit = %this.maxZ + 0.01; %downCount = %brick.getNumDownBricks(); for(%i = 0; %i < %downCount; %i++) { %nextBrick = %brick.getDownBrick(%i); //If the brick is not in the list yet, add it to the queue if($NS[%this, "I", %nextBrick] $= "") { if(%queueCount >= %brickLimit) continue; //Check trust if(!ndTrustCheckSelect(%nextBrick, %group, %bl_id, %admin)) { %trustFailCount++; continue; } $NS[%this, "B", %queueCount] = %nextBrick; $NS[%this, "I", %nextBrick] = %queueCount; %queueCount++; } } } //Save number of connections %this.maxConnections = 0; %this.connectionCount = 0; %this.trustFailCount = %trustFailCount; %this.highlightGroup = %highlightGroup; %this.queueCount = %queueCount; %this.brickCount = %brickCount; if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); //First selection tick %this.selectionStart = 1; if(%queueCount > %brickCount) %this.tickStackSelection(%direction, %limited, %heightLimit, %brickLimit); else %this.finishStackSelection(); } //Begin stack selection (multiselect) function ND_Selection::startStackSelectionAdditive(%this, %brick, %direction, %limited) { //If we have no bricks, start normal stack selection if(%this.brickCount < 1) { %this.startStackSelection(%brick, %direction, %limited); return; } //If we already reched the limit, don't even try if(%this.brickLimitReached) { %this.finishStackSelection(); return; } %highlightGroup = %this.highlightGroup; if(%this.client.isAdmin) %brickLimit = $Pref::Server::ND::MaxBricksAdmin; else %brickLimit = $Pref::Server::ND::MaxBricksPlayer; %queueCount = %this.queueCount; %brickCount = %this.brickCount; //If the brick is not part of the selection yet, process it if($NS[%this, "I", %brick] $= "") { $NS[%this, "B", %queueCount] = %brick; $NS[%this, "I", %brick] = %queueCount; %this.recordBrickData(%queueCount); ndHighlightBrick(%highlightGroup, %brick); %brickIsNew = true; %brickIndex = %queueCount; %conns = 0; %queueCount++; %brickCount++; } //Variables for trust checks %admin = %this.client.isAdmin; %group = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; //Add bricks connected to the first brick to queue (do not register connections yet) if(%direction == 1) { //Set lower height limit %heightLimit = getWord(%brick.getWorldBox(), 2) - 0.01; } else { //Set upper height limit %heightLimit = getWord(%brick.getWorldBox(), 5) + 0.01; } //Process all up bricks %upCount = %brick.getNumUpBricks(); for(%i = 0; %i < %upCount; %i++) { %nextBrick = %brick.getUpBrick(%i); //If the brick is not in the list yet, add it to the queue %nId = $NS[%this, "I", %nextBrick]; if(%nId $= "") { //Don't add up bricks if we're searching down if(%direction != 1) continue; if(%queueCount >= %brickLimit) continue; //Check trust if(!ndTrustCheckSelect(%nextBrick, %group, %bl_id, %admin)) { %trustFailCount++; continue; } $NS[%this, "B", %queueCount] = %nextBrick; $NS[%this, "I", %nextBrick] = %queueCount; %queueCount++; } else if(%brickIsNew) { //If this brick already exists, we have to add the connection now //(Start brick won't be processed again unlike the others) $NS[%this, "C", %brickIndex, %conns] = %nId; %conns++; %ci = $NS[%this, "N", %nId]++; $NS[%this, "C", %nId, %ci - 1] = %brickIndex; if(%ci > %this.maxConnections) %this.maxConnections = %ci; %this.connectionCount++; } } //Process all down bricks %downCount = %brick.getNumDownBricks(); for(%i = 0; %i < %downCount; %i++) { %nextBrick = %brick.getDownBrick(%i); //If the brick is not in the list yet, add it to the queue %nId = $NS[%this, "I", %nextBrick]; if(%nId $= "") { //Don't add down bricks if we're searching up if(%direction == 1) continue; if(%queueCount >= %brickLimit) continue; //Check trust if(!ndTrustCheckSelect(%nextBrick, %group, %bl_id, %admin)) { %trustFailCount++; continue; } $NS[%this, "B", %queueCount] = %nextBrick; $NS[%this, "I", %nextBrick] = %queueCount; %queueCount++; } else if(%brickIsNew) { //If this brick already exists, we have to add the connection now //(Start brick won't be processed again unlike the others) $NS[%this, "C", %brickIndex, %conns] = %nId; %conns++; %ci = $NS[%this, "N", %nId]++; $NS[%this, "C", %nId, %ci - 1] = %brickIndex; if(%ci > %this.maxConnections) %this.maxConnections = %ci; %this.connectionCount++; } } $NS[%this, "N", %brickIndex] = %conns; //Inc number of connections %this.connectionCount += %conns; if(%conns > %this.maxConnections) %this.maxConnections = %conns; %this.trustFailCount += %trustFailCount; %this.queueCount = %queueCount; %this.brickCount = %brickCount; if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); //First selection tick %this.selectionStart = %queueCount; if(%queueCount > %brickCount) %this.tickStackSelection(%direction, %limited, %heightLimit, %brickLimit); else %this.finishStackSelection(); } //Tick stack selection function ND_Selection::tickStackSelection(%this, %direction, %limited, %heightLimit, %brickLimit) { cancel(%this.stackSelectSchedule); %highlightGroup = %this.highlightGroup; %selectionStart = %this.selectionStart; %queueCount = %this.queueCount; //Continue processing where we left off last tick %start = %this.brickCount; %end = %start + $Pref::Server::ND::ProcessPerTick; //Variables for trust checks %admin = %this.client.isAdmin; %group = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; for(%i = %start; %i < %end; %i++) { //If no more bricks are queued, we're done! if(%i >= %queueCount) { %this.queueCount = %queueCount; %this.brickCount = %i; if(%i >= %brickLimit) %this.brickLimitReached = true; %this.finishStackSelection(); return; } //Record data for next brick in queue %brick = ND_Selection::recordBrickData(%this, %i); if(!%brick) { messageClient(%this.client, 'MsgError', "\c0Error: \c6Queued brick does not exist anymore. Do not modify the build during selection!"); %this.cancelStackSelection(); %this.client.ndSetMode(NDM_StackSelect); return; } ndHighlightBrick(%highlightGroup, %brick); //Queue all up bricks %upCount = %brick.getNumUpBricks(); %conns = 0; for(%j = 0; %j < %upCount; %j++) { %nextBrick = %brick.getUpBrick(%j); //Skip bricks out of the limit if(%limited && %direction == 0 && getWord(%nextBrick.getWorldBox(), 5) > %heightLimit) continue; //If the brick is not in the selection yet, add it to the queue to get an id %nId = $NS[%this, "I", %nextBrick]; if(%nId $= "") { if(%queueCount >= %brickLimit) continue; //Check trust if(!ndTrustCheckSelect(%nextBrick, %group, %bl_id, %admin)) { %trustFailCount++; continue; } $NS[%this, "B", %queueCount] = %nextBrick; $NS[%this, "I", %nextBrick] = %queueCount; %nId = %queueCount; %queueCount++; } $NS[%this, "C", %i, %conns] = %nId; %conns++; //If this brick is from a previous stack selection, //we need to link the connection back as well if(%nId < %selectionStart) { %ci = $NS[%this, "N", %nId]++; $NS[%this, "C", %nId, %ci - 1] = %i; if(%ci > %this.maxConnections) %this.maxConnections = %ci; %this.connectionCount++; } } //Queue all down bricks %downCount = %brick.getNumDownBricks(); for(%j = 0; %j < %downCount; %j++) { %nextBrick = %brick.getDownBrick(%j); //Skip bricks out of the limit if(%limited && %direction == 1 && getWord(%nextBrick.getWorldBox(), 2) < %heightLimit) continue; //If the brick is not in the selection yet, add it to the queue to get an id %nId = $NS[%this, "I", %nextBrick]; if(%nId $= "") { if(%queueCount >= %brickLimit) continue; //Check trust if(!ndTrustCheckSelect(%nextBrick, %group, %bl_id, %admin)) { %trustFailCount++; continue; } $NS[%this, "B", %queueCount] = %nextBrick; $NS[%this, "I", %nextBrick] = %queueCount; %nId = %queueCount; %queueCount++; } $NS[%this, "C", %i, %conns] = %nId; %conns++; //If this brick is from a previous stack selection, //we need to link the connection back as well if(%nId < %selectionStart) { %ci = $NS[%this, "N", %nId]++; $NS[%this, "C", %nId, %ci - 1] = %i; if(%ci > %this.maxConnections) %this.maxConnections = %ci; %this.connectionCount++; } } $NS[%this, "N", %i] = %conns; //Inc number of connections %this.connectionCount += %conns; if(%conns > %this.maxConnections) %this.maxConnections = %conns; } %this.trustFailCount += %trustFailCount; %this.queueCount = %queueCount; %this.brickCount = %i; if(%i >= %brickLimit) { %this.brickLimitReached = true; %this.finishStackSelection(); return; } //Tell the client how much we selected this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } //Schedule next tick %this.stackSelectSchedule = %this.schedule(30, tickStackSelection, %direction, %limited, %heightLimit, %brickLimit); } //Finish stack selection function ND_Selection::finishStackSelection(%this) { %this.updateSize(); %this.updateHighlightBox(); if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadEnd', ""); %s = %this.brickCount == 1 ? "" : "s"; %msg = "\c6Selected \c3" @ %this.brickCount @ "\c6 Brick" @ %s @ "!"; if(%this.brickLimitReached) %msg = %msg @ " (Limit Reached)"; if(%this.trustFailCount > 0) %msg = %msg @ "\n\c3" @ %this.trustFailCount @ "\c6 missing trust."; commandToClient(%this.client, 'centerPrint', %msg, 5); %this.client.ndSetMode(NDM_StackSelect); } //Cancel stack selection function ND_Selection::cancelStackSelection(%this) { cancel(%this.stackSelectSchedule); %this.deleteData(); } //Box Selection ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Begin box selection function ND_Selection::startBoxSelection(%this, %box, %limited) { //Ensure there is no highlight group %this.deHighlight(); //Save the chunk sizes %this.chunkX1 = getWord(%box, 0); %this.chunkY1 = getWord(%box, 1); %this.chunkZ1 = getWord(%box, 2); %this.chunkX2 = getWord(%box, 3); %this.chunkY2 = getWord(%box, 4); %this.chunkZ2 = getWord(%box, 5); %this.chunkSize = $Pref::Server::ND::BoxSelectChunkDim; %this.numChunksX = mCeil((%this.chunkX2 - %this.chunkX1) / %this.chunkSize); %this.numChunksY = mCeil((%this.chunkY2 - %this.chunkY1) / %this.chunkSize); %this.numChunksZ = mCeil((%this.chunkZ2 - %this.chunkZ1) / %this.chunkSize); %this.numChunks = %this.numChunksX * %this.numChunksY * %this.numChunksZ; %this.currChunkX = 0; %this.currChunkY = 0; %this.currChunkZ = 0; %this.currChunk = 0; %this.queueCount = 0; %this.brickCount = 0; %this.trustFailCount = 0; %this.brickLimitReached = false; %this.maxConnections = 0; %this.connectionCount = 0; if(%this.client.isAdmin) %brickLimit = $Pref::Server::ND::MaxBricksAdmin; else %brickLimit = $Pref::Server::ND::MaxBricksPlayer; //Process first tick if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); %this.tickBoxSelectionChunk(%limited, %brickLimit); } //Queue all bricks in a chunk function ND_Selection::tickBoxSelectionChunk(%this, %limited, %brickLimit) { cancel(%this.boxSelectSchedule); //Restore chunk variables (scopes and slow object fields suck) %chunkSize = %this.chunkSize; %currChunk = %this.currChunk; %currChunkX = %this.currChunkX; %currChunkY = %this.currChunkY; %currChunkZ = %this.currChunkZ; %numChunksX = %this.numChunksX; %numChunksY = %this.numChunksY; %numChunksZ = %this.numChunksZ; %chunkX1 = %this.chunkX1; %chunkY1 = %this.chunkY1; %chunkZ1 = %this.chunkZ1; %chunkX2 = %this.chunkX2; %chunkY2 = %this.chunkY2; %chunkZ2 = %this.chunkZ2; //Where to insert bricks in the queue %queueIndex = %this.queueCount; //Variables for trust checks %admin = %this.client.isAdmin; %group = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; %chunksDone = 0; %bricksFound = 0; %trustFailCount = 0; //Process chunks until we reach the brick or chunk limit while(%chunksDone < 600 && %bricksFound < 1000) { %chunksDone++; //Calculate position and size of chunk %x1 = %chunkX1 + (%currChunkX * %chunkSize) + 0.05; %y1 = %chunkY1 + (%currChunkY * %chunkSize) + 0.05; %z1 = %chunkZ1 + (%currChunkZ * %chunkSize) + 0.05; %x2 = getMin(%chunkX2 - 0.05, %x1 + %chunkSize - 0.1); %y2 = getMin(%chunkY2 - 0.05, %y1 + %chunkSize - 0.1); %z2 = getMin(%chunkZ2 - 0.05, %z1 + %chunkSize - 0.1); %size = %x2 - %x1 SPC %y2 - %y1 SPC %z2 - %z1; %pos = vectorAdd(%x1 SPC %y1 SPC %z1, vectorScale(%size, 0.5)); //Queue all new bricks found in this chunk initContainerBoxSearch(%pos, %size, $TypeMasks::FxBrickAlwaysObjectType); while(%obj = containerSearchNext()) { %bricksFound++; if($NS[%this, "I", %obj] $= "") { if(%limited) { //Skip bricks that are outside the limit %box = %obj.getWorldBox(); if(getWord(%box, 0) < %chunkX1 - 0.1) continue; if(getWord(%box, 1) < %chunkY1 - 0.1) continue; if(getWord(%box, 2) < %chunkZ1 - 0.1) continue; if(getWord(%box, 3) > %chunkX2 + 0.1) continue; if(getWord(%box, 4) > %chunkY2 + 0.1) continue; if(getWord(%box, 5) > %chunkZ2 + 0.1) continue; } //Check trust if(!ndTrustCheckSelect(%obj, %group, %bl_id, %admin)) { %trustFailCount++; continue; } //Queue brick $NS[%this, "I", %obj] = %queueIndex; $NS[%this, "B", %queueIndex] = %obj; %queueIndex++; //Test brick limit if(%queueIndex >= %brickLimit) { %limitReached = true; break; } } } //Stop processing chunks if limit was reached if(%limitReached) break; //Set next chunk index or break %currChunk++; if(%currChunkX++ >= %numChunksX) { %currChunkX = 0; if(%currChunkY++ >= %numChunksY) { %currChunkY = 0; if(%currChunkZ++ >= %numChunksZ) { %searchComplete = true; break; } } } } //Save chunk variables (scopes and slow object fields suck) %this.currChunk = %currChunk; %this.currChunkX = %currChunkX; %this.currChunkY = %currChunkY; %this.currChunkZ = %currChunkZ; %this.numChunksX = %numChunksX; %this.numChunksY = %numChunksY; %this.numChunksZ = %numChunksZ; %this.trustFailCount += %trustFailCount; %this.queueCount = %queueIndex; //If the brick limit was reached, start processing if(%limitReached) { %this.brickLimitReached = true; %this.rootPosition = $NS[%this, "B", 0].getPosition(); %this.boxSelectSchedule = %this.schedule(30, tickBoxSelectionProcess); return; } //If all chunks have been searched, start processing if(%searchComplete) { //Did we find any bricks at all? if(%queueIndex > 0) { //Create highlight group %this.highlightGroup = ndNewHighlightGroup(); //Start processing bricks %this.rootPosition = $NS[%this, "B", 0].getPosition(); %this.boxSelectSchedule = %this.schedule(30, tickBoxSelectionProcess); } else { messageClient(%this.client, 'MsgError', ""); %m = "\c6No bricks were found inside the selection!"; if(%this.trustFailCount > 0) %m = %m @ "\n\c3" @ %this.trustFailCount @ "\c6 missing trust."; commandToClient(%this.client, 'centerPrint', %m, 5); %this.cancelBoxSelection(); %this.client.ndSetMode(NDM_BoxSelect); } return; } //Tell the client which chunks we just processed if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } //Schedule next chunk %this.boxSelectSchedule = %this.schedule(30, tickBoxSelectionChunk, %limited, %brickLimit); } //Save connections between bricks and highlight them function ND_Selection::tickBoxSelectionProcess(%this) { cancel(%this.boxSelectSchedule); %highlightGroup = %this.highlightGroup; //Get bounds for this tick %start = %this.brickCount; %end = %start + $Pref::Server::ND::ProcessPerTick; if(%end > %this.queueCount) %end = %this.queueCount; //Save connections for bricks in the list for(%i = %start; %i < %end; %i++) { //Record data for next brick in queue %brick = ND_Selection::recordBrickData(%this, %i); if(!%brick) { messageClient(%this.client, 'MsgError', "\c0Error: \c6Queued brick does not exist anymore. Do not modify the build during selection!"); %this.cancelBoxSelection(); %this.client.ndSetMode(NDM_BoxSelect); return; } ndHighlightBrick(%highlightGroup, %brick); //Save all up bricks %upCount = %brick.getNumUpBricks(); %conns = 0; for(%j = 0; %j < %upCount; %j++) { %conn = %brick.getUpBrick(%j); //If the brick is in the selection, save the connection if((%nId = $NS[%this, "I", %conn]) !$= "") { $NS[%this, "C", %i, %conns] = %nId; %conns++; } } //Save all down bricks %downCount = %brick.getNumDownBricks(); for(%j = 0; %j < %downCount; %j++) { %conn = %brick.getDownBrick(%j); //If the brick is in the selection, save the connection if((%nId = $NS[%this, "I", %conn]) !$= "") { $NS[%this, "C", %i, %conns] = %nId; %conns++; } } $NS[%this, "N", %i] = %conns; //Inc number of connections %this.connectionCount += %conns; if(%conns > %this.maxConnections) %this.maxConnections = %conns; } //Save how far we got %this.brickCount = %i; //Tell the client how much we selected this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } if(%i >= %this.queueCount) %this.finishBoxSelection(); else %this.boxSelectSchedule = %this.schedule(30, tickBoxSelectionProcess); } //Finish box selection function ND_Selection::finishBoxSelection(%this) { %this.updateSize(); %this.updateHighlightBox(); if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadEnd', ""); %s = %this.brickCount == 1 ? "" : "s"; %msg = "\c6Selected \c3" @ %this.brickCount @ "\c6 Brick" @ %s @ "!"; if(%this.brickLimitReached) %msg = %msg @ " (Limit Reached)"; if(%this.trustFailCount > 0) %msg = %msg @ "\n\c3" @ %this.trustFailCount @ "\c6 missing trust."; %msg = %msg @ "\n\c6Press [Cancel Brick] to adjust the box."; commandToClient(%this.client, 'centerPrint', %msg, 8); %this.client.ndSetMode(NDM_BoxSelect); } //Cancel box selection function ND_Selection::cancelBoxSelection(%this) { cancel(%this.boxSelectSchedule); %this.deleteData(); } //Recording Brick Data ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Record info about a queued brick function ND_Selection::recordBrickData(%this, %i) { //Return false if brick no longer exists if(!isObject(%brick = $NS[%this, "B", %i])) return false; /////////////////////////////////////////////////////////// //Variables required for every brick //Datablock %datablock = %brick.getDatablock(); $NS[%this, "D", %i] = %datablock; //Offset from base brick $NS[%this, "P", %i] = vectorSub(%brick.getPosition(), %this.rootPosition); //Rotation $NS[%this, "R", %i] = %brick.angleID; //Colors if($NDHN[%brick]) { $NS[%this, "CO", %i] = %brick.colorID; $NS[%this, "CF", %i] = $NDHF[%brick]; } else { $NS[%this, "CO", %i] = %brick.colorID; if(%brick.colorFxID) $NS[%this, "CF", %i] = %brick.colorFxID; } /////////////////////////////////////////////////////////// //Optional variables only required for few bricks if(%tmp = %brick.shapeFxID) $NS[%this, "SF", %i] = %tmp; //Wrench settings if((%tmp = %brick.getName()) !$= "") { $NS[%this, "HN", %tmp] = true; $NS[%this, "NT", %i] = getSubStr(%tmp, 1, 254); } if(%tmp = %brick.light | 0) $NS[%this, "LD", %i] = %tmp.getDatablock(); if(%tmp = %brick.emitter | 0) { $NS[%this, "ED", %i] = %tmp.getEmitterDatablock(); $NS[%this, "ER", %i] = %brick.emitterDirection; } if(%tmp = %brick.item | 0) { $NS[%this, "ID", %i] = %tmp.getDatablock(); $NS[%this, "IP", %i] = %brick.itemPosition; $NS[%this, "IR", %i] = %brick.itemDirection; $NS[%this, "IT", %i] = %brick.itemRespawnTime; } if(%tmp = %brick.vehicleDataBlock) { $NS[%this, "VD", %i] = %tmp; $NS[%this, "VC", %i] = %brick.reColorVehicle; } if(%tmp = %brick.AudioEmitter | 0) $NS[%this, "MD", %i] = %tmp.profile.getID(); if(!%brick.isRaycasting()) $NS[%this, "NRC", %i] = true; if(!%brick.isColliding()) $NS[%this, "NC", %i] = true; if(!%brick.isRendering()) $NS[%this, "NR", %i] = true; //Prints if(%datablock.hasPrint) $NS[%this, "PR", %i] = %brick.printID; //Events if(%numEvents = %brick.numEvents) { $NS[%this, "EN", %i] = %numEvents; for(%j = 0; %j < %numEvents; %j++) { $NS[%this, "EE", %i, %j] = %brick.eventEnabled[%j]; $NS[%this, "ED", %i, %j] = %brick.eventDelay[%j]; $NS[%this, "EI", %i, %j] = %brick.eventInput[%j]; $NS[%this, "EII", %i, %j] = %brick.eventInputIdx[%j]; $NS[%this, "EO", %i, %j] = %brick.eventOutput[%j]; $NS[%this, "EOI", %i, %j] = %brick.eventOutputIdx[%j]; $NS[%this, "EOC", %i, %j] = %brick.eventOutputAppendClient[%j]; %target = %brick.eventTargetIdx[%j]; if(%target == -1) $NS[%this, "ENT", %i, %j] = %brick.eventNT[%j]; $NS[%this, "ET", %i, %j] = %brick.eventTarget[%j]; $NS[%this, "ETI", %i, %j] = %target; $NS[%this, "EP", %i, %j, 0] = %brick.eventOutputParameter[%j, 1]; $NS[%this, "EP", %i, %j, 1] = %brick.eventOutputParameter[%j, 2]; $NS[%this, "EP", %i, %j, 2] = %brick.eventOutputParameter[%j, 3]; $NS[%this, "EP", %i, %j, 3] = %brick.eventOutputParameter[%j, 4]; } } //Update total selection size %box = %brick.getWorldBox(); %minX = getWord(%box, 0); %minY = getWord(%box, 1); %minZ = getWord(%box, 2); %maxX = getWord(%box, 3); %maxY = getWord(%box, 4); %maxZ = getWord(%box, 5); if(%i) { if(%minX < %this.minX) %this.minX = %minX; if(%minY < %this.minY) %this.minY = %minY; if(%minZ < %this.minZ) %this.minZ = %minZ; if(%maxX > %this.maxX) %this.maxX = %maxX; if(%maxY > %this.maxY) %this.maxY = %maxY; if(%maxZ > %this.maxZ) %this.maxZ = %maxZ; } else { %this.minX = %minX; %this.minY = %minY; %this.minZ = %minZ; %this.maxX = %maxX; %this.maxY = %maxY; %this.maxZ = %maxZ; } return %brick; } //Highlighting bricks ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Set the size variables after selecting bricks function ND_Selection::updateSize(%this) { %this.minSize = vectorSub(%this.minX SPC %this.minY SPC %this.minZ, %this.rootPosition); %this.maxSize = vectorSub(%this.maxX SPC %this.maxY SPC %this.maxZ, %this.rootPosition); %this.brickSizeX = mFloatLength((%this.maxX - %this.minX) * 2, 0); %this.brickSizeY = mFloatLength((%this.maxY - %this.minY) * 2, 0); %this.brickSizeZ = mFloatLength((%this.maxZ - %this.minZ) * 5, 0); %this.rootToCenter = vectorAdd(%this.minSize, vectorScale(vectorSub(%this.maxSize, %this.minSize), 0.5)); } //Create or update the highlight box function ND_Selection::updateHighlightBox(%this) { if(!isObject(%this.highlightBox)) %this.highlightBox = ND_HighlightBox(); if(!isObject(%this.ghostGroup)) { %min = vectorAdd(%this.rootPosition, %this.minSize); %max = vectorAdd(%this.rootPosition, %this.maxSize); %this.highlightBox.setSize(%min, %max); } else %this.highlightBox.setSize(%this.getGhostWorldBox()); } //Remove the highlight box function ND_Selection::deleteHighlightBox(%this) { if(isObject(%this.highlightBox)) %this.highlightBox.delete(); } //Start clearing the highlight set function ND_Selection::deHighlight(%this) { if(%this.highlightGroup) { ndStartDeHighlight(%this.highlightGroup); %this.highlightGroup = 0; } } //Cutting bricks ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Begin cutting function ND_Selection::startCutting(%this) { //Process first tick if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); %this.cutIndex = 0; %this.cutSuccessCount = 0; %this.cutFailCount = 0; %this.tickCutting(); } //Cut some bricks function ND_Selection::tickCutting(%this) { cancel(%this.cutSchedule); //Get bounds for this tick %start = %this.cutIndex; %end = %start + $Pref::Server::ND::ProcessPerTick; if(%end > %this.brickCount) %end = %this.brickCount; %cutSuccessCount = %this.cutSuccessCount; %cutFailCount = %this.cutFailCount; %admin = %this.client.isAdmin; %group = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; //Cut bricks for(%i = %start; %i < %end; %i++) { %brick = $NS[%this, "B", %i]; if(!isObject(%brick)) continue; if(!ndTrustCheckModify(%brick, %group, %bl_id, %admin)) { %cutFailCount++; continue; } // Support for hole bots if(isObject(%brick.hBot)) { %brick.hBot.spawnProjectile("audio2d", "deathProjectile", "0 0 0", 1); %brick.hBot.delete(); } %brick.delete(); %cutSuccessCount++; } //Save how far we got %this.cutIndex = %i; %this.cutSuccessCount = %cutSuccessCount; %this.cutFailCount = %cutFailCount; //Tell the client how much we cut this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } if(%i >= %this.brickCount) %this.finishCutting(); else %this.cutSchedule = %this.schedule(30, tickCutting); } //Finish cutting function ND_Selection::finishCutting(%this) { %s = %this.cutSuccessCount == 1 ? "" : "s"; %msg = "\c6Cut \c3" @ %this.cutSuccessCount @ "\c6 Brick" @ %s @ "!"; if(%this.cutFailCount > 0) %msg = %msg @ "\n\c3" @ %this.cutFailCount @ "\c6 missing trust."; commandToClient(%this.client, 'centerPrint', %msg, 8); %this.client.ndSetMode(NDM_PlantCopy); } //Cancel cutting function ND_Selection::cancelCutting(%this) { cancel(%this.cutSchedule); } //Supercut ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Begin supercut function ND_Selection::startSuperCut(%this, %box) { //Ensure there is no highlight group %this.deHighlight(); //Save the chunk sizes %this.chunkX1 = getWord(%box, 0); %this.chunkY1 = getWord(%box, 1); %this.chunkZ1 = getWord(%box, 2); %this.chunkX2 = getWord(%box, 3); %this.chunkY2 = getWord(%box, 4); %this.chunkZ2 = getWord(%box, 5); %this.chunkSize = $Pref::Server::ND::BoxSelectChunkDim; %this.numChunksX = mCeil((%this.chunkX2 - %this.chunkX1) / %this.chunkSize); %this.numChunksY = mCeil((%this.chunkY2 - %this.chunkY1) / %this.chunkSize); %this.numChunksZ = mCeil((%this.chunkZ2 - %this.chunkZ1) / %this.chunkSize); %this.numChunks = %this.numChunksX * %this.numChunksY * %this.numChunksZ; %this.currChunkX = 0; %this.currChunkY = 0; %this.currChunkZ = 0; %this.currChunk = 0; %this.trustFailCount = 0; %this.superCutCount = 0; %this.superCutPlacedCount = 0; //Process first tick if(%client && $Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); %this.tickSuperCutChunk(); } //Process all bricks in a chunk function ND_Selection::tickSuperCutChunk(%this) { cancel(%this.superCutSchedule); //Restore chunk variables (scopes and slow object fields suck) %chunkSize = %this.chunkSize; %currChunk = %this.currChunk; %currChunkX = %this.currChunkX; %currChunkY = %this.currChunkY; %currChunkZ = %this.currChunkZ; %numChunksX = %this.numChunksX; %numChunksY = %this.numChunksY; %numChunksZ = %this.numChunksZ; %chunkX1 = %this.chunkX1; %chunkY1 = %this.chunkY1; %chunkZ1 = %this.chunkZ1; %chunkX2 = %this.chunkX2; %chunkY2 = %this.chunkY2; %chunkZ2 = %this.chunkZ2; //Variables for trust checks if(%this.client) { %admin = %this.client.isAdmin; %group = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; } %chunksDone = 0; %bricksFound = 0; %bricksPlanted = 0; %trustFailCount = 0; ndUpdateSpawnedClientList(); //Process chunks until we reach the brick or chunk limit while(%chunksDone < 600 && %bricksFound < 1000 && %bricksPlanted < 300) { %chunksDone++; //Calculate position and size of chunk %x1 = %chunkX1 + (%currChunkX * %chunkSize) + 0.05; %y1 = %chunkY1 + (%currChunkY * %chunkSize) + 0.05; %z1 = %chunkZ1 + (%currChunkZ * %chunkSize) + 0.05; %x2 = getMin(%chunkX2 - 0.05, %x1 + %chunkSize - 0.1); %y2 = getMin(%chunkY2 - 0.05, %y1 + %chunkSize - 0.1); %z2 = getMin(%chunkZ2 - 0.05, %z1 + %chunkSize - 0.1); %size = %x2 - %x1 SPC %y2 - %y1 SPC %z2 - %z1; %pos = vectorAdd(%x1 SPC %y1 SPC %z1, vectorScale(%size, 0.5)); //Process all new bricks found in this chunk initContainerBoxSearch(%pos, %size, $TypeMasks::FxBrickAlwaysObjectType); while(%obj = containerSearchNext()) { %db = %obj.getDatablock(); %bricksFound++; //Check trust if(%this.client && !ndTrustCheckModify(%obj, %group, %bl_id, %admin)) { %trustFailCount++; continue; } //Skip zone bricks if(%db.isWaterBrick) continue; //Skip dead bricks if(%obj.isDead()) continue; //Set variables for the fill brick function $ND::FillBrickGroup = %obj.getGroup(); $ND::FillBrickClient = %obj.client; $ND::FillBrickBL_ID = %obj.getGroup().bl_id; $ND::FillBrickColorID = %obj.colorID; $ND::FillBrickColorFxID = %obj.colorFxID; $ND::FillBrickShapeFxID = %obj.shapeFxID; $ND::FillBrickRendering = %obj.isRendering(); $ND::FillBrickColliding = %obj.isColliding(); $ND::FillBrickRayCasting = %obj.isRayCasting(); $ND::FillBrickSubset = %obj.getDatablock().ndSubset; %box = %obj.getWorldBox(); %boxX1 = getWord(%box, 0); %boxY1 = getWord(%box, 1); %boxZ1 = getWord(%box, 2); %boxX2 = getWord(%box, 3); %boxY2 = getWord(%box, 4); %boxZ2 = getWord(%box, 5); %obj.delete(); %deleted = true; %cutCount++; $ND::FillBrickCount = 0; if((%boxX1 + 0.05) < %chunkX1) { ndFillAreaWithBricks( %boxX1 SPC %boxY1 SPC %boxZ1, %chunkX1 SPC %boxY2 SPC %boxZ2); } if((%boxX2 - 0.05) > %chunkX2) { ndFillAreaWithBricks( %chunkX2 SPC %boxY1 SPC %boxZ1, %boxX2 SPC %boxY2 SPC %boxZ2); } if((%boxY1 + 0.05) < %chunkY1) { ndFillAreaWithBricks( getMax(%boxX1, %chunkX1) SPC %boxY1 SPC %boxZ1, getMin(%boxX2, %chunkX2) SPC %chunkY1 SPC %boxZ2); } if((%boxY2 - 0.05) > %chunkY2) { ndFillAreaWithBricks( getMax(%boxX1, %chunkX1) SPC %chunkY2 SPC %boxZ1, getMin(%boxX2, %chunkX2) SPC %boxY2 SPC %boxZ2); } if((%boxZ1 + 0.05) < %chunkZ1) { ndFillAreaWithBricks( getMax(%boxX1, %chunkX1) SPC getMax(%boxY1, %chunkY1) SPC %boxZ1, getMin(%boxX2, %chunkX2) SPC getMin(%boxY2, %chunkY2) SPC %chunkZ1); } if((%boxZ2 - 0.05) > %chunkZ2) { ndFillAreaWithBricks( getMax(%boxX1, %chunkX1) SPC getMax(%boxY1, %chunkY1) SPC %chunkZ2, getMin(%boxX2, %chunkX2) SPC getMin(%boxY2, %chunkY2) SPC %boxZ2); } %bricksPlanted += $ND::FillBrickCount; } //Set next chunk index or break %currChunk++; if(%currChunkX++ >= %numChunksX) { %currChunkX = 0; if(%currChunkY++ >= %numChunksY) { %currChunkY = 0; if(%currChunkZ++ >= %numChunksZ) { %searchComplete = true; break; } } } } //Save chunk variables (scopes and slow object fields suck) %this.currChunk = %currChunk; %this.currChunkX = %currChunkX; %this.currChunkY = %currChunkY; %this.currChunkZ = %currChunkZ; %this.numChunksX = %numChunksX; %this.numChunksY = %numChunksY; %this.numChunksZ = %numChunksZ; %this.trustFailCount += %trustFailCount; %this.superCutCount += %cutCount; %this.superCutPlacedCount += %bricksPlanted; //If all chunks have been searched, start processing if(%searchComplete) { %this.finishSuperCut(); return; } //Tell the client which chunks we just processed if(%this.client && %this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } //Schedule next chunk %this.superCutSchedule = %this.schedule(30, tickSuperCutChunk); } //Finish super cut function ND_Selection::finishSuperCut(%this) { if(!%this.client) return; if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadEnd', ""); %s = %this.superCutCount == 1 ? "" : "s"; %msg = "\c6Deleted \c3" @ %this.superCutCount @ "\c6 Brick" @ %s @ "!"; if(%this.superCutPlacedCount > 0) { %s = %this.superCutPlacedCount == 1 ? "" : "s"; %msg = %msg @ "\n\c6Placed \c3" @ %this.superCutPlacedCount @ "\c6 new one" @ %s @ "."; } if(%this.trustFailCount > 0) %msg = %msg @ "\n\c3" @ %this.trustFailCount @ "\c6 missing trust."; commandToClient(%this.client, 'centerPrint', %msg, 12); %this.client.ndSetMode(NDM_BoxSelect); if(%this.client.fillBricksAfterSuperCut) { %this.client.fillBricksAfterSuperCut = false; if(%this.trustFailCount) messageClient(%this.client, '', "\c6Cannot run fill bricks, you do not have enough trust bricks already in the area."); else %this.client.doFillBricks(%this.client.NDFillBrickSubset); } } //Cancel super cut function ND_Selection::cancelSuperCut(%this) { cancel(%this.superCutSchedule); %this.deleteData(); } //Ghost bricks ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Spawn ghost bricks at a specific location function ND_Selection::spawnGhostBricks(%this, %position, %angleID) { %this.ghostMirrorX = false; %this.ghostMirrorY = false; %this.ghostMirrorZ = false; //Create group to hold the ghost bricks %this.ghostGroup = ND_GhostGroup(); //Scoping is broken for ghost bricks, make temp list of spawned clients to use later ndUpdateSpawnedClientList(); //Figure out correct increment to spawn no more than the max number of ghost bricks %max = %this.brickCount; %increment = 1; if(%max > $Pref::Server::ND::MaxGhostBricks) { if($Pref::Server::ND::ScatterGhostBricks) %increment = %max / $Pref::Server::ND::MaxGhostBricks; else %max = $Pref::Server::ND::MaxGhostBricks; } %ghostGroup = %this.ghostGroup; //Spawn ghost bricks for(%f = 0; %f < %max; %f += %increment) { %i = mFloor(%f); //Skip missing bricks if($NS[%this, "D", %i] == 0) continue; //Offset position %bPos = vectorAdd(ndRotateVector($NS[%this, "P", %i], %angleID), %position); //Rotate local angle id and get correct rotation value %bAngle = ($NS[%this, "R", %i] + %angleID ) % 4; switch(%bAngle) { case 0: %bRot = "1 0 0 0"; case 1: %bRot = "0 0 1 90.0002"; case 2: %bRot = "0 0 1 180"; case 3: %bRot = "0 0 -1 90.0002"; } //Spawn ghost brick %brick = new FxDTSBrick() { datablock = $NS[%this, "D", %i]; isPlanted = false; position = %bPos; rotation = %bRot; angleID = %bAngle; colorID = $NS[%this, "CO", %i]; printID = $NS[%this, "PR", %i]; //Used in shiftGhostBricks selectionIndex = %i; }; //Add ghost brick to ghost set %ghostGroup.add(%brick); //Scope ghost brick to all clients we found earlier for(%j = 0; %j < $ND::NumSpawnedClients; %j++) %brick.scopeToClient($ND::SpawnedClient[%j]); } //Update variables %this.ghostPosition = %position; %this.ghostAngleID = %angleID; //Change highlightbox to blue %this.highlightBox.color = "0.2 0.2 1 0.99"; %this.highlightBox.applyColors(); %this.updateHighlightBox(); } //Move ghost bricks to an offset position function ND_Selection::shiftGhostBricks(%this, %offset) { //Fit to grid %x = mFloatLength(getWord(%offset, 0) * 2, 0) / 2; %y = mFloatLength(getWord(%offset, 1) * 2, 0) / 2; %z = mFloatLength(getWord(%offset, 2) * 5, 0) / 5; if(%x == 0 && %y == 0 && %z == 0) return; //Update variables %this.ghostPosition = vectorAdd(%this.ghostPosition, %x SPC %y SPC %z); %this.updateHighlightBox(); //Update ghost bricks %this.updateGhostBricks(0, $Pref::Server::ND::InstantGhostBricks, 230); } //Rotate ghost bricks left/right function ND_Selection::rotateGhostBricks(%this, %direction, %useSelectionCenter) { //First brick is root brick %rootBrick = %this.ghostGroup.getObject(0); //Figure out the pivot and shift values if(%useSelectionCenter) { %pivot = %this.getGhostCenter(); %brickSizeX = %this.brickSizeX; %brickSizeY = %this.brickSizeY; } else { %pivot = %this.ghostPosition; %brickSizeX = %rootBrick.getDatablock().brickSizeX; %brickSizeY = %rootBrick.getDatablock().brickSizeY; } //Even x odd sized rectangles can't be rotated around their center to stay in the grid %shiftCorrect = "0 0 0"; if((%brickSizeX % 2) != (%brickSizeY % 2)) { if(%this.ghostAngleID % 2) %shiftCorrect = "-0.25 -0.25 0"; else %shiftCorrect = "0.25 0.25 0"; } //Get vector from pivot to root brick %pOffset = vectorSub(%rootBrick.getPosition(), %pivot); //Rotate offset vector 90 degrees %pOffset = ndRotateVector(%pOffset, %direction); //Add shift correction if(%direction % 2 != 0) %pOffset = vectorAdd(%pOffset, %shiftCorrect); //Update variables %this.ghostAngleID = (%this.ghostAngleID + %direction) % 4; %this.ghostPosition = vectorAdd(%pivot, %pOffset); %this.updateHighlightBox(); //Update ghost bricks %this.updateGhostBricks(0, $Pref::Server::ND::InstantGhostBricks, 230); } //Mirror ghost bricks on x,y,z axis function ND_Selection::mirrorGhostBricks(%this, %axis) { //Update variables if(%axis == 0) { %this.ghostMirrorX = !%this.ghostMirrorX; //Offset ghost so we end up in the same area if(%this.ghostMirrorX) %offset = (getWord(%this.rootToCenter, 0) * 2) @ " 0 0"; else %offset = (getWord(%this.rootToCenter, 0) * -2) @ " 0 0"; } else if(%axis == 1) { %this.ghostMirrorY = !%this.ghostMirrorY; //Offset ghost so we end up in the same area if(%this.ghostMirrorY) %offset = "0 " @ (getWord(%this.rootToCenter, 1) * 2) @ " 0"; else %offset = "0 " @ (getWord(%this.rootToCenter, 1) * -2) @ " 0"; } else { %this.ghostMirrorZ = !%this.ghostMirrorZ; //Offset ghost so we end up in the same area if(%this.ghostMirrorZ) %offset = "0 0 " @ getWord(%this.rootToCenter, 2) * 2; else %offset = "0 0 " @ getWord(%this.rootToCenter, 2) * -2; } //Double mirror is just a rotation if(%this.ghostMirrorX && %this.ghostMirrorY) { %this.ghostAngleID = (%this.ghostAngleID + 2) % 4; %this.ghostMirrorX = false; %this.ghostMirrorY = false; if(%axis == 0) %offset = (getWord(%this.rootToCenter, 0) * -2) @ " 0 0"; else %offset = "0 " @ (getWord(%this.rootToCenter, 1) * -2) @ " 0"; } //If pivot is whole selection, shift bricks to keep area if(%this.client.ndPivot) %this.ghostPosition = vectorAdd(%this.ghostPosition, ndRotateVector(%offset, %this.ghostAngleID)); %this.updateHighlightBox(); //Update ghost bricks %this.updateGhostBricks(0, $Pref::Server::ND::InstantGhostBricks, 230); } //Update some of the ghost bricks to the latest position/rotation function ND_Selection::updateGhostBricks(%this, %start, %count, %wait) { cancel(%this.ghostMoveSchedule); %max = %this.ghostGroup.getCount(); if(%max - %start > %count) { %max = %start + %count; //Start schedule to move remaining ghost bricks %this.ghostMoveSchedule = %this.schedule(%wait, updateGhostBricks, %max, $Pref::Server::ND::ProcessPerTick, 30); } %pos = %this.ghostPosition; %angle = %this.ghostAngleID; %ghostGroup = %this.ghostGroup; %mirrX = %this.ghostMirrorX; %mirrY = %this.ghostMirrorY; %mirrZ = %this.ghostMirrorZ; //Update the ghost bricks in this tick for(%i = %start; %i < %max; %i++) { %brick = %ghostGroup.getObject(%i); %j = %brick.selectionIndex; //Offset position %bPos = $NS[%this, "P", %j]; //Rotated local angle id %bAngle = $NS[%this, "R", %j]; //Apply mirror effects (ugh) %datablock = $NS[%this, "D", %j]; if(%mirrX) { //Mirror offset %bPos = -firstWord(%bPos) SPC restWords(%bPos); //Handle symmetries switch($ND::Symmetry[%datablock]) { //Asymmetric case 0: if(%db = $ND::SymmetryXDatablock[%datablock]) { %datablock = %db; %bAngle = (%bAngle + $ND::SymmetryXOffset[%datablock]) % 4; //Pair is made on X, so apply mirror logic for X afterwards if(%bAngle % 2 == 1) %bAngle = (%bAngle + 2) % 4; } //Do nothing for fully symmetric //X symmetric - rotate 180 degrees if brick is angled 90 or 270 degrees case 2: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 2) % 4; //Y symmetric - rotate 180 degrees if brick is angled 0 or 180 degrees case 3: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 2) % 4; //X+Y symmetric - rotate 90 degrees case 4: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 1) % 4; else %bAngle = (%bAngle + 3) % 4; //X-Y symmetric - rotate -90 degrees case 5: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 3) % 4; else %bAngle = (%bAngle + 1) % 4; } } else if(%mirrY) { //Mirror offset %bPos = getWord(%bPos, 0) SPC -getWord(%bPos, 1) SPC getWord(%bPos, 2); //Handle symmetries switch($ND::Symmetry[%datablock]) { //Asymmetric case 0: if(%db = $ND::SymmetryXDatablock[%datablock]) { %datablock = %db; %bAngle = (%bAngle + $ND::SymmetryXOffset[%datablock]) % 4; //Pair is made on X, so apply mirror logic for X afterwards if(%bAngle % 2 == 0) %bAngle = (%bAngle + 2) % 4; } //Do nothing for fully symmetric //X symmetric - rotate 180 degrees if brick is angled 90 or 270 degrees case 2: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 2) % 4; //Y symmetric - rotate 180 degrees if brick is angled 0 or 180 degrees case 3: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 2) % 4; //X+Y symmetric - rotate 90 degrees case 4: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 1) % 4; else %bAngle = (%bAngle + 3) % 4; //X-Y symmetric - rotate -90 degrees case 5: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 3) % 4; else %bAngle = (%bAngle + 1) % 4; } } if(%mirrZ) { //Mirror offset %bPos = getWords(%bPos, 0, 1) SPC -getWord(%bPos, 2); //Change datablock if asymmetric if(!$ND::SymmetryZ[%datablock]) { if(%db = $ND::SymmetryZDatablock[%datablock]) { %datablock = %db; %bAngle = (%bAngle + $ND::SymmetryZOffset[%datablock]) % 4; } } } //Apply datablock if(%brick.getDatablock() != %datablock) %brick.setDatablock(%datablock); //Rotate and add offset %bAngle = (%bAngle + %angle) % 4; %bPos = vectorAdd(%pos, ndRotateVector(%bPos, %angle)); switch(%bAngle) { case 0: %bRot = "1 0 0 0"; case 1: %bRot = "0 0 1 1.5708"; case 2: %bRot = "0 0 1 3.14150"; case 3: %bRot = "0 0 -1 1.5708"; } //Apply transform %brick.setTransform(%bPos SPC %bRot); } } //Delete ghost bricks function ND_Selection::deleteGhostBricks(%this) { if(!isObject(%this.ghostGroup)) return; cancel(%this.ghostMoveSchedule); %this.ghostGroup.tickDelete(); %this.ghostGroup = false; } //World box center for ghosted selection function ND_Selection::getGhostCenter(%this) { if(!isObject(%this.ghostGroup)) return "0 0 0"; %offset = %this.rootToCenter; if(%this.ghostMirrorX) %offset = -getWord(%offset, 0) SPC getWord(%offset, 1) SPC getWord(%offset, 2); else if(%this.ghostMirrorY) %offset = getWord(%offset, 0) SPC -getWord(%offset, 1) SPC getWord(%offset, 2); if(%this.ghostMirrorZ) %offset = getWord(%offset, 0) SPC getWord(%offset, 1) SPC -getWord(%offset, 2); return vectorAdd(%this.ghostPosition, ndRotateVector(%offset, %this.ghostAngleID)); } //World box for ghosted selection function ND_Selection::getGhostWorldBox(%this) { if(!isObject(%this.ghostGroup)) return "0 0 0 0 0 0"; %min = %this.minSize; %max = %this.maxSize; //Handle mirrors if(%this.ghostMirrorX) { %min = -firstWord(%min) SPC restWords(%min); %max = -firstWord(%max) SPC restWords(%max); } else if(%this.ghostMirrorY) { %min = getWord(%min, 0) SPC -getWord(%min, 1) SPC getWord(%min, 2); %max = getWord(%max, 0) SPC -getWord(%max, 1) SPC getWord(%max, 2); } if(%this.ghostMirrorZ) { %min = getWords(%min, 0, 1) SPC -getWord(%min, 2); %max = getWords(%max, 0, 1) SPC -getWord(%max, 2); } //Handle rotation %min = ndRotateVector(%min, %this.ghostAngleID); %max = ndRotateVector(%max, %this.ghostAngleID); //Get max values %minX = getMin(getWord(%min, 0), getWord(%max, 0)); %minY = getMin(getWord(%min, 1), getWord(%max, 1)); %minZ = getMin(getWord(%min, 2), getWord(%max, 2)); %maxX = getMax(getWord(%min, 0), getWord(%max, 0)); %maxY = getMax(getWord(%min, 1), getWord(%max, 1)); %maxZ = getMax(getWord(%min, 2), getWord(%max, 2)); %pos = %this.ghostPosition; return vectorAdd(%pos, %minX SPC %minY SPC %minZ) SPC vectorAdd(%pos, %maxX SPC %maxY SPC %maxZ); } //Planting bricks ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Start planting bricks! function ND_Selection::startPlant(%this, %position, %angleID, %forcePlant) { %this.forcePlant = %forcePlant; %this.plantSearchIndex = 0; %this.plantQueueIndex = 0; %this.plantQueueCount = 0; %this.plantSuccessCount = 0; %this.plantTrustFailCount = 0; %this.plantBlockedFailCount = 0; %this.plantMissingFailCount = 0; %this.undoGroup = new SimSet(); ND_ServerGroup.add(%this.undoGroup); //Reset mirror error list %client = %this.client; %countX = $NS[%client, "MXC"]; %countZ = $NS[%client, "MZC"]; for(%i = 0; %i < %countX; %i++) $NS[%client, "MXK", $NS[%client, "MXE", %i]] = ""; for(%i = 0; %i < %countZ; %i++) $NS[%client, "MZK", $NS[%client, "MZE", %i]] = ""; $NS[%client, "MZC"] = 0; $NS[%client, "MXC"] = 0; //Make list of spawned clients to scope bricks %this.numClients = 0; for(%i = 0; %i < ClientGroup.getCount(); %i++) { %cl = ClientGroup.getObject(%i); if(%cl.hasSpawnedOnce && isObject(%obj = %cl.getControlObject()) && vectorDist(%this.ghostPosition, %obj.getTransform()) < 10000) { $NS[%this, "CL", %this.numClients] = %cl; %this.numClients++; } } if($Pref::Server::ND::PlayMenuSounds && %this.brickCount > $Pref::Server::ND::ProcessPerTick * 10) messageClient(%this.client, 'MsgUploadStart', ""); %this.tickPlantSearch($Pref::Server::ND::ProcessPerTick, %position, %angleID); } //Go through the list of bricks until we find one that plants successfully function ND_Selection::tickPlantSearch(%this, %remainingPlants, %position, %angleID) { %start = %this.plantSearchIndex; %end = %start + %remainingPlants; if(%end > %this.brickCount) %end = %this.brickCount; %client = %this.client; if(isObject(%this.targetGroup)) { %group = %this.targetGroup; %bl_id = %this.targetBlid; } else { %group = %client.brickGroup.getId(); %bl_id = %client.bl_id; } %qCount = %this.plantQueueCount; %numClients = %this.numClients; for(%i = %start; %i < %end; %i++) { //Brick already placed if($NP[%this, %i]) continue; //Skip nonexistant bricks if($NS[%this, "D", %i] == 0) { $NP[%this, %i] = true; %this.plantMissingFailCount++; continue; } //Attempt to place brick %brick = ND_Selection::plantBrick(%this, %i, %position, %angleID, %group, %client, %bl_id); %plants++; if(%brick > 0) { //Success! Add connected bricks to plant queue %this.plantSuccessCount++; %this.undoGroup.add(%brick); $NP[%this, %i] = true; %conns = $NS[%this, "N", %i]; for(%j = 0; %j < %conns; %j++) { %id = $NS[%this, "C", %i, %j]; if(%id < %i && !$NP[%this, %id]) { %found = true; $NS[%this, "PQueue", %qCount] = %id; $NP[%this, %id] = true; %qCount++; } } //Instantly ghost the brick to all spawned clients (wow hacks) for(%j = 0; %j < %numClients; %j++) { %cl = $NS[%this, "CL", %j]; %brick.scopeToClient(%cl); %brick.clearScopeToClient(%cl); } //If we added bricks to plant queue, switch to second loop if(%found) { %this.plantSearchIndex = %i + 1; %this.plantQueueCount = %qCount; %this.tickPlantTree(%remainingPlants - %plants, %position, %angleID); return; } %lastPos = %brick.position; } else if(%brick == -1) { $NP[%this, %i] = true; %this.plantBlockedFailCount++; } else if(%brick == -2) { $NP[%this, %i] = true; %this.plantTrustFailCount++; } } %this.plantSearchIndex = %i; %this.plantQueueCount = %qCount; if(strLen(%lastPos)) serverPlay3D(BrickPlantSound, %lastPos); //Tell the client how far we got if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } if(%end < %this.brickCount && %this.plantSuccessCount < %this.brickCount) %this.plantSchedule = %this.schedule(30, tickPlantSearch, $Pref::Server::ND::ProcessPerTick, %position, %angleID); else %this.finishPlant(); } //Plant search has prepared a queue, plant all bricks in this queue and add their connected bricks aswell function ND_Selection::tickPlantTree(%this, %remainingPlants, %position, %angleID) { %start = %this.plantQueueIndex; %end = %start + %remainingPlants; %client = %this.client; if(isObject(%this.targetGroup)) { %group = %this.targetGroup; %bl_id = %this.targetBlid; } else { %group = %client.brickGroup.getId(); %bl_id = %client.bl_id; } %qCount = %this.plantQueueCount; %numClients = %this.numClients; %searchIndex = %this.plantSearchIndex; for(%i = %start; %i < %end; %i++) { //The queue is empty! Switch back to plant search. if(%i >= %qCount) { if(strLen(%lastPos)) serverPlay3D(BrickPlantSound, %lastPos); %this.plantQueueCount = %qCount; %this.plantQueueIndex = %i; %this.tickPlantSearch(%end - %i, %position, %angleID); return; } //Attempt to plant queued brick %bId = $NS[%this, "PQueue", %i]; //Skip nonexistant bricks if($NS[%this, "D", %i] == 0) { $NP[%this, %bId] = true; %this.plantMissingFailCount++; continue; } %brick = ND_Selection::plantBrick(%this, %bId, %position, %angleID, %group, %client, %bl_id); if(%brick > 0) { //Success! Add connected bricks to plant queue %this.plantSuccessCount++; %this.undoGroup.add(%brick); $NP[%this, %bId] = true; %conns = $NS[%this, "N", %bId]; for(%j = 0; %j < %conns; %j++) { %id = $NS[%this, "C", %bId, %j]; if(%id < %searchIndex && !$NP[%this, %id]) { $NS[%this, "PQueue", %qCount] = %id; $NP[%this, %id] = true; %qCount++; } } %lastPos = %brick.position; //Instantly ghost the brick to all spawned clients (wow hacks) for(%j = 0; %j < %numClients; %j++) { %cl = $NS[%this, "CL", %j]; %brick.scopeToClient(%cl); %brick.clearScopeToClient(%cl); } } else if(%brick == -1) { %this.plantBlockedFailCount++; $NP[%this, %bId] = true; } else if(%brick == -2) { %this.plantTrustFailCount++; $NP[%this, %bId] = true; } } if(strLen(%lastPos)) serverPlay3D(BrickPlantSound, %lastPos); //Tell the client how far we got if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } %this.plantQueueCount = %qCount; %this.plantQueueIndex = %i; %this.plantSchedule = %this.schedule(30, tickPlantTree, $Pref::Server::ND::ProcessPerTick, %position, %angleID); } //Attempt to plant brick with id %i //Returns brick if planted, 0 if floating, -1 if blocked, -2 if trust failure function ND_Selection::plantBrick(%this, %i, %position, %angleID, %brickGroup, %client, %bl_id) { //Offset position %bPos = $NS[%this, "P", %i]; //Local angle id %bAngle = $NS[%this, "R", %i]; //Apply mirror effects (ugh) %datablock = $NS[%this, "D", %i]; %mirrX = %this.ghostMirrorX; %mirrY = %this.ghostMirrorY; %mirrZ = %this.ghostMirrorZ; if(%mirrX) { //Mirror offset %bPos = -firstWord(%bPos) SPC restWords(%bPos); //Handle symmetries switch($ND::Symmetry[%datablock]) { //Asymmetric case 0: if(%db = $ND::SymmetryXDatablock[%datablock]) { %datablock = %db; %bAngle = (%bAngle + $ND::SymmetryXOffset[%datablock]) % 4; //Pair is made on X, so apply mirror logic for X afterwards if(%bAngle % 2 == 1) %bAngle = (%bAngle + 2) % 4; } else { //Add datablock to list of mirror problems if(!$NS[%client, "MXK", %datablock]) { %id = $NS[%client, "MXC"]; $NS[%client, "MXC"]++; $NS[%client, "MXE", %id] = %datablock; $NS[%client, "MXK", %datablock] = true; } } //Do nothing for fully symmetric //X symmetric - rotate 180 degrees if brick is angled 90 or 270 degrees case 2: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 2) % 4; //Y symmetric - rotate 180 degrees if brick is angled 0 or 180 degrees case 3: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 2) % 4; //X+Y symmetric - rotate 90 degrees case 4: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 1) % 4; else %bAngle = (%bAngle + 3) % 4; //X-Y symmetric - rotate -90 degrees case 5: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 3) % 4; else %bAngle = (%bAngle + 1) % 4; } } else if(%mirrY) { //Mirror offset %bPos = getWord(%bPos, 0) SPC -getWord(%bPos, 1) SPC getWord(%bPos, 2); //Handle symmetries switch($ND::Symmetry[%datablock]) { //Asymmetric case 0: if(%db = $ND::SymmetryXDatablock[%datablock]) { %datablock = %db; %bAngle = (%bAngle + $ND::SymmetryXOffset[%datablock]) % 4; //Pair is made on X, so apply mirror logic for X afterwards if(%bAngle % 2 == 0) %bAngle = (%bAngle + 2) % 4; } else { //Add datablock to list of mirror problems if(!$NS[%client, "MXK", %datablock]) { %id = $NS[%client, "MXC"]; $NS[%client, "MXC"]++; $NS[%client, "MXE", %id]= %datablock; $NS[%client, "MXK", %datablock] = true; } } //Do nothing for fully symmetric //X symmetric - rotate 180 degrees if brick is angled 90 or 270 degrees case 2: if(%bAngle % 2 == 0) %bAngle = (%bAngle + 2) % 4; //Y symmetric - rotate 180 degrees if brick is angled 0 or 180 degrees case 3: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 2) % 4; //X+Y symmetric - rotate 90 degrees case 4: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 1) % 4; else %bAngle = (%bAngle + 3) % 4; //X-Y symmetric - rotate -90 degrees case 5: if(%bAngle % 2 == 1) %bAngle = (%bAngle + 3) % 4; else %bAngle = (%bAngle + 1) % 4; } } if(%mirrZ) { //Mirror offset %bPos = getWords(%bPos, 0, 1) SPC -getWord(%bPos, 2); //Change datablock if asymmetric if(!$ND::SymmetryZ[%datablock]) { if(%db = $ND::SymmetryZDatablock[%datablock]) { %datablock = %db; %bAngle = (%bAngle + $ND::SymmetryZOffset[%datablock]) % 4; } else { //Add datablock to list of mirror problems if(!$NS[%client, "MZK", %datablock]) { %id = $NS[%client, "MZC"]; $NS[%client, "MZC"]++; $NS[%client, "MZE", %id]= %datablock; $NS[%client, "MZK", %datablock] = true; } } } } //Rotate and add offset %bAngle = (%bAngle + %angleID) % 4; %bPos = vectorAdd(%position, ndRotateVector(%bPos, %angleID)); switch(%bAngle) { case 0: %bRot = "1 0 0 0"; case 1: %bRot = "0 0 1 90.0002"; case 2: %bRot = "0 0 1 180"; case 3: %bRot = "0 0 -1 90.0002"; } //Attempt to plant brick %brick = new FxDTSBrick() { datablock = %datablock; isPlanted = true; client = %client; position = %bPos; rotation = %bRot; angleID = %bAngle; colorID = $NS[%this, "CO", %i]; colorFxID = $NS[%this, "CF", %i]; printID = $NS[%this, "PR", %i]; }; //This will call ::onLoadPlant instead of ::onPlant %prev1 = $Server_LoadFileObj; %prev2 = $LastLoadedBrick; $Server_LoadFileObj = %brick; $LastLoadedBrick = %brick; //Add to brickgroup %brickGroup.add(%brick); //Attempt plant %error = %brick.plant(); //Restore variable $Server_LoadFileObj = %prev1; $LastLoadedBrick = %prev2; if(!isObject(%brick)) return -1; if(%error == 2) { //Do we plant floating bricks? if(%this.forcePlant) { //Brick is floating. Pretend it is supported by terrain %brick.isBaseplate = true; //Make engine recompute distance from ground to apply it %brick.willCauseChainKill(); } else { %brick.delete(); return 0; } } else if(%error) { %brick.delete(); return -1; } //Check for trust %downCount = %brick.getNumDownBricks(); if(!%client.isAdmin || !$Pref::Server::ND::AdminTrustBypass2) { for(%j = 0; %j < %downCount; %j++) { if(!ndFastTrustCheck(%brick.getDownBrick(%j), %bl_id, %brickGroup)) { %brick.delete(); return -2; } } %upCount = %brick.getNumUpBricks(); for(%j = 0; %j < %upCount; %j++) { if(!ndFastTrustCheck(%brick.getUpBrick(%j), %bl_id, %brickGroup)) { %brick.delete(); return -2; } } } else if(!%downCount) %upCount = %brick.getNumUpBricks(); //Finished trust check if(%downCount) %brick.stackBL_ID = %brick.getDownBrick(0).stackBL_ID; else if(%upCount) %brick.stackBL_ID = %brick.getUpBrick(0).stackBL_ID; else %brick.stackBL_ID = %bl_id; %brick.trustCheckFinished(); //Apply special settings %brick.setRendering(!$NS[%this, "NR", %i]); %brick.setRaycasting(!$NS[%this, "NRC", %i]); %brick.setColliding(!$NS[%this, "NC", %i]); %brick.setShapeFx($NS[%this, "SF", %i]); //Apply events if(%numEvents = $NS[%this, "EN", %i]) { %brick.numEvents = %numEvents; %brick.implicitCancelEvents = 0; for(%j = 0; %j < %numEvents; %j++) { %brick.eventEnabled[%j] = $NS[%this, "EE", %i, %j]; %brick.eventDelay[%j] = $NS[%this, "ED", %i, %j]; %inputIdx = $NS[%this, "EII", %i, %j]; %brick.eventInput[%j] = $NS[%this, "EI", %i, %j]; %brick.eventInputIdx[%j] = %inputIdx; %target = $NS[%this, "ET", %i, %j]; %targetIdx = $NS[%this, "ETI", %i, %j]; if(%targetIdx == -1) { %nt = $NS[%this, "ENT", %i, %j]; %brick.eventNT[%j] = %nt; } %brick.eventTarget[%j] = %target; %brick.eventTargetIdx[%j] = %targetIdx; %output = $NS[%this, "EO", %i, %j]; %outputIdx = $NS[%this, "EOI", %i, %j]; //Only rotate outputs for named bricks if they are selected if(%targetIdx >= 0 || $NS[%this, "HN", %nt]) { //Rotate fireRelay events switch$(%output) { case "fireRelayUp": %dir = 0; case "fireRelayDown": %dir = 1; case "fireRelayNorth": %dir = 2; case "fireRelayEast": %dir = 3; case "fireRelaySouth": %dir = 4; case "fireRelayWest": %dir = 5; default: %dir = -1; } if(%dir >= 0) { %rotated = ndTransformDirection(%dir, %angleID, %mirrX, %mirrY, %mirrZ); %outputIdx += %rotated - %dir; switch(%rotated) { case 0: %output = "fireRelayUp"; case 1: %output = "fireRelayDown"; case 2: %output = "fireRelayNorth"; case 3: %output = "fireRelayEast"; case 4: %output = "fireRelaySouth"; case 5: %output = "fireRelayWest"; } } } %brick.eventOutput[%j] = %output; %brick.eventOutputIdx[%j] = %outputIdx; %brick.eventOutputAppendClient[%j] = $NS[%this, "EOC", %i, %j]; //Why does this need to be so complicated? if(%targetIdx >= 0) %targetClass = getWord($InputEvent_TargetListfxDtsBrick_[%inputIdx], %targetIdx * 2 + 1); else %targetClass = "FxDTSBrick"; %paramList = $OutputEvent_ParameterList[%targetClass, %outputIdx]; %paramCount = getFieldCount(%paramList); for(%k = 0; %k < %paramCount; %k++) { %param = $NS[%this, "EP", %i, %j, %k]; //Only rotate outputs for named bricks if they are selected if(%targetIdx >= 0 || $NS[%this, "HN", %nt]) { %paramType = getField(%paramList, %k); switch$(getWord(%paramType, 0)) { case "vector": //Apply mirror effects if(%mirrX) %param = -firstWord(%param) SPC restWords(%param); else if(%mirrY) %param = getWord(%param, 0) SPC -getWord(%param, 1) SPC getWord(%param, 2); if(%mirrZ) %param = getWord(%param, 0) SPC getWord(%param, 1) SPC -getWord(%param, 2); %param = ndRotateVector(%param, %angleID); case "list": %value = getWord(%paramType, %param * 2 + 1); switch$(%value) { case "Up": %dir = 0; case "Down": %dir = 1; case "North": %dir = 2; case "East": %dir = 3; case "South": %dir = 4; case "West": %dir = 5; default: %dir = -1; } if(%dir >= 0) { switch(ndTransformDirection(%dir, %angleID, %mirrX, %mirrY, %mirrZ)) { case 0: %value = "Up"; case 1: %value = "Down"; case 2: %value = "North"; case 3: %value = "East"; case 4: %value = "South"; case 5: %value = "West"; } for(%l = 1; %l < getWordCount(%paramType); %l += 2) { if(getWord(%paramType, %l) $= %value) { %param = getWord(%paramType, %l + 1); break; } } } } } %brick.eventOutputParameter[%j, %k + 1] = %param; } } } setCurrentQuotaObject(getQuotaObjectFromClient(%client)); if((%tmp = $NS[%this, "NT", %i]) !$= "") %brick.setNTObjectName(%tmp); if(%tmp = $NS[%this, "LD", %i]) %brick.setLight(%tmp, %client); if(%tmp = $NS[%this, "ED", %i]) { %dir = ndTransformDirection($NS[%this, "ER", %i], %angleID, %mirrX, %mirrY, %mirrZ); %brick.emitterDirection = %dir; %brick.setEmitter(%tmp, %client); } if(%tmp = $NS[%this, "ID", %i]) { %pos = ndTransformDirection($NS[%this, "IP", %i], %angleID, %mirrX, %mirrY, %mirrZ); %dir = ndTransformDirection($NS[%this, "IR", %i], %angleID, %mirrX, %mirrY, %mirrZ); %brick.itemPosition = %pos; %brick.itemDirection = %dir; %brick.itemRespawnTime = $NS[%this, "IT", %i]; %brick.setItem(%tmp, %client); } if(%tmp = $NS[%this, "VD", %i]) { %brick.reColorVehicle = $NS[%this, "VC", %i]; %brick.setVehicle(%tmp, %client); } if(%tmp = $NS[%this, "MD", %i]) %brick.setSound(%tmp, %client); return %brick; } //Finished planting all the bricks! function ND_Selection::finishPlant(%this) { //Report mirror errors if($NS[%this.client, "MXC"] > 0 || $NS[%this.client, "MZC"] > 0) messageClient(%this.client, '', "\c6Some bricks were probably mirrored incorrectly. Say \c3/mirErrors\c6 to find out more."); %count = %this.brickCount; %planted = %this.plantSuccessCount; %blocked = %this.plantBlockedFailCount; %trusted = %this.plantTrustFailCount; %missing = %this.plantMissingFailCount; %floating = %count - %planted - %blocked - %trusted - %missing; %s = %this.plantSuccessCount == 1 ? "" : "s"; %message = "\c6Planted \c3" @ %this.plantSuccessCount @ "\c6 / \c3" @ %count @ "\c6 Brick" @ %s @ "!"; if(%trusted) %message = %message @ "\n\c3" @ %trusted @ "\c6 missing trust."; if(%blocked) %message = %message @ "\n\c3" @ %blocked @ "\c6 blocked."; if(%floating) %message = %message @ "\n\c3" @ %floating @ "\c6 floating."; if(%missing) %message = %message @ "\n\c3" @ %missing @ "\c6 missing Datablock."; commandToClient(%this.client, 'centerPrint', %message, 4); if($Pref::Server::ND::PlayMenuSounds && %planted && %this.brickCount > $Pref::Server::ND::ProcessPerTick * 10) messageClient(%this.client, 'MsgProcessComplete', ""); deleteVariables("$NP" @ %this @ "_*"); if(%planted) { %this.undoGroup.brickCount = %this.undoGroup.getCount(); %this.client.undoStack.push(%this.undoGroup TAB "ND_PLANT"); } else %this.undoGroup.delete(); %this.client.ndSetMode(NDM_PlantCopy); } //Cancel planting bricks function ND_Selection::cancelPlanting(%this) { cancel(%this.plantSchedule); deleteVariables("$NP" @ %this @ "_*"); if(%this.plantSuccessCount) { %this.undoGroup.brickCount = %this.undoGroup.getCount(); %this.client.undoStack.push(%this.undoGroup TAB "ND_PLANT"); } else %this.undoGroup.delete(); } //Fill Colors ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Start filling bricks with a specific color function ND_Selection::startFillColor(%this, %mode, %colorID) { %this.paintIndex = 0; %this.paintFailCount = 0; %this.paintSuccessCount = 0; //Create undo group %this.undoGroup = new ScriptObject(ND_UndoGroupPaint) { paintType = %mode; brickCount = 0; client = %this.client; }; ND_ServerGroup.add(%this.undoGroup); %this.tickFillColor(%mode, %colorID); } //Tick filling bricks with a specific color function ND_Selection::tickFillColor(%this, %mode, %colorID) { cancel(%this.fillColorSchedule); %start = %this.paintIndex; %end = %start + $Pref::Server::ND::ProcessPerTick; if(%end > %this.brickCount) %end = %this.brickCount; %admin = %this.client.isAdmin; %group2 = %this.client.brickGroup.getId(); %bl_id = %this.client.bl_id; %paintCount = %this.paintSuccessCount; %failCount = %this.paintFailCount; %undoCount = %this.undoGroup.brickCount; %clientId = %this.client; %undoId = %this.undoGroup; for(%i = %start; %i < %end; %i++) { if(isObject(%brick = $NS[%this, "B", %i])) { if(ndTrustCheckModify(%brick, %group2, %bl_id, %admin)) { //Color brick switch(%mode) { case 0: //Don't change to same value if(%brick.colorID == %colorID) continue; //Write previous value to undo array $NU[%clientId, %undoId, "V", %paintCount] = %brick.colorID; %brick.setColor(%colorID); //Update selection data $NS[%this, "CO", $NS[%this, "I", %brick]] = %colorID; case 1: //Check whether brick is highlighted if($NDHN[%brick]) { //Don't change to same value if($NDHF[%brick] == %colorID) continue; //Write previous value to undo array $NU[%clientId, %undoId, "V", %paintCount] = $NDHF[%brick]; //If we're highlighted, change the original color instead $NDHF[%brick] = %colorID; } else { //Don't change to same value if(%brick.colorFxID == %colorID) continue; //Write previous value to undo array $NU[%clientId, %undoId, "V", %paintCount] = %brick.colorFxID; %brick.setColorFx(%colorID); } //Update selection data $NS[%this, "CF", $NS[%this, "I", %brick]] = %colorID; case 2: //Don't change to same value if(%brick.shapeFxID == %colorID) continue; //Write previous value to undo array $NU[%clientId, %undoId, "V", %paintCount] = %brick.shapeFxID; %brick.setShapeFx(%colorID); //Update selection data $NS[%this, "SF", $NS[%this, "I", %brick]] = %colorID; } $NU[%clientId, %undoId, "B", %paintCount] = %brick; %paintCount++; } else %failCount++; } } %this.paintIndex = %i; %this.paintSuccessCount = %paintCount; %this.paintFailCount = %failCount; %this.undoGroup.brickCount = %paintCount; //Tell the client how much we painted this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } if(%i >= %this.brickCount) %this.finishFillColor(); else %this.fillColorSchedule = %this.schedule(30, tickFillColor, %mode, %colorID); } //Finish filling color function ND_Selection::finishFillColor(%this) { %s = %this.undoGroup.brickCount == 1 ? "" : "s"; %msg = "\c6Painted \c3" @ %this.undoGroup.brickCount @ "\c6 Brick" @ %s @ "!"; if(%this.paintFailCount > 0) %msg = %msg @ "\n\c3" @ %this.paintFailCount @ "\c6 missing trust."; commandToClient(%this.client, 'centerPrint', %msg, 8); if(%this.undoGroup.brickCount) %this.client.undoStack.push(%this.undoGroup TAB "ND_PAINT"); else %this.undoGroup.delete(); %this.client.ndSetMode(NDM_FillColor); } //Cancel filling color function ND_Selection::cancelFillColor(%this) { cancel(%this.fillColorSchedule); if(%this.undoGroup.brickCount) %this.client.undoStack.push(%this.undoGroup TAB "ND_PAINT"); else %this.undoGroup.delete(); } //Fill Wrench ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Start applying wrench settings to all bricks function ND_Selection::startFillWrench(%this, %data) { %valid = false; %this.fillWrenchName = false; %this.fillWrenchLight = false; %this.fillWrenchEmitter = false; %this.fillWrenchEmitterDir = false; %this.fillWrenchItem = false; %this.fillWrenchItemPos = false; %this.fillWrenchItemDir = false; %this.fillWrenchItemTime = false; %this.fillWrenchRaycasting = false; %this.fillWrenchCollision = false; %this.fillWrenchRendering = false; //Verify and save data %fieldCount = getFieldCount(%data); for(%i = 0; %i < %fieldCount; %i++) { %field = getField(%data, %i); %type = getWord(%field, 0); %value = trim(restWords(%field)); switch$(%type) { case "N": %this.fillWrenchName = true; %this.fillWrenchNameValue = getSafeVariableName(%value); %valid = true; case "LDB": if((isObject(%value) && %value.getClassName() $= "FxLightData" && %value.uiName !$= "") || %value == 0) { %this.fillWrenchLight = true; %this.fillWrenchLightValue = %value; %valid = true; } else messageClient(%this.client, '', "\c6Fill wrench error - Invalid light datablock " @ %value); case "EDB": if((isObject(%value) && %value.getClassName() $= "ParticleEmitterData" && %value.uiName !$= "") || %value == 0) { %this.fillWrenchEmitter = true; %this.fillWrenchEmitterValue = %value; %valid = true; } else messageClient(%this.client, '', "\c6Fill wrench error - Invalid emitter datablock " @ %value); case "EDIR": if(%value >= 0 && %value <= 5) { %this.fillWrenchEmitterDir = true; %this.fillWrenchEmitterDirValue = %value; %valid = true; } else messageClient(%this.client, '', "\c6Fill wrench error - Invalid emitter direction " @ %value); case "IDB": if((isObject(%value) && %value.getClassName() $= "ItemData" && %value.uiName !$= "") || %value == 0) { %this.fillWrenchItem = true; %this.fillWrenchItemValue = %value; %valid = true; } else messageClient(%this.client, '', "\c6Fill wrench error - Invalid item datablock " @ %value); case "IPOS": if(%value >= 0 && %value <= 5) { %this.fillWrenchItemPos = true; %this.fillWrenchItemPosValue = %value; %valid = true; } else messageClient(%this.client, '', "\c6Fill wrench error - Invalid item position " @ %value); case "IDIR": if(%value >= 2 && %value <= 5) { %this.fillWrenchItemDir = true; %this.fillWrenchItemDirValue = %value; %valid = true; } else messageClient(%this.client, '', "\c6Fill wrench error - Invalid item direction " @ %value); case "IRT": %this.fillWrenchItemTime = true; %this.fillWrenchItemTimeValue = mFloor(%value) * 1000; %valid = true; case "RC": %this.fillWrenchRaycasting = true; %this.fillWrenchRaycastingValue = %value; %valid = true; case "C": %this.fillWrenchCollision = true; %this.fillWrenchCollisionValue = %value; %valid = true; case "R": %this.fillWrenchRendering = true; %this.fillWrenchRenderingValue = %value; %valid = true; default: messageClient(%this.client, '', "\c6Fill wrench error - Invalid field " @ %type); } } if(!%valid) { messageClient(%this.client, '', "\c6Fill wrench error - No data to apply?"); %this.cancelFillWrench(); %this.client.ndSetMode(%this.client.ndLastSelectMode); return; } %this.wrenchIndex = 0; %this.wrenchFailCount = 0; %this.wrenchSuccessCount = 0; //Create undo group %this.undoGroup = new ScriptObject(ND_UndoGroupWrench) { fillWrenchName = %this.fillWrenchName; fillWrenchLight = %this.fillWrenchLight; fillWrenchEmitter = %this.fillWrenchEmitter; fillWrenchEmitterDir = %this.fillWrenchEmitterDir; fillWrenchItem = %this.fillWrenchItem; fillWrenchItemPos = %this.fillWrenchItemPos; fillWrenchItemDir = %this.fillWrenchItemDir; fillWrenchItemTime = %this.fillWrenchItemTime; fillWrenchRaycasting = %this.fillWrenchRaycasting; fillWrenchCollision = %this.fillWrenchCollision; fillWrenchRendering = %this.fillWrenchRendering; brickCount = 0; client = %this.client; }; ND_ServerGroup.add(%this.undoGroup); %this.tickFillWrench(); } //Tick applying wrench settings to all bricks function ND_Selection::tickFillWrench(%this) { cancel(%this.fillWrenchSchedule); %start = %this.wrenchIndex; %end = %start + $Pref::Server::ND::ProcessPerTick; if(%end > %this.brickCount) %end = %this.brickCount; %client = %this.client; %admin = %this.client.isAdmin; %group2 = %client.brickGroup.getId(); %bl_id = %client.bl_id; %wrenchCount = %this.wrenchSuccessCount; %failCount = %this.wrenchFailCount; %undoCount = %this.undoGroup.brickCount; %clientId = %this.client; %undoId = %this.undoGroup; setCurrentQuotaObject(getQuotaObjectFromClient(%client)); %fillWrenchName = %this.fillWrenchName; %fillWrenchLight = %this.fillWrenchLight; %fillWrenchEmitter = %this.fillWrenchEmitter; %fillWrenchEmitterDir = %this.fillWrenchEmitterDir; %fillWrenchItem = %this.fillWrenchItem; %fillWrenchItemPos = %this.fillWrenchItemPos; %fillWrenchItemDir = %this.fillWrenchItemDir; %fillWrenchItemTime = %this.fillWrenchItemTime; %fillWrenchRaycasting = %this.fillWrenchRaycasting; %fillWrenchCollision = %this.fillWrenchCollision; %fillWrenchRendering = %this.fillWrenchRendering; %fillWrenchNameValue = %this.fillWrenchNameValue; %fillWrenchLightValue = %this.fillWrenchLightValue; %fillWrenchEmitterValue = %this.fillWrenchEmitterValue; %fillWrenchEmitterDirValue = %this.fillWrenchEmitterDirValue; %fillWrenchItemValue = %this.fillWrenchItemValue; %fillWrenchItemPosValue = %this.fillWrenchItemPosValue; %fillWrenchItemDirValue = %this.fillWrenchItemDirValue; %fillWrenchItemTimeValue = %this.fillWrenchItemTimeValue; %fillWrenchRaycastingValue = %this.fillWrenchRaycastingValue; %fillWrenchCollisionValue = %this.fillWrenchCollisionValue; %fillWrenchRenderingValue = %this.fillWrenchRenderingValue; for(%i = %start; %i < %end; %i++) { if(isObject(%brick = $NS[%this, "B", %i])) { if(ndTrustCheckModify(%brick, %group2, %bl_id, %admin)) { %undoRequired = false; //Apply wrench settings if(%fillWrenchName) { %curr = getSubStr(%brick.getName(), 1, 254); $NU[%clientId, %undoId, "N", %undoCount] = %curr; if(%curr !$= %fillWrenchNameValue) { %brick.setNTObjectName(%fillWrenchNameValue); %undoRequired = true; } } if(%fillWrenchLight) { if(%tmp = %brick.light | 0) %curr = %tmp.getDatablock(); else %curr = 0; $NU[%clientId, %undoId, "LDB", %undoCount] = %curr; if(%curr != %fillWrenchLightValue) { %brick.setLight(%fillWrenchLightValue, %client); %undoRequired = true; } } if(%fillWrenchEmitter) { if(%tmp = %brick.emitter | 0) %curr = %tmp.getEmitterDatablock(); else if(%tmp = %brick.oldEmitterDB | 0) %curr = %tmp; else %curr = 0; $NU[%clientId, %undoId, "EDB", %undoCount] = %curr; if(%curr != %fillWrenchEmitterValue) { %brick.setEmitter(%fillWrenchEmitterValue, %client); %undoRequired = true; } } if(%fillWrenchEmitterDir) { %curr = %brick.emitterDirection; $NU[%clientId, %undoId, "EDIR", %undoCount] = %curr; if(%curr != %fillWrenchEmitterDirValue) { %brick.setEmitterDirection(%fillWrenchEmitterDirValue); %undoRequired = true; } } if(%fillWrenchItem) { if(%tmp = %brick.item | 0) %curr = %tmp.getDatablock(); else %curr = 0; $NU[%clientId, %undoId, "IDB", %undoCount] = %curr; if(%curr != %fillWrenchItemValue) { %brick.setItem(%fillWrenchItemValue, %client); %undoRequired = true; } } if(%fillWrenchItemPos) { %curr = %brick.itemPosition; $NU[%clientId, %undoId, "IPOS", %undoCount] = %curr; if(%curr != %fillWrenchItemPosValue) { %brick.setItemPosition(%fillWrenchItemPosValue); %undoRequired = true; } } if(%fillWrenchItemDir) { %curr = %brick.itemPosition; $NU[%clientId, %undoId, "IDIR", %undoCount] = %curr; if(%curr != %fillWrenchItemDirValue) { %brick.setItemDirection(%fillWrenchItemDirValue); %undoRequired = true; } } if(%fillWrenchItemTime) { %curr = %brick.itemRespawnTime; $NU[%clientId, %undoId, "IRT", %undoCount] = %curr; if(%curr != %fillWrenchItemTimeValue) { %brick.setItemRespawnTime(%fillWrenchItemTimeValue); %undoRequired = true; } } if(%fillWrenchRaycasting) { %curr = %brick.isRaycasting(); $NU[%clientId, %undoId, "RC", %undoCount] = %curr; if(%curr != %fillWrenchRaycastingValue) { %brick.setRaycasting(%fillWrenchRaycastingValue); %undoRequired = true; } } if(%fillWrenchCollision) { %curr = %brick.isColliding(); $NU[%clientId, %undoId, "C", %undoCount] = %curr; if(%curr != %fillWrenchCollisionValue) { %brick.setColliding(%fillWrenchCollisionValue); %undoRequired = true; } } if(%fillWrenchRendering) { %curr = %brick.isRendering(); $NU[%clientId, %undoId, "R", %undoCount] = %curr; if(%curr != %fillWrenchRenderingValue) { //Copy emitter ...? if(!%fillWrenchRenderingValue && (%tmp = %brick.emitter | 0)) %emitter = %tmp.getEmitterDatablock(); else %emitter = 0; %brick.setRendering(%fillWrenchRenderingValue); %undoRequired = true; if(!%fillWrenchRenderingValue && %emitter) %brick.setEmitter(%emitter); } } if(%undoRequired) { $NU[%clientId, %undoId, "B", %undoCount] = %brick; %undoCount++; } %wrenchCount++; } else %failCount++; } } clearCurrentQuotaObject(); %this.wrenchIndex = %i; %this.wrenchSuccessCount = %wrenchCount; %this.wrenchFailCount = %failCount; %this.undoGroup.brickCount = %undoCount; //Tell the client how much we wrenched this tick if(%client.ndLastMessageTime + 0.1 < $Sim::Time) { %client.ndUpdateBottomPrint(); %client.ndLastMessageTime = $Sim::Time; } if(%i >= %this.brickCount) %this.finishFillWrench(); else %this.fillWrenchSchedule = %this.schedule(30, tickFillWrench); } //Finish wrenching function ND_Selection::finishFillWrench(%this) { %s = %this.undoGroup.brickCount == 1 ? "" : "s"; %msg = "\c6Applied changes to \c3" @ %this.undoGroup.brickCount @ "\c6 Brick" @ %s @ "!"; if(%this.wrenchFailCount > 0) %msg = %msg @ "\n\c3" @ %this.wrenchFailCount @ "\c6 missing trust."; commandToClient(%this.client, 'centerPrint', %msg, 8); if(%this.undoGroup.brickCount) %this.client.undoStack.push(%this.undoGroup TAB "ND_WRENCH"); else %this.undoGroup.delete(); %this.client.ndSetMode(%this.client.ndLastSelectMode); } //Cancel wrenching function ND_Selection::cancelFillWrench(%this) { cancel(%this.fillWrenchSchedule); if(%this.undoGroup.brickCount) %this.client.undoStack.push(%this.undoGroup TAB "ND_WRENCH"); else %this.undoGroup.delete(); } //Saving bricks ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Begin saving function ND_Selection::startSaving(%this, %filePath) { //Open file %this.saveFilePath = %filePath; %this.saveFile = new FileObject(); if(!%this.saveFile.openForWrite(%filePath)) return false; //Write file header %this.saveFile.writeLine("Do not modify this file at all. You will break it."); %this.saveFile.writeLine("1"); %this.saveFile.writeLine("Saved by " @ %this.client.name @ " (" @ %this.client.bl_id @ ") at " @ getDateTime()); //Write colorset for(%i = 0; %i < 64; %i++) %this.saveFile.writeLine(getColorIDTable(%i)); //Write line count %this.saveFile.writeLine("Linecount " @ %this.brickCount); if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); //Schedule first tick %this.saveStage = 0; %this.saveIndex = 0; %this.saveSchedule = %this.schedule(30, tickSaveBricks); return true; } //Save some bricks function ND_Selection::tickSaveBricks(%this) { cancel(%this.saveSchedule); //Get bounds for this tick %start = %this.saveIndex; %end = %start + $Pref::Server::ND::ProcessPerTick * 2; if(%end > %this.brickCount) %end = %this.brickCount; %file = %this.saveFile; //Save bricks for(%i = %start; %i < %end; %i++) { %data = $NS[%this, "D", %i]; //Get correct print texture if(%data.hasPrint) { %fileName = getPrintTexture($NS[%this, "PR", %i]); %fileBase = fileBase(%fileName); %path = filePath(%fileName); if(%path !$= "" && %fileName !$= "base/data/shapes/bricks/brickTop.png") { %dirName = getSubStr(%path, 8, 999); %posA = strStr(%dirName, "_"); %posB = strPos(%dirName, "_", %posA + 1); %aspectRatio = getSubStr(%dirName, %posA + 1, %posB - %posA - 1); %printTexture = %aspectRatio @ "/" @ %fileBase; } else %printTexture = "/"; } else %printTexture = ""; //Write brick data %file.writeLine(%data.uiName @ "\"" SPC vectorAdd($NS[%this, "P", %i], %this.rootPosition) SPC $NS[%this, "R", %i] SPC 0 SPC $NS[%this, "CO", %i] SPC %printTexture SPC $NS[%this, "CF", %i] * 1 SPC $NS[%this, "SF", %i] * 1 SPC !$NS[%this, "NRC", %i] SPC !$NS[%this, "NC", %i] SPC !$NS[%this, "NR", %i] ); //Write brick name if((%tmp = $NS[%this, "NT", %i]) !$= "") %file.writeLine("+-NTOBJECTNAME " @ %tmp); //Write events %cnt = $NS[%this, "EN", %i]; for(%j = 0; %j < %cnt; %j++) { //Basic event parameters %enabled = $NS[%this, "EE", %i, %j]; %inputName = $NS[%this, "EI", %i, %j]; %delay = $NS[%this, "ED", %i, %j]; %targetIdx = $NS[%this, "ETI", %i, %j]; if(%targetIdx == -1) { %targetName = "-1"; %NT = $NS[%this, "ENT", %i, %j]; } else { %targetName = $NS[%this, "ET", %i, %j]; %NT = ""; } %outputName = $NS[%this, "EO", %i, %j]; //Temp line (without output parameters) %line = "+-EVENT" TAB %j TAB %enabled TAB %inputName TAB %delay TAB %targetName TAB %NT TAB %outputName; //Output event parameters if(%targetIdx >= 0) %targetClass = getWord(getField($InputEvent_TargetListfxDtsBrick_[$NS[%this, "EII", %i, %j]], %targetIdx), 1); else %targetClass = "FxDTSBrick"; for(%k = 0; %k < 4; %k++) { %param = $NS[%this, "EP", %i, %j, %k]; %dataType = getWord(getField($OutputEvent_parameterList[%targetClass, $NS[%this, "EOI", %i, %j]], %k), 0); if(%dataType $= "Datablock") { if(isObject(%param)) %line = %line TAB %param.getName(); else %line = %line TAB "-1"; } else %line = %line TAB %param; } %file.writeLine(%line); } //Write emitter %edb = $NS[%this, "ED", %i]; %edir = $NS[%this, "ER", %i]; if(isObject(%edb)) %file.writeLine("+-EMITTER" SPC %edb.uiName @ "\" " @ %edir); else if(%edir != 0) %file.writeLine("+-EMITTER NONE\" " @ %edir); //Write light %ldb = $NS[%this, "LD", %i]; if(isObject(%ldb)) %file.writeLine("+-LIGHT" SPC %ldb.uiName @ "\" 1"); //Write item %idb = $NS[%this, "ID", %i]; %ipos = $NS[%this, "IP", %i]; %idir = $NS[%this, "IR", %i]; %irt = $NS[%this, "IT", %i]; if(isObject(%idb)) %file.writeLine("+-ITEM" SPC %idb.uiName @ "\" " @ %ipos SPC %idir SPC %irt); else if(%ipos != 0 || (%idir !$= "" && %idir != 2) || (%irt != 4000 && %irt != 0)) %file.writeLine("+-ITEM NONE\" " @ %ipos SPC %idir SPC %irt); //Write music %mdb = $NS[%this, "MD", %i]; if(isObject(%mdb)) %file.writeLine("+-AUDIOEMITTER" SPC %mdb.uiName @ "\""); //Write vehicle %vdb = $NS[%this, "VD", %i]; %vcol = $NS[%this, "VC", %i]; if(isObject(%vdb)) %file.writeLine("+-VEHICLE" SPC %vdb.uiName @ "\" " @ %vcol); } //Save how far we got %this.saveIndex = %i; //Tell the client how much we saved this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } //Finished saving all bricks? if(%i >= %this.brickCount) { //Find width of connection numbers if(%this.maxConnections >= 241 * 241) %numberSize = 3; else if(%this.maxConnections >= 241) %numberSize = 2; else %numberSize = 1; //Find width of connection indices if(%this.brickCount > 241 * 241) %indexSize = 3; else if(%this.brickCount > 241) %indexSize = 2; else %indexSize = 1; //Save the sizes %file.writeLine("ND_SIZE\" 1 " @ %this.connectionCount SPC %numberSize SPC %indexSize); %this.saveStage = 1; %this.saveIndex = 0; %this.saveLineBuffer = "ND_TREE\" "; //Create byte table if(!$ND::Byte241TableCreated) ndCreateByte241Table(); //Start saving connections %this.connectionCount = 0; %this.saveSchedule = %this.schedule(30, tickSaveConnections, %numberSize, %indexSize); } else %this.saveSchedule = %this.schedule(30, tickSaveBricks); } //Save some connections function ND_Selection::tickSaveConnections(%this, %numberSize, %indexSize) { cancel(%this.saveSchedule); //Get bounds for this tick %start = %this.saveIndex; %end = %start + $Pref::Server::ND::ProcessPerTick * 4; if(%end > %this.brickCount) %end = %this.brickCount; %file = %this.saveFile; %connections = %this.connectionCount; %lineBuffer = %this.saveLineBuffer; %len = strLen(%lineBuffer); //Save connections for(%i = %start; %i < %end; %i++) { //Save number of connections of this brick %cnt = $NS[%this, "N", %i]; %connections += %cnt; //Write compressed connection number if(%numberSize == 1) { %lineBuffer = %lineBuffer @ $ND::Byte241ToChar[%cnt]; %len++; } else if(%numberSize == 2) { %lineBuffer = %lineBuffer @ $ND::Byte241ToChar[(%cnt / 241) | 0] @ $ND::Byte241ToChar[%cnt % 241]; %len += 2; } else { %lineBuffer = %lineBuffer @ $ND::Byte241ToChar[(((%cnt / 241) | 0) / 241) | 0] @ $ND::Byte241ToChar[((%cnt / 241) | 0) % 241] @ $ND::Byte241ToChar[%cnt % 241]; %len += 3; } //If buffer is full, save to file if(%len > 254) { %file.writeLine(%lineBuffer); %lineBuffer = "ND_TREE\" "; %len = 9; } for(%j = 0; %j < %cnt; %j++) { //Write compressed connection index if(%indexSize == 1) { %lineBuffer = %lineBuffer @ $ND::Byte241ToChar[$NS[%this, "C", %i, %j]]; %len++; } else if(%indexSize == 2) { %conn = $NS[%this, "C", %i, %j]; %lineBuffer = %lineBuffer @ $ND::Byte241ToChar[(%conn / 241) | 0] @ $ND::Byte241ToChar[%conn % 241]; %len += 2; } else { %conn = $NS[%this, "C", %i, %j]; %lineBuffer = %lineBuffer @ $ND::Byte241ToChar[(((%conn / 241) | 0) / 241) | 0] @ $ND::Byte241ToChar[((%conn / 241) | 0) % 241] @ $ND::Byte241ToChar[%conn % 241]; %len += 3; } //If buffer is full, save to file if(%len > 254) { %file.writeLine(%lineBuffer); %lineBuffer = "ND_TREE\" "; %len = 9; } } } //Save how far we got %this.saveIndex = %i; %this.saveLineBuffer = %lineBuffer; %this.connectionCount = %connections; //Tell the client how much we cut this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } if(%i >= %this.brickCount) { if(strLen(%lineBuffer) != 9) %file.writeLine(%lineBuffer); %this.saveLineBuffer = ""; %this.finishSaving(); } else %this.saveSchedule = %this.schedule(30, tickSaveConnections, %numberSize, %indexSize); } //Finish saving function ND_Selection::finishSaving(%this) { %this.saveFile.close(); %this.saveFile.delete(); %s1 = %this.brickCount == 1 ? "" : "s"; %s2 = %this.connectionCount == 1 ? "" : "s"; messageClient(%this.client, 'MsgProcessComplete', "\c6Finished saving selection, wrote \c3" @ %this.brickCount @ "\c6 Brick" @ %s1 @ " with \c3" @ %this.connectionCount @ "\c6 Connection" @ %s2 @ "!"); %this.client.ndLastSaveTime = $Sim::Time; %this.client.ndSetMode(NDM_PlantCopy); } //Cancel saving function ND_Selection::cancelSaving(%this) { cancel(%this.saveSchedule); %this.saveFile.close(); %this.saveFile.delete(); if(isFile(%this.saveFilePath)) fileDelete(%this.saveFilePath); %this.client.ndLastSaveTime = $Sim::Time; } //Loading bricks ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Begin loading function ND_Selection::startLoading(%this, %filePath) { //Open file %this.loadFile = new FileObject(); if(!%this.loadFile.openForRead(%filePath)) return false; //Skip file header %this.loadFile.readLine(); %cnt = %this.loadFile.readLine(); for(%i = 0; %i < %cnt; %i++) %this.loadFile.readLine(); //Read colorset for(%i = 0; %i < 64; %i++) $NS[%this, "CT", %i] = ndGetClosestColorID2(getColorI(%this.loadFile.readLine())); //Read line count (temporary, allows displaying percentage) %this.loadExpectedBrickCount = getWord(%this.loadFile.readLine(), 1) * 1; if($Pref::Server::ND::PlayMenuSounds) messageClient(%this.client, 'MsgUploadStart', ""); //Schedule first tick %this.connectionCount = 0; %this.brickCount = 0; %this.loadCount = 0; %this.loadStage = 0; %this.loadIndex = -1; %this.loadSchedule = %this.schedule(30, tickLoadBricks); return true; } //Load some bricks function ND_Selection::tickLoadBricks(%this) { cancel(%this.loadSchedule); %file = %this.loadFile; %index = %this.loadIndex; %loadCount = %this.loadCount; //Process lines while(!%file.isEOF()) { %line = %file.readLine(); //Skip empty lines if(trim(%line $= "")) continue; //Figure out what to do with the line switch$(getWord(%line, 0)) { //Line is brick name case "+-NTOBJECTNAME": $NS[%this, "NT", %index] = getWord(%line, 1); //Line is event case "+-EVENT": //Mostly copied from default loading code %idx = $NS[%this, "EN", %index]; if(!%idx) %idx = 0; %enabled = getField(%line, 2); %inputName = getField(%line, 3); %delay = getField(%line, 4); %targetName = getField(%line, 5); %NT = getField(%line, 6); %outputName = getField(%line, 7); %par1 = getField(%line, 8); %par2 = getField(%line, 9); %par3 = getField(%line, 10); %par4 = getField(%line, 11); %inputIdx = inputEvent_GetInputEventIdx(%inputName); if(%inputIdx == -1) warn("LOAD DUP: Input Event not found for name \"" @ %inputName @ "\""); %targetIdx = inputEvent_GetTargetIndex("FxDTSBrick", %inputIdx, %targetName); if(%targetName == -1) %targetClass = "FxDTSBrick"; else { %field = getField($InputEvent_TargetList["FxDTSBrick", %inputIdx], %targetIdx); %targetClass = getWord(%field, 1); } %outputIdx = outputEvent_GetOutputEventIdx(%targetClass, %outputName); if(%outputIdx == -1) warn("LOAD DUP: Output Event not found for name \"" @ %outputName @ "\""); for(%j = 1; %j < 5; %j++) { %field = getField($OutputEvent_ParameterList[%targetClass, %outputIdx], %j - 1); %dataType = getWord(%field, 0); if(%dataType $= "Datablock" && %par[%j] !$= "-1") { %par[%j] = nameToId(%par[%j]); if(!isObject(%par[%j])) { warn("LOAD DUP: Datablock not found for event " @ %outputName @ " -> " @ %par[%j]); %par[%j] = 0; } } } //Save event $NS[%this, "EE", %index, %idx] = %enabled; $NS[%this, "ED", %index, %idx] = %delay; $NS[%this, "EI", %index, %idx] = %inputName; $NS[%this, "EII", %index, %idx] = %inputIdx; $NS[%this, "EO", %index, %idx] = %outputName; $NS[%this, "EOI", %index, %idx] = %outputIdx; $NS[%this, "EOC", %index, %idx] = $OutputEvent_AppendClient["FxDTSBrick", %outputIdx]; $NS[%this, "ET", %index, %idx] = %targetName; $NS[%this, "ETI", %index, %idx] = %targetIdx; $NS[%this, "ENT", %index, %idx] = %NT; $NS[%this, "EP", %index, %idx, 0] = %par1; $NS[%this, "EP", %index, %idx, 1] = %par2; $NS[%this, "EP", %index, %idx, 2] = %par3; $NS[%this, "EP", %index, %idx, 3] = %par4; $NS[%this, "EN", %index] = %idx + 1; //Line is emitter case "+-EMITTER": %line = getSubStr(%line, 10, 9999); %pos = strStr(%line, "\""); %dbName = getSubStr(%line, 0, %pos); if(%dbName !$= "NONE") { %db = $UINameTable_Emitters[%dbName]; //Ensure emitter exists if(!isObject(%db)) { warn("LOAD DUP: Emitter datablock no found for uiName \"" @ %dbName @ "\""); %db = 0; } } else %db = 0; $NS[%this, "ED", %index] = %db; $NS[%this, "ER", %index] = mFLoor(getSubStr(%line, %pos + 2, 9999)); //Line is light case "+-LIGHT": %line = getSubStr(%line, 8, 9999); %pos = strStr(%line, "\""); %dbName = getSubStr(%line, 0, %pos); %db = $UINameTable_Lights[%dbName]; //Ensure light exists if(!isObject(%db)) { warn("LOAD DUP: Light datablock no found for uiName \"" @ %dbName @ "\""); %db = 0; } else $NS[%this, "LD", %index] = %db; //Line is item case "+-ITEM": %line = getSubStr(%line, 7, 9999); %pos = strStr(%line, "\""); %dbName = getSubStr(%line, 0, %pos); if(%dbName !$= "NONE") { %db = $UINameTable_Items[%dbName]; //Ensure item exists if(!isObject(%db)) { warn("LOAD DUP: Item datablock no found for uiName \"" @ %dbName @ "\""); %db = 0; } } else %db = 0; %line = getSubStr(%line, %pos + 2, 9999); $NS[%this, "ID", %index] = %db; $NS[%this, "IP", %index] = getWord(%line, 0); $NS[%this, "IR", %index] = getWord(%line, 1); $NS[%this, "IT", %index] = getWord(%line, 2); //Line is music case "+-AUDIOEMITTER": %line = getSubStr(%line, 15, 9999); %pos = strStr(%line, "\""); %dbName = getSubStr(%line, 0, %pos); %db = $UINameTable_Music[%dbName]; //Ensure music exists if(!isObject(%db)) { warn("LOAD DUP: Music datablock no found for uiName \"" @ %dbName @ "\""); %db = 0; } else $NS[%this, "MD", %index] = %db; //Line is vehicle case "+-VEHICLE": %line = getSubStr(%line, 10, 9999); %pos = strStr(%line, "\""); %dbName = getSubStr(%line, 0, %pos); if(%dbName !$= "NONE") { %db = $UINameTable_Vehicle[%dbName]; //Ensure vehicle exists if(!isObject(%db)) { warn("LOAD DUP: Vehicle datablock no found for uiName \"" @ %dbName @ "\""); %db = 0; } } else %db = 0; $NS[%this, "VD", %index] = %db; $NS[%this, "VC", %index] = mFLoor(getSubStr(%line, %pos + 2, 9999)); //Start reading connections case "ND_SIZE\"": %version = getWord(%line, 1); %this.loadExpectedConnectionCount = getWord(%line, 2); %numberSize = getWord(%line, 3); %indexSize = getWord(%line, 4); %connections = true; break; //Error case "ND_TREE\"": warn("LOAD DUP: Got connection data before connection sizes"); //Line is irrelevant case "+-OWNER": %nothing = ""; //Line is brick default: //Increment selection index %index++; %quotePos = strstr(%line, "\""); if(%quotePos >= 0) { //Get datablock %uiName = getSubStr(%line, 0, %quotePos); %db = $uiNameTable[%uiName]; if(isObject(%db)) { $NS[%this, "D", %index] = %db; //Load all the info from brick line %line = getSubStr(%line, %quotePos + 2, 9999); %pos = getWords(%line, 0, 2); %angId = getWord(%line, 3); if(%loadCount == 0) %this.rootPosition = %pos; $NS[%this, "P", %index] = vectorSub(%pos, %this.rootPosition); $NS[%this, "R", %index] = %angId; $NS[%this, "CO", %index] = $NS[%this, "CT", getWord(%line, 5)]; $NS[%this, "CF", %index] = getWord(%line, 7); $NS[%this, "SF", %index] = getWord(%line, 8); if(%db.hasPrint) { if((%print = $printNameTable[getWord(%line, 6)]) $= "") warn("LOAD DUP: Print texture not found for path \"" @ getWord(%line, 6) @ "\""); $NS[%this, "PR", %index] = %print; } if(!getWord(%line, 9)) $NS[%this, "NRC", %index] = true; if(!getWord(%line, 10)) $NS[%this, "NC", %index] = true; if(!getWord(%line, 11)) $NS[%this, "NR", %index] = true; //Update selection size with brick datablock if(%angId % 2 == 0) { %sx = %db.brickSizeX / 4; %sy = %db.brickSizeY / 4; } else { %sy = %db.brickSizeX / 4; %sx = %db.brickSizeY / 4; } %sz = %db.brickSizeZ / 10; %minX = getWord(%pos, 0) - %sx; %minY = getWord(%pos, 1) - %sy; %minZ = getWord(%pos, 2) - %sz; %maxX = getWord(%pos, 0) + %sx; %maxY = getWord(%pos, 1) + %sy; %maxZ = getWord(%pos, 2) + %sz; if(%loadCount) { if(%minX < %this.minX) %this.minX = %minX; if(%minY < %this.minY) %this.minY = %minY; if(%minZ < %this.minZ) %this.minZ = %minZ; if(%maxX > %this.maxX) %this.maxX = %maxX; if(%maxY > %this.maxY) %this.maxY = %maxY; if(%maxZ > %this.maxZ) %this.maxZ = %maxZ; } else { %this.minX = %minX; %this.minY = %minY; %this.minZ = %minZ; %this.maxX = %maxX; %this.maxY = %maxY; %this.maxZ = %maxZ; } %loadCount++; } else { warn("LOAD DUP: Brick datablock not found for uiName \"" @ %uiName @ "\""); $NS[%this, "D", %index] = 0; } } else { warn("LOAD DUP: Brick uiName missing on line \"" @ %line @ "\""); $NS[%this, "D", %index] = 0; } } if(%linesProcessed++ > $Pref::Server::ND::ProcessPerTick * 2) break; } //Save how far we got %this.loadIndex = %index; %this.brickCount = %index + 1; %this.loadCount = %loadCount; //Tell the client how much we loaded this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } //Switch over to connection mode if necessary if(%connections) { %this.loadStage = 1; %this.loadIndex = 0; %this.connectionCount = 0; %this.connectionIndex = -1; %this.connectionIndex2 = 0; %this.connectionsRemaining = 0; if((%numberSize != 1 && %numberSize != 2 && %numberSize != 3) || (%indexSize != 1 && %indexSize != 2 && %indexSize != 3)) { messageClient(%this.client, '', "\c0Warning:\c6 The connection data is corrupted. Planting may not work as expected."); %this.finishLoading(); return; } //Create byte table if(!$ND::Byte241TableCreated) ndCreateByte241Table(); %this.loadSchedule = %this.schedule(30, tickLoadConnections, %numberSize, %indexSize); return; } //Reached end of file, means we got no connection data if(%file.isEOF()) { messageClient(%this.client, '', "\c0Warning:\c6 The save was not written by the New Duplicator. Planting may not work as expected."); %this.finishLoading(); } else %this.loadSchedule = %this.schedule(30, tickLoadBricks); } //Load connections function ND_Selection::tickLoadConnections(%this, %numberSize, %indexSize) { cancel(%this.loadSchedule); %connections = %this.connectionCount; %maxConnections = %this.maxConnections; %connectionIndex = %this.connectionIndex; %connectionIndex2 = %this.connectionIndex2; %connectionsRemaining = %this.connectionsRemaining; //Process 10 lines for(%i = 0; %i < 10 && !%this.loadFile.isEOF(); %i++) { %line = getSubStr(%this.loadFile.readLine(), 9, 9999); %len = strLen(%line); %pos = 0; while(%pos < %len) { if(%connectionsRemaining) { //Read a connection if(%indexSize == 1) { $NS[%this, "C", %connectionIndex, %connectionIndex2] = strStr($ND::Byte241Lookup, getSubStr(%line, %pos, 1)); %pos++; } else if(%indexSize == 2) { %tmp = getSubStr(%line, %pos, 2); $NS[%this, "C", %connectionIndex, %connectionIndex2] = strStr($ND::Byte241Lookup, getSubStr(%tmp, 0, 1)) * 241 + strStr($ND::Byte241Lookup, getSubStr(%tmp, 1, 1)); %pos += 2; } else { %tmp = getSubStr(%line, %pos, 3); $NS[%this, "C", %connectionIndex, %connectionIndex2] = ((strStr($ND::Byte241Lookup, getSubStr(%tmp, 0, 1)) * 58081) | 0) + strStr($ND::Byte241Lookup, getSubStr(%tmp, 1, 1)) * 241 + strStr($ND::Byte241Lookup, getSubStr(%tmp, 2, 1)); %pos += 3; } %connectionsRemaining--; %connectionIndex2++; %connections++; } else { //No connections remaining for active brick, increment index %connectionIndex++; %connectionIndex2 = 0; //Read a connection number if(%numberSize == 1) { %connectionsRemaining = strStr($ND::Byte241Lookup, getSubStr(%line, %pos, 1)); %pos++; } else if(%numberSize == 2) { %tmp = getSubStr(%line, %pos, 2); %connectionsRemaining = strStr($ND::Byte241Lookup, getSubStr(%tmp, 0, 1)) * 241 + strStr($ND::Byte241Lookup, getSubStr(%tmp, 1, 1)); %pos += 2; } else { %tmp = getSubStr(%line, %pos, 3); %connectionsRemaining = ((strStr($ND::Byte241Lookup, getSubStr(%tmp, 0, 1)) * 58081) | 0) + strStr($ND::Byte241Lookup, getSubStr(%tmp, 1, 1)) * 241 + strStr($ND::Byte241Lookup, getSubStr(%tmp, 2, 1)); %pos += 3; } $NS[%this, "N", %connectionIndex] = %connectionsRemaining; if(%maxConnections < %connectionsRemaining) %maxConnections = %connectionsRemaining; } } } //Save how far we got %this.connectionCount = %connections; %this.maxConnections = %maxConnections; %this.connectionIndex = %connectionIndex; %this.connectionIndex2 = %connectionIndex2; %this.connectionsRemaining = %connectionsRemaining; //Tell the client how much we loaded this tick if(%this.client.ndLastMessageTime + 0.1 < $Sim::Time) { %this.client.ndUpdateBottomPrint(); %this.client.ndLastMessageTime = $Sim::Time; } //Check if we're done if(%this.loadFile.isEOF()) %this.finishLoading(); else %this.loadSchedule = %this.schedule(30, tickLoadConnections, %numberSize, %indexSize); } //Finish loading function ND_Selection::finishLoading(%this) { %this.loadFile.close(); %this.loadFile.delete(); //Align the build to the brick grid %this.updateSize(); %pos = vectorAdd(%this.rootPosition, %this.rootToCenter); %shiftX = mCeil(getWord(%pos, 0) * 2 - %this.brickSizeX % 2) / 2 + (%this.brickSizeX % 2) / 4 - getWord(%pos, 0); %shiftY = mCeil(getWord(%pos, 1) * 2 - %this.brickSizeY % 2) / 2 + (%this.brickSizeY % 2) / 4 - getWord(%pos, 1); %shiftZ = mCeil(getWord(%pos, 2) * 5 - %this.brickSizeZ % 2) / 5 + (%this.brickSizeZ % 2) / 10 - getWord(%pos, 2); %this.rootPosition = vectorAdd(%shiftX SPC %shiftY SPC %shiftZ, %this.rootPosition); %this.minX = %this.minX + %shiftX; %this.maxX = %this.maxX + %shiftX; %this.minY = %this.minY + %shiftY; %this.maxY = %this.maxY + %shiftY; %this.minZ = %this.minZ + %shiftZ; %this.maxZ = %this.maxZ + %shiftZ; %this.updateSize(); %this.updateHighlightBox(); //Message client %s1 = %this.brickCount == 1 ? "" : "s"; %s2 = %this.connectionCount == 1 ? "" : "s"; messageClient(%this.client, 'MsgProcessComplete', "\c6Finished loading selection, got \c3" @ %this.brickCount @ "\c6 Brick" @ %s1 @ " with \c3" @ %this.connectionCount @ "\c6 Connection" @ %s2 @ "!"); %this.client.ndLastLoadTime = $Sim::Time; %this.client.ndSetMode(NDM_PlantCopy); } //Cancel loading function ND_Selection::cancelLoading(%this) { cancel(%this.loadSchedule); %this.loadFile.close(); %this.loadFile.delete(); %this.client.ndLastLoadTime = $Sim::Time; }