commit 93d385e859beec02bd9ba53d38dbb605ecc998eb Author: Redo Date: Wed Oct 5 16:02:11 2022 -0600 initial commit diff --git a/classes/server/duplimode/boxselect.cs b/classes/server/duplimode/boxselect.cs new file mode 100644 index 0000000..a6f8e50 --- /dev/null +++ b/classes/server/duplimode/boxselect.cs @@ -0,0 +1,462 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Switch to this mode +function NDM_BoxSelect::onStartMode(%this, %client, %lastMode) +{ + if(%lastMode == $NDM::StackSelect) + { + if(isObject(%client.ndSelection) && %client.ndSelection.brickCount) + { + //Create selection box from the size of the previous selection + %root = %client.ndSelection.rootPosition; + %min = vectorAdd(%root, %client.ndSelection.minSize); + %max = vectorAdd(%root, %client.ndSelection.maxSize); + + if(%client.isAdmin) + %limit = $Pref::Server::ND::MaxBoxSizeAdmin; + else + %limit = $Pref::Server::ND::MaxBoxSizePlayer; + + if((getWord(%max, 0) - getWord(%min, 0) <= %limit) + && (getWord(%max, 1) - getWord(%min, 1) <= %limit) + && (getWord(%max, 2) - getWord(%min, 2) <= %limit)) + { + %name = %client.name; + + if(getSubStr(%name, strLen(%name - 1), 1) $= "s") + %shapeName = %name @ "' Selection Box"; + else + %shapeName = %name @ "'s Selection Box"; + + %client.ndSelectionBox = ND_SelectionBox(%shapeName); + %client.ndSelectionBox.setSizeAligned(%min, %max, %client.getControlObject()); + } + else + commandToClient(%client, 'centerPrint', "\c6Oops!\n" @ + "\c6Your selection box is limited to \c3" @ mFloor(%limit * 2) @ " \c6studs.", 5); + + %client.ndSelection.deleteData(); + } + + %client.ndSelectionAvailable = false; + } + else if(%lastMode == $NDM::BoxSelectProgress && %client.ndSelection.brickCount > 0) + { + %client.ndSelectionBox.setDisabledMode(); + %client.ndSelectionAvailable = true; + } + else if(%lastMode != $NDM::FillColor && %lastMode != $NDM::WrenchProgress) + %client.ndSelectionAvailable = false; + + %client.ndLastSelectMode = %this; + %client.ndUpdateBottomPrint(); +} + +//Switch away from this mode +function NDM_BoxSelect::onChangeMode(%this, %client, %nextMode) +{ + if(%nextMode == $NDM::StackSelect) + { + //Clear selection + if(isObject(%client.ndSelection)) + %client.ndSelection.deleteData(); + + //Remove the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); + } + else if(%nextMode == $NDM::PlantCopy) + { + //Start de-highlighting the bricks + %client.ndSelection.deHighlight(); + + //Remove the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); + } + else if(%nextMode == $NDM::CutProgress) + { + //Remove the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); + } + else if(%nextMode == $NDM::FillColor) + { + //Start de-highlighting the bricks + %client.ndSelection.deHighlight(); + } + else if(%nextMode == $NDM::WrenchProgress) + { + //Start de-highlighting the bricks + %client.ndSelection.deHighlight(); + } + else if(%nextMode == $NDM::LoadProgress) + { + //Remove the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); + } +} + +//Kill this mode +function NDM_BoxSelect::onKillMode(%this, %client) +{ + //Destroy selection + if(isObject(%client.ndSelection)) + %client.ndSelection.delete(); + + //Delete the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); +} + + + +//Duplicator image callbacks +/////////////////////////////////////////////////////////////////////////// + +//Selecting an object with the duplicator +function NDM_BoxSelect::onSelectObject(%this, %client, %obj, %pos, %normal) +{ + if((%obj.getType() & $TypeMasks::FxBrickAlwaysObjectType) == 0) + return; + + if(!ndTrustCheckMessage(%obj, %client)) + return; + + if(%client.ndSelectionAvailable) + { + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6Selection canceled! " @ + "You can now edit the box again.", 5); + + %client.ndSelectionAvailable = false; + %client.ndSelection.deleteData(); + %client.ndSelectionBox.setNormalMode(); + %client.ndUpdateBottomPrint(); + } + + if(isObject(%client.ndSelectionBox)) + { + if(%client.ndMultiSelect) + { + %box1 = %client.ndSelectionBox.getWorldBox(); + //%box2 = ndGetPlateBoxFromRayCast(%pos, %normal); + %box2 = %obj.getWorldBox(); + + %p1 = getMin(getWord(%box1, 0), getWord(%box2, 0)) + SPC getMin(getWord(%box1, 1), getWord(%box2, 1)) + SPC getMin(getWord(%box1, 2), getWord(%box2, 2)); + + %p2 = getMax(getWord(%box1, 3), getWord(%box2, 3)) + SPC getMax(getWord(%box1, 4), getWord(%box2, 4)) + SPC getMax(getWord(%box1, 5), getWord(%box2, 5)); + } + else + { + %box = %obj.getWorldBox(); + %p1 = getWords(%box, 0, 2); + %p2 = getWords(%box, 3, 5); + } + } + else + { + %name = %client.name; + + if(getSubStr(%name, strLen(%name - 1), 1) $= "s") + %shapeName = %name @ "' Selection Box"; + else + %shapeName = %name @ "'s Selection Box"; + + %client.ndSelectionBox = ND_SelectionBox(%shapeName); + + // if(%client.ndMultiSelect) + // %box = ndGetPlateBoxFromRayCast(%pos, %normal); + // else + %box = %obj.getWorldBox(); + + %p1 = getWords(%box, 0, 2); + %p2 = getWords(%box, 3, 5); + } + + %client.ndSelectionBox.setSizeAligned(%p1, %p2, %client.getControlObject()); + %client.ndUpdateBottomPrint(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Light key +function NDM_BoxSelect::onLight(%this, %client) +{ + if($Pref::Server::ND::PlayMenuSounds) + %client.play2d(lightOffSound); + + %client.ndSetMode(NDM_StackSelect); +} + +//Prev Seat +function NDM_BoxSelect::onPrevSeat(%this, %client) +{ + %client.ndLimited = !%client.ndLimited; + %client.ndUpdateBottomPrint(); + + if($Pref::Server::ND::PlayMenuSounds) + %client.play2d(%client.ndLimited ? lightOnSound : lightOffSound); +} + +//Shift Brick +function NDM_BoxSelect::onShiftBrick(%this, %client, %x, %y, %z) +{ + if(!isObject(%client.ndSelectionBox)) + return; + + //If we have a selection, enter plant mode! + if(%client.ndSelectionAvailable) + { + %client.ndSetMode(NDM_PlantCopy); + NDM_PlantCopy.onShiftBrick(%client, %x, %y, %z); + + return; + } + + //Move the corner + switch(getAngleIDFromPlayer(%client.getControlObject())) + { + case 0: %newX = %x; %newY = %y; + case 1: %newX = -%y; %newY = %x; + case 2: %newX = -%x; %newY = -%y; + case 3: %newX = %y; %newY = -%x; + } + + %newX = mFloor(%newX) / 2; + %newY = mFloor(%newY) / 2; + %z = mFloor(%z ) / 5; + + if(!%client.ndMultiSelect) + { + if(%client.isAdmin) + %limit = $Pref::Server::ND::MaxBoxSizeAdmin; + else + %limit = $Pref::Server::ND::MaxBoxSizePlayer; + + if(%client.ndSelectionBox.shiftCorner(%newX SPC %newY SPC %z, %limit)) + commandToClient(%client, 'centerPrint', "\c6Oops!\n" @ + "\c6Your selection box is limited to \c3" @ mFloor(%limit * 2) @ " \c6studs.", 5); + + %client.ndUpdateBottomPrint(); + } + else + { + %client.ndSelectionBox.shift(%newX SPC %newY SPC %z); + } +} + +//Super Shift Brick +function NDM_BoxSelect::onSuperShiftBrick(%this, %client, %x, %y, %z) +{ + //If we have a selection, enter plant mode! + if(%client.ndSelectionAvailable) + { + %client.ndSetMode(NDM_PlantCopy); + NDM_PlantCopy.onSuperShiftBrick(%client, %x, %y, %z); + + return; + } + + %this.onShiftBrick(%client, %x * 8, %y * 8, %z * 20); +} + +//Rotate Brick +function NDM_BoxSelect::onRotateBrick(%this, %client, %direction) +{ + if(!isObject(%client.ndSelectionBox)) + return; + + //If we have a selection, enter plant mode! + if(%client.ndSelectionAvailable) + { + %client.ndSetMode(NDM_PlantCopy); + NDM_PlantCopy.onRotateBrick(%client, %direction); + + return; + } + + if(!%client.ndMultiSelect) + %client.ndSelectionBox.switchCorner(); + else + { + %client.ndSelectionBox.rotate(%direction); + %client.ndUpdateBottomPrint(); + } + +} + +//Plant Brick +function NDM_BoxSelect::onPlantBrick(%this, %client) +{ + if(!isObject(%client.ndSelectionBox)) + return; + + //If we have a selection, enter plant mode! + if(%client.ndSelectionAvailable) + { + %client.ndSetMode(NDM_PlantCopy); + return; + } + + //Check timeout + if(!%client.isAdmin && %client.ndLastSelectTime + ($Pref::Server::ND::SelectTimeoutMS / 1000) > $Sim::Time) + { + %remain = mCeil(%client.ndLastSelectTime + ($Pref::Server::ND::SelectTimeoutMS / 1000) - $Sim::Time); + + if(%remain != 1) + %s = "s"; + + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6You need to wait\c3 " @ + %remain @ "\c6 second" @ %s @ " before selecting again!", 5); + + return; + } + + %client.ndLastSelectTime = $Sim::Time; + + //Prepare a selection to copy the bricks + if(isObject(%client.ndSelection)) + %client.ndSelection.deleteData(); + else + %client.ndSelection = ND_Selection(%client); + + //Start selection + %box = %client.ndSelectionBox.getWorldBox(); + + %client.ndSetMode(NDM_BoxSelectProgress); + %client.ndSelection.startBoxSelection(%box, %client.ndLimited); +} + +//Cancel Brick +function NDM_BoxSelect::onCancelBrick(%this, %client) +{ + if(!isObject(%client.ndSelectionBox)) + return; + + if(%client.ndSelectionAvailable) + { + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6Selection canceled! " @ + "You can now edit the box again.", 5); + + %client.ndSelectionAvailable = false; + %client.ndSelection.deleteData(); + %client.ndSelectionBox.setNormalMode(); + %client.ndUpdateBottomPrint(); + + return; + } + + if(isObject(%client.ndSelection)) + %client.ndSelection.deleteData(); + + %client.ndSelectionBox.delete(); + %client.ndSelectionAvailable = false; + %client.ndUpdateBottomPrint(); +} + +//Copy Selection +function NDM_BoxSelect::onCopy(%this, %client) +{ + %this.onPlantBrick(%client); +} + +//Cut Selection +function NDM_BoxSelect::onCut(%this, %client) +{ + if(!isObject(%client.ndSelectionBox)) + return; + + if(!%client.ndSelectionAvailable) + { + %this.onPlantBrick(%client); + return; + } + + %client.ndSetMode(NDM_CutProgress); + %client.ndSelection.startCutting(); +} + +//Supercut selection +function NDM_BoxSelect::onSuperCut(%this, %client) +{ + if(!isObject(%client.ndSelectionBox)) + return; + + //Prepare a selection to handle the callback + if(isObject(%client.ndSelection)) + %client.ndSelection.deleteData(); + else + %client.ndSelection = ND_Selection(%client); + + if(!$ND::SimpleBrickTableCreated) + ndCreateSimpleBrickTable(); + + //Start supercut + %box = %client.ndSelectionBox.getWorldBox(); + + %client.ndSetMode(NDM_SuperCutProgress); + %client.ndSelection.startSuperCut(%box); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_BoxSelect::getBottomPrint(%this, %client) +{ + if(isObject(%client.ndSelection) && %client.ndSelection.brickCount) + { + %count = %client.ndSelection.brickCount; + %title = "Selection Mode (\c3" @ %count @ "\c6 Brick" @ (%count > 1 ? "s)" : ")"); + } + else + %title = "Selection Mode"; + + %l0 = "Type: \c3Box \c6[Light]"; + %l1 = "Limited: " @ (%client.ndLimited ? "\c3Yes" : "\c0No") @ " \c6[Prev Seat]"; + + if(isObject(%client.ndSelectionBox)) + { + %size = %client.ndSelectionBox.getSize(); + %x = mFloatLength(getWord(%size, 0) * 2, 0); + %y = mFloatLength(getWord(%size, 1) * 2, 0); + %z = mFloatLength(getWord(%size, 2) * 5, 0); + %l2 = "Size: \c3" @ %x @ "\c6 x \c3" @ %y @ "\c6 x \c3" @ %z @ "\c6 Plates"; + } + + if(!isObject(%client.ndSelectionBox)) + { + %r0 = "Click Brick: Place selection box"; + %r1 = ""; + %r2 = ""; + } + else if(!%client.ndSelectionAvailable) + { + %r0 = "[Shift Brick]: Move corner"; + %r1 = "[Rotate Brick]: Switch corner"; + } + else + { + %r0 = "[Cancel Brick]: Adjust box"; + %r1 = "[Plant Brick]: Duplicate"; + } + + return ndFormatMessage(%title, %l0, %r0, %l1, %r1, %l2, %r2); +} diff --git a/classes/server/duplimode/boxselectprogress.cs b/classes/server/duplimode/boxselectprogress.cs new file mode 100644 index 0000000..e9bf157 --- /dev/null +++ b/classes/server/duplimode/boxselectprogress.cs @@ -0,0 +1,59 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_BoxSelectProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); + + //Remove selection box + %client.ndSelectionBox.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_BoxSelectProgress::onCancelBrick(%this, %client) +{ + commandToClient(%client, 'centerPrint', "\c6Selection canceled!", 4); + + %client.ndSelection.cancelBoxSelection(); + %client.ndSetMode(NDM_BoxSelect); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_BoxSelectProgress::getBottomPrint(%this, %client) +{ + %qCount = %client.ndSelection.queueCount; + %bCount = %client.ndSelection.brickCount; + + if(%bCount <= 0) + { + %curr = %client.ndSelection.currChunk + 1; + %num = %client.ndSelection.numChunks; + + %percent = mFloor(%curr * 100 / %num); + %title = "Searching... (\c3" @ %percent @ "%\c6, \c3" @ %qCount @ "\c6 Bricks)"; + } + else + { + %percent = mFloor(%bCount * 100 / %qCount); + %title = "Processing... (\c3" @ %percent @ "%\c6)"; + } + + %l0 = "[Cancel Brick]: Cancel selection"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/cutprogress.cs b/classes/server/duplimode/cutprogress.cs new file mode 100644 index 0000000..8219996 --- /dev/null +++ b/classes/server/duplimode/cutprogress.cs @@ -0,0 +1,48 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Switch away from this mode +function NDM_CutProgress::onChangeMode(%this, %client, %nextMode) +{ + if(%nextMode != $NDM::PlantCopy) + %client.ndSelection.deleteData(); +} + +//Kill this mode +function NDM_CutProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_CutProgress::onCancelBrick(%this, %client) +{ + %client.ndSelection.cancelCutting(); + %client.ndSetMode(%client.ndLastSelectMode); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_CutProgress::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + %percent = mFloor(%client.ndSelection.cutIndex * 100 / %count); + + %title = "Cutting... (\c3" @ %percent @ "%\c6)"; + %l0 = "[Cancel Brick]: Cancel cut"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/fillcolor.cs b/classes/server/duplimode/fillcolor.cs new file mode 100644 index 0000000..3361312 --- /dev/null +++ b/classes/server/duplimode/fillcolor.cs @@ -0,0 +1,128 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Switch to this mode +function NDM_FillColor::onStartMode(%this, %client, %lastMode) +{ + %client.ndUpdateBottomPrint(); + cancel(%client.ndToolSchedule); +} + +//Switch away from this mode +function NDM_FillColor::onChangeMode(%this, %client, %nextMode) +{ + //Hide paint gui + if(%nextMode != $NDM::FillColorProgress) + { + %client.ndLastEquipTime = $Sim::Time; + + if(%client.ndEquippedFromItem) + commandToClient(%client, 'setScrollMode', 2); + else + commandToClient(%client, 'setScrollMode', 3); + } +} + +//Kill this mode +function NDM_FillColor::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); + + //Remove the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Plant Brick +function NDM_FillColor::onPlantBrick(%this, %client) +{ + //Admin limit + if($Pref::Server::ND::PaintAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Paint Mode is admin only. Ask an admin for help."); + return; + } + + //Normal colors + if(%client.currentFxColor $= "") + { + %client.ndSetMode(NDM_FillColorProgress); + %client.ndSelection.startFillColor(0, %client.currentColor); + return; + } + + //Admin limit + if($Pref::Server::ND::PaintFxAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Paint Fx Mode is admin only. Ask an admin for help."); + return; + } + + %client.ndSetMode(NDM_FillColorProgress); + + if(%client.currentFxColor < 7) + %client.ndSelection.startFillColor(1, %client.currentFxColor); + else + %client.ndSelection.startFillColor(2, %client.currentFxColor - 7); +} + +//Cancel Brick +function NDM_FillColor::onCancelBrick(%this, %client) +{ + %client.ndSetMode(%client.ndLastSelectMode); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_FillColor::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + %title = "Paint Mode (\c3" @ %count @ "\c6 Brick" @ (%count > 1 ? "s)" : ")"); + + if(%client.currentFxColor !$= "") + { + switch(%client.currentFxColor) + { + case 0: %color = "\c3Fx - None"; + case 1: %color = "\c3Fx - Pearl"; + case 2: %color = "\c3Fx - Chrome"; + case 3: %color = "\c3Fx - Glow"; + case 4: %color = "\c3Fx - Blink"; + case 5: %color = "\c3Fx - Swirl"; + case 6: %color = "\c3Fx - Rainbow"; + case 7: %color = "\c3Fx - Stable"; + case 8: %color = "\c3Fx - Undulo"; + } + } + else + { + %color = "" @ ndGetPaintColorCode(%client.currentColor) @ "|||||\c3"; + + %alpha = mFloor(100 * getWord(getColorIdTable(%client.currentColor), 3)); + + if(%alpha != 100) + %color = %color SPC %alpha @ "%"; + } + + + %l0 = "Select paint can to chose color"; + %l1 = "Color: " @ %color; + + %r0 = "[Plant Brick]: Paint bricks"; + %r1 = "[Cancel Brick]: Exit mode"; + + return ndFormatMessage(%title, %l0, %r0, %l1, %r1, %l2); +} diff --git a/classes/server/duplimode/fillcolorprogress.cs b/classes/server/duplimode/fillcolorprogress.cs new file mode 100644 index 0000000..bfac7e7 --- /dev/null +++ b/classes/server/duplimode/fillcolorprogress.cs @@ -0,0 +1,41 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_FillColorProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_FillColorProgress::onCancelBrick(%this, %client) +{ + %client.ndSelection.cancelFillColor(); + %client.ndSetMode(NDM_FillColor); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_FillColorProgress::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + %percent = mFloor(%client.ndSelection.paintIndex * 100 / %count); + + %title = "Painting... (\c3" @ %percent @ "%\c6)"; + %l0 = "[Cancel Brick]: Cancel painting"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/loadprogress.cs b/classes/server/duplimode/loadprogress.cs new file mode 100644 index 0000000..80c2337 --- /dev/null +++ b/classes/server/duplimode/loadprogress.cs @@ -0,0 +1,76 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Switch to this mode +function NDM_LoadProgress::onStartMode(%this, %client, %lastMode) +{ + //Prepare selection to load data into + if(isObject(%client.ndSelection)) + %client.ndSelection.deleteData(); + else + %client.ndSelection = ND_Selection(%client); +} + +//Kill this mode +function NDM_LoadProgress::onKillMode(%this, %client) +{ + //Destroy selection + %client.ndSelection.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_LoadProgress::onCancelBrick(%this, %client) +{ + %client.ndSelection.cancelLoading(); + %client.ndSelection.delete(); + + %client.ndSetMode(%client.ndLastSelectMode); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_LoadProgress::getBottomPrint(%this, %client) +{ + if(%client.ndSelection.loadStage == 0) + { + %count = %client.ndSelection.loadExpectedBrickCount; + + if(%count != 0) + { + %percent = mFloor(%client.ndSelection.brickCount * 100 / %count); + + %title = "Loading Bricks... (\c3" @ %percent @ "%\c6)"; + } + else + %title = "Loading Bricks... (\c3" @ %client.ndSelection.brickCount @ "\c6 Bricks)"; + } + else + { + %count = %client.ndSelection.loadExpectedConnectionCount; + + if(%count != 0) + { + %percent = mFloor(%client.ndSelection.connectionCount * 100 / %count); + + %title = "Loading Connections... (\c3" @ %percent @ "%\c6)"; + } + else + %title = "Loading Connections... (\c3" @ %client.ndSelection.connectionCount @ "\c6 Connections)"; + } + + %l0 = "[Cancel Brick]: Cancel loading"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/plantcopy.cs b/classes/server/duplimode/plantcopy.cs new file mode 100644 index 0000000..14d5568 --- /dev/null +++ b/classes/server/duplimode/plantcopy.cs @@ -0,0 +1,268 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Switch to this mode +function NDM_PlantCopy::onStartMode(%this, %client, %lastMode) +{ + if(%lastMode == $NDM::StackSelect + || %lastMode == $NDM::BoxSelect + || %lastMode == $NDM::CutProgress + || %lastMode == $NDM::LoadProgress) + { + %client.ndSelection.spawnGhostBricks(%client.ndSelection.rootPosition, 0); + %client.ndSelection.angleIdReference = getAngleIDFromPlayer(%client.getControlObject()); + } + + %client.ndUpdateBottomPrint(); +} + +//Switch away from this mode +function NDM_PlantCopy::onChangeMode(%this, %client, %nextMode) +{ + if(%nextMode == $NDM::StackSelect || %nextMode == $NDM::BoxSelect) + { + %client.ndSelection.deleteData(); + } +} + +//Kill this mode +function NDM_PlantCopy::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); +} + + + +//Duplicator image callbacks +/////////////////////////////////////////////////////////////////////////// + +//Selecting an object with the duplicator +function NDM_PlantCopy::onSelectObject(%this, %client, %obj, %pos, %normal) +{ + %this.moveBricksTo(%client, %pos, %normal); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Prev Seat +function NDM_PlantCopy::onPrevSeat(%this, %client) +{ + %client.ndPivot = !%client.ndPivot; + %client.ndUpdateBottomPrint(); + + if($Pref::Server::ND::PlayMenuSounds) + %client.play2d(%client.ndPivot ? lightOnSound : lightOffSound); +} + +//Shift Brick +function NDM_PlantCopy::onShiftBrick(%this, %client, %x, %y, %z) +{ + switch(getAngleIDFromPlayer(%client.getControlObject())) + { + case 0: %newX = %x; %newY = %y; + case 1: %newX = -%y; %newY = %x; + case 2: %newX = -%x; %newY = -%y; + case 3: %newX = %y; %newY = -%x; + } + + %client.ndSelection.shiftGhostBricks(%newX / 2 SPC %newY / 2 SPC %z / 5); +} + +//Super Shift Brick +function NDM_PlantCopy::onSuperShiftBrick(%this, %client, %x, %y, %z) +{ + switch(getAngleIDFromPlayer(%client.getControlObject())) + { + case 0: %newX = %x; %newY = %y; + case 1: %newX = -%y; %newY = %x; + case 2: %newX = -%x; %newY = -%y; + case 3: %newX = %y; %newY = -%x; + } + + if(%client.ndPivot) + %box = %client.ndSelection.getGhostWorldBox(); + else + %box = %client.ndSelection.ghostGroup.getObject(0).getWorldBox(); + + %newX *= (getWord(%box, 3) - getWord(%box, 0)); + %newy *= (getWord(%box, 4) - getWord(%box, 1)); + %z *= (getWord(%box, 5) - getWord(%box, 2)); + + %client.ndSelection.shiftGhostBricks(%newX SPC %newY SPC %z); +} + +//Rotate Brick +function NDM_PlantCopy::onRotateBrick(%this, %client, %direction) +{ + %client.ndSelection.rotateGhostBricks(%direction, %client.ndPivot); +} + +//Plant Brick +function NDM_PlantCopy::onPlantBrick(%this, %client) +{ + //Check force plant + if(%client.ndForcePlant) + { + if($Pref::Server::ND::FloatAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Force Plant has been disabled because it is admin only. Ask an admin for help."); + %client.ndForcePlant = false; + } + } + + %this.conditionalPlant(%client, %client.ndForcePlant); +} + +//Cancel Brick +function NDM_PlantCopy::onCancelBrick(%this, %client) +{ + if(%client.ndEquipped) + %client.ndSetMode(%client.ndLastSelectMode); + else + %client.ndKillMode(); +} + +//Paste Selection +function NDM_PlantCopy::onPaste(%this, %client) +{ + %this.onPlantBrick(%client); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_PlantCopy::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + + %size = vectorSub(%client.ndSelection.maxSize, %client.ndSelection.minSize); + + %x = mFloor(getWord(%size, 0) * 2); + %y = mFloor(getWord(%size, 1) * 2); + %z = mFloor(getWord(%size, 2) * 5); + + if(%count == 1) + %title = "Plant Mode (\c31\c6 Brick)"; + else if(%count <= $Pref::Server::ND::MaxGhostBricks) + %title = "Plant Mode (\c3" @ %count @ "\c6 Bricks)"; + else + %title = "Plant Mode (\c3" @ %count @ "\c6 Bricks, \c3" @ mFloor($Pref::Server::ND::MaxGhostBricks * 100 / %count) @ "%\c6 Ghosted)"; + + %l0 = "Pivot: \c3" @ (%client.ndPivot ? "Whole Selection" : "Start Brick") @ "\c6 [Prev Seat]"; + + if(isObject(%client.ndSelection.targetGroup)) + %l1 = "Planting as: \c3" @ %client.ndSelection.targetGroup.name; + else + %l1 = "Size: \c3" @ %x @ "\c6 x \c3" @ %y @ "\c6 x \c3" @ %z @ "\c6 Plates"; + + %r0 = "Use normal ghost brick controls"; + %r1 = "[Cancel Brick] to exit plant mode"; + + return ndFormatMessage(%title, %l0, %r0, %l1, %r1); +} + + + +//Functions +/////////////////////////////////////////////////////////////////////////// + +//Move the bricks to a specific location, like with the brick tool +function NDM_PlantCopy::moveBricksTo(%his, %client, %pos, %normal) +{ + //Get half size of world box for offset + if(%client.ndPivot) + %box = %client.ndSelection.getGhostWorldBox(); + else + %box = %client.ndSelection.ghostGroup.getObject(0).getWorldBox(); + + %halfSize = vectorScale(vectorSub(getWords(%box, 3, 5), getWords(%box, 0, 2)), 0.5); + + //Point offset in correct direction based on normal + %offX = getWord(%halfSize, 0) * mFloatLength(getWord(%normal, 0), 0); + %offY = getWord(%halfSize, 1) * mFloatLength(getWord(%normal, 1), 0); + %offZ = getWord(%halfSize, 2) * mFloatLength(getWord(%normal, 2), 0); + %offset = %offX SPC %offY SPC %offZ; + + //Get shift vector + %pos = vectorSub(vectorAdd(%pos, %offset), %client.ndSelection.ghostPosition); + + if(%client.ndPivot) + { + %toCenter = %client.ndSelection.rootToCenter; + + //Apply mirror + if(%client.ndSelection.ghostMirrorX) + %toCenter = -firstWord(%toCenter) SPC restWords(%toCenter); + else if(%client.ndSelection.ghostMirrorY) + %toCenter = getWord(%toCenter, 0) SPC -getWord(%toCenter, 1) SPC getWord(%toCenter, 2); + + if(%client.ndSelection.ghostMirrorZ) + %toCenter = getWord(%toCenter, 0) SPC getWord(%toCenter, 1) SPC -getWord(%toCenter, 2); + + %pos = vectorSub(%pos, ndRotateVector(%toCenter, %client.ndSelection.ghostAngleID)); + } + + %client.ndSelection.shiftGhostBricks(%pos); + + //Offset required for New Brick Tool to display the tracer shape correctly + if(%client.ndPivot) + return vectorSub(%client.ndSelection.getGhostCenter(), %offset); + else + return vectorSub(%client.ndSelection.ghostGroup.getObject(0).getWorldBoxCenter(), %offset); +} + +//Check time limit and attempt to plant bricks +function NDM_PlantCopy::conditionalPlant(%this, %client, %force) +{ + //Check timeout + if(!%client.isAdmin && %client.ndLastPlantTime + ($Pref::Server::ND::PlantTimeoutMS / 1000) > $Sim::Time) + { + %remain = mCeil(%client.ndLastPlantTime + ($Pref::Server::ND::PlantTimeoutMS / 1000) - $Sim::Time); + + if(%remain != 1) + %s = "s"; + + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6You need to wait\c3 " @ %remain @ "\c6 second" @ %s @ " before planting again!", 5); + return; + } + + //Check too far distance + %offset = vectorSub(%client.ndSelection.getGhostCenter(), %client.getControlObject().position); + + if(vectorLen(%offset) > $Pref::Server::TooFarDistance) + { + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6You can't plant so far away!", 5); + return; + } + + //Validate target group + if(isObject(%client.ndSelection.targetGroup) && + getTrustLevel(%client, %client.ndSelection.targetGroup) < 1 && + (!%client.isAdmin || !$Pref::Server::ND::AdminTrustBypass2)) + { + messageClient(%client, '', "\c6You need build trust with \c3" + @ %client.ndSelection.targetGroup.name @ "\c6 to plant bricks in their group."); + + return; + } + + %client.ndLastPlantTime = $Sim::Time; + + %pos = %client.ndSelection.ghostPosition; + %ang = %client.ndSelection.ghostAngleID; + + %client.ndSetMode(NDM_PlantCopyProgress); + %client.ndSelection.startPlant(%pos, %ang, %force); +} diff --git a/classes/server/duplimode/plantcopyprogress.cs b/classes/server/duplimode/plantcopyprogress.cs new file mode 100644 index 0000000..8e3bd85 --- /dev/null +++ b/classes/server/duplimode/plantcopyprogress.cs @@ -0,0 +1,62 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_PlantCopyProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_PlantCopyProgress::onCancelBrick(%this, %client) +{ + commandToClient(%client, 'centerPrint', "\c6Planting canceled!", 4); + + %client.ndSelection.cancelPlanting(); + %client.ndSetMode(NDM_PlantCopy); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_PlantCopyProgress::getBottomPrint(%this, %client) +{ + %qIndex = %client.ndSelection.plantQueueIndex; + %qCount = %client.ndSelection.plantQueueCount; + + %count = %client.ndSelection.brickCount; + %planted = %client.ndSelection.plantSuccessCount; + + if(%qIndex == %qCount) + { + //Searching for a brick + %pIndex = %client.ndSelection.plantSearchIndex; + %percent = mFloor(%client.ndSelection.plantSearchIndex * 100 / %count); + + %title = "Finding Next Brick... (\c3" @ %percent @ "%\c6, \c3" @ %planted @ "\c6 planted)"; + } + else + { + //Planting bricks + %failed = %client.ndSelection.plantTrustFailCount + %client.ndSelection.plantBlockedFailCount; + %percent = mFloor(%planted * 100 / %count); + + %title = "Planting... (\c3" @ %percent @ "%\c6, \c3" @ %failed @ "\c6 failed)"; + } + + %l0 = "[Cancel Brick]: Cancel planting"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/saveprogress.cs b/classes/server/duplimode/saveprogress.cs new file mode 100644 index 0000000..dc6d68c --- /dev/null +++ b/classes/server/duplimode/saveprogress.cs @@ -0,0 +1,43 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_SaveProgress::onKillMode(%this, %client) +{ + //Destroy selection + %client.ndSelection.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_SaveProgress::onCancelBrick(%this, %client) +{ + %client.ndSelection.cancelSaving(); + %client.ndSetMode(NDM_PlantCopy); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_SaveProgress::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + %index = %client.ndSelection.saveIndex / 2 + %client.ndSelection.saveStage * %count / 2; + + %percent = mFloor(%index * 100 / %count); + + %title = "Saving Selection... (\c3" @ %percent @ "%\c6)"; + %l0 = "[Cancel Brick]: Cancel saving"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/stackselect.cs b/classes/server/duplimode/stackselect.cs new file mode 100644 index 0000000..df49ace --- /dev/null +++ b/classes/server/duplimode/stackselect.cs @@ -0,0 +1,210 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Switch to this mode +function NDM_StackSelect::onStartMode(%this, %client, %lastMode) +{ + %client.ndLastSelectMode = %this; + %client.ndUpdateBottomPrint(); +} + +//Switch away from this mode +function NDM_StackSelect::onChangeMode(%this, %client, %nextMode) +{ + if(%nextMode == $NDM::FillColor + || %nextMode == $NDM::PlantCopy + || %nextMode == $NDM::WrenchProgress) + { + //Start de-highlighting the bricks + %client.ndSelection.deHighlight(); + } + + //The transition to box select mode will be + //handled in NDM_BoxSelect::onStartMode +} + +//Kill this mode +function NDM_StackSelect::onKillMode(%this, %client) +{ + //Destroy selection + if(isObject(%client.ndSelection)) + %client.ndSelection.delete(); +} + + + +//Duplicator image callbacks +/////////////////////////////////////////////////////////////////////////// + +//Selecting an object with the duplicator +function NDM_StackSelect::onSelectObject(%this, %client, %obj, %pos, %normal) +{ + if((%obj.getType() & $TypeMasks::FxBrickAlwaysObjectType) == 0) + return; + + //Check timeout + if(!%client.isAdmin && %client.ndLastSelectTime + ($Pref::Server::ND::SelectTimeoutMS / 1000) > $Sim::Time) + { + %remain = mCeil(%client.ndLastSelectTime + ($Pref::Server::ND::SelectTimeoutMS / 1000) - $Sim::Time); + + if(%remain != 1) + %s = "s"; + + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6You need to wait\c3 " @ + %remain @ "\c6 second" @ %s @ " before selecting again!", 5); + + return; + } + + %client.ndLastSelectTime = $Sim::Time; + + if(!ndTrustCheckMessage(%obj, %client)) + return; + + //Prepare selection to copy the bricks + if(!isObject(%client.ndSelection)) + %client.ndSelection = ND_Selection(%client); + + //Start selection + %client.ndSetMode(NDM_StackSelectProgress); + + if(%client.ndMultiSelect) + %client.ndSelection.startStackSelectionAdditive(%obj, %client.ndDirection, %client.ndLimited); + else + %client.ndSelection.startStackSelection(%obj, %client.ndDirection, %client.ndLimited); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Light key +function NDM_StackSelect::onLight(%this, %client) +{ + if($Pref::Server::ND::PlayMenuSounds) + %client.play2d(lightOnSound); + + %client.ndSetMode(NDM_BoxSelect); +} + +//Next Seat +function NDM_StackSelect::onNextSeat(%this, %client) +{ + %client.ndDirection = !%client.ndDirection; + %client.ndUpdateBottomPrint(); + + if($Pref::Server::ND::PlayMenuSounds) + %client.play2d(%client.ndDirection ? lightOnSound : lightOffSound); +} + +//Prev Seat +function NDM_StackSelect::onPrevSeat(%this, %client) +{ + %client.ndLimited = !%client.ndLimited; + %client.ndUpdateBottomPrint(); + + if($Pref::Server::ND::PlayMenuSounds) + %client.play2d(%client.ndLimited ? lightOnSound : lightOffSound); +} + +//Shift Brick +function NDM_StackSelect::onShiftBrick(%this, %client, %x, %y, %z) +{ + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + return; + + //Change to plant mode and apply the shift + %client.ndSetMode(NDM_PlantCopy); + NDM_PlantCopy.onShiftBrick(%client, %x, %y, %z); +} + +//Super Shift Brick +function NDM_StackSelect::onSuperShiftBrick(%this, %client, %x, %y, %z) +{ + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + return; + + //Change to plant mode and apply the shift + %client.ndSetMode(NDM_PlantCopy); + NDM_PlantCopy.onSuperShiftBrick(%client, %x, %y, %z); +} + +//Rotate Brick +function NDM_StackSelect::onRotateBrick(%this, %client, %dir) +{ + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + return; + + //Change to plant mode and apply the shift + %client.ndSetMode(NDM_PlantCopy); + NDM_PlantCopy.onRotateBrick(%client, %dir); +} + +//Plant Brick +function NDM_StackSelect::onPlantBrick(%this, %client) +{ + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + return; + + %client.ndSetMode(NDM_PlantCopy); +} + +//Cancel Brick +function NDM_StackSelect::onCancelBrick(%this, %client) +{ + if(isObject(%client.ndSelection)) + %client.ndSelection.deleteData(); + + %client.ndUpdateBottomPrint(); +} + +//Copy Selection +function NDM_StackSelect::onCopy(%this, %client) +{ + %this.onPlantBrick(%client); +} + +//Cut Selection +function NDM_StackSelect::onCut(%this, %client) +{ + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + return; + + %client.ndSetMode(NDM_CutProgress); + %client.ndSelection.startCutting(); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_StackSelect::getBottomPrint(%this, %client) +{ + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + { + %title = "Selection Mode"; + %r0 = "Click Brick: Select stack " @ (%client.ndDirection ? "up" : "down"); + %r1 = ""; + } + else + { + %count = %client.ndSelection.brickCount; + + %title = "Selection Mode (\c3" @ %count @ "\c6 Brick" @ (%count > 1 ? "s)" : ")"); + %r0 = "Ctrl-Click Brick: Multiselect"; + %r1 = "[Plant Brick]: Duplicate"; + } + + %l0 = "Type: \c3" @ (%client.ndMultiSelect ? "Multi-" : "") @ "Stack \c6[Light]"; + %l1 = "Limited: " @ (%client.ndLimited ? "\c3Yes" : "\c0No") @ " \c6[Prev Seat]"; + %l2 = "Direction: \c3" @ (%client.ndDirection ? "Up" : "Down") @ " \c6[Next Seat]"; + + return ndFormatMessage(%title, %l0, %r0, %l1, %r1, %l2); +} diff --git a/classes/server/duplimode/stackselectprogress.cs b/classes/server/duplimode/stackselectprogress.cs new file mode 100644 index 0000000..956abc2 --- /dev/null +++ b/classes/server/duplimode/stackselectprogress.cs @@ -0,0 +1,43 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_StackSelectProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_StackSelectProgress::onCancelBrick(%this, %client) +{ + commandToClient(%client, 'centerPrint', "\c6Selection canceled!", 4); + + %client.ndSelection.cancelStackSelection(); + %client.ndSetMode(NDM_StackSelect); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_StackSelectProgress::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + %qCount = %client.ndSelection.queueCount - %count; + + %title = "Selecting... (\c3" @ %count @ "\c6 Bricks, \c3" @ %qCount @ "\c6 in Queue)"; + %l0 = "[Cancel Brick]: Cancel selection"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/supercutprogress.cs b/classes/server/duplimode/supercutprogress.cs new file mode 100644 index 0000000..554afb2 --- /dev/null +++ b/classes/server/duplimode/supercutprogress.cs @@ -0,0 +1,50 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_SuperCutProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); + + //Remove selection box + %client.ndSelectionBox.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_SuperCutProgress::onCancelBrick(%this, %client) +{ + commandToClient(%client, 'centerPrint', "\c6Supercut canceled!", 4); + + %client.ndSelection.cancelSuperCut(); + %client.ndSetMode(NDM_BoxSelect); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_SuperCutProgress::getBottomPrint(%this, %client) +{ + %curr = %client.ndSelection.currChunk + 1; + %num = %client.ndSelection.numChunks; + + %percent = mFloor(%curr * 100 / %num); + %deleted = %client.ndSelection.superCutCount; + %planted = %client.ndSelection.superCutPlacedCount; + + %title = "Supercut in progress... (\c3" @ %percent @ "%\c6, \c3" @ %deleted @ "\c6 deleted, \c3" @ %planted @ "\c6 planted)"; + %l0 = "[Cancel Brick]: Cancel supercut"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/duplimode/wrenchprogress.cs b/classes/server/duplimode/wrenchprogress.cs new file mode 100644 index 0000000..b5fa6c1 --- /dev/null +++ b/classes/server/duplimode/wrenchprogress.cs @@ -0,0 +1,45 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Changing modes +/////////////////////////////////////////////////////////////////////////// + +//Kill this mode +function NDM_WrenchProgress::onKillMode(%this, %client) +{ + //Destroy the selection + %client.ndSelection.delete(); + + //Remove the selection box + if(isObject(%client.ndSelectionBox)) + %client.ndSelectionBox.delete(); +} + + + +//Generic inputs +/////////////////////////////////////////////////////////////////////////// + +//Cancel Brick +function NDM_WrenchProgress::onCancelBrick(%this, %client) +{ + %client.ndSelection.cancelFillWrench(); + %client.ndSetMode(%client.ndLastSelectMode); +} + + + +//Interface +/////////////////////////////////////////////////////////////////////////// + +//Create bottomprint for client +function NDM_WrenchProgress::getBottomPrint(%this, %client) +{ + %count = %client.ndSelection.brickCount; + %percent = mFloor(%client.ndSelection.wrenchIndex * 100 / %count); + + %title = "Applying... (\c3" @ %percent @ "%\c6)"; + %l0 = "[Cancel Brick]: Cancel"; + + return ndFormatMessage(%title, %l0); +} diff --git a/classes/server/ghostgroup.cs b/classes/server/ghostgroup.cs new file mode 100644 index 0000000..85dc1f9 --- /dev/null +++ b/classes/server/ghostgroup.cs @@ -0,0 +1,38 @@ +// Deletes large numbers of ghost bricks without causing lag. +// ------------------------------------------------------------------- + +//Create a new ghost group +function ND_GhostGroup() +{ + ND_ServerGroup.add( + %this = new ScriptGroup(ND_GhostGroup) + ); + + return %this; +} + +//Delete some of the bricks in this group +function ND_GhostGroup::tickDelete(%this) +{ + %max = $Pref::Server::ND::ProcessPerTick; + %bricks = getBrickCount(); + + //Deleting objects causes increasing lag with more bricks in total + if(%bricks > 450000) + %max /= 6; + else if(%bricks > 300000) + %max /= 4; + else if(%bricks > 150000) + %max /= 2; + + if(%this.getCount() <= %max) + { + %this.delete(); + return; + } + + for(%i = 0; %i < %max; %i++) + %this.getObject(0).delete(); + + %this.schedule(30, tickDelete); +} diff --git a/classes/server/highlightbox.cs b/classes/server/highlightbox.cs new file mode 100644 index 0000000..e8351bd --- /dev/null +++ b/classes/server/highlightbox.cs @@ -0,0 +1,112 @@ +// Resizable highlight box to visualize the size of the selection. +// ------------------------------------------------------------------- + +//Create a new highlight box +function ND_HighlightBox() +{ + ND_ServerGroup.add( + %this = new ScriptObject(ND_HighlightBox) + ); + + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i] = new StaticShape(){datablock = ND_SelectionBoxBorder;}; + %this.border_y[%i] = new StaticShape(){datablock = ND_SelectionBoxBorder;}; + %this.border_z[%i] = new StaticShape(){datablock = ND_SelectionBoxBorder;}; + + %this.border_x[%i].setScopeAlways(); + %this.border_y[%i].setScopeAlways(); + %this.border_z[%i].setScopeAlways(); + } + + %this.color = "1 0.84 0 0.99"; + %this.applyColors(); + + return %this; +} + +//Destroy static shapes when highlight box is removed +function ND_HighlightBox::onRemove(%this) +{ + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i].delete(); + %this.border_y[%i].delete(); + %this.border_z[%i].delete(); + } +} + +//Apply color changes to the highlight box +function ND_HighlightBox::applyColors(%this) +{ + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i].setNodeColor("ALL", %this.color); + %this.border_y[%i].setNodeColor("ALL", %this.color); + %this.border_z[%i].setNodeColor("ALL", %this.color); + } +} + +//Return current size of highlight box +function ND_HighlightBox::getSize(%this) +{ + return %this.point1 SPC %this.point2; +} + +//Resize the highlight box +function ND_HighlightBox::setSize(%this, %point1, %point2) +{ + if(getWordCount(%point1) == 6) + { + %point2 = getWords(%point1, 3, 5); + %point1 = getWords(%point1, 0, 2); + } + + %this.point1 = %point1; + %this.point2 = %point2; + + %x1 = getWord(%point1, 0); + %y1 = getWord(%point1, 1); + %z1 = getWord(%point1, 2); + + %x2 = getWord(%point2, 0); + %y2 = getWord(%point2, 1); + %z2 = getWord(%point2, 2); + + %len_x = %x2 - %x1; + %len_y = %y2 - %y1; + %len_z = %z2 - %z1; + + %center_x = (%x1 + %x2) / 2; + %center_y = (%y1 + %y2) / 2; + %center_z = (%z1 + %z2) / 2; + + %rot_x = "0 1 0 1.57079"; + %rot_y = "1 0 0 1.57079"; + %rot_z = "0 0 1 0"; + + %this.border_x0.setTransform(%center_x SPC %y1 SPC %z1 SPC %rot_x); + %this.border_x1.setTransform(%center_x SPC %y2 SPC %z1 SPC %rot_x); + %this.border_x2.setTransform(%center_x SPC %y2 SPC %z2 SPC %rot_x); + %this.border_x3.setTransform(%center_x SPC %y1 SPC %z2 SPC %rot_x); + + %this.border_y0.setTransform(%x1 SPC %center_y SPC %z1 SPC %rot_y); + %this.border_y1.setTransform(%x2 SPC %center_y SPC %z1 SPC %rot_y); + %this.border_y2.setTransform(%x2 SPC %center_y SPC %z2 SPC %rot_y); + %this.border_y3.setTransform(%x1 SPC %center_y SPC %z2 SPC %rot_y); + + %this.border_z0.setTransform(%x1 SPC %y1 SPC %center_z SPC %rot_z); + %this.border_z1.setTransform(%x2 SPC %y1 SPC %center_z SPC %rot_z); + %this.border_z2.setTransform(%x2 SPC %y2 SPC %center_z SPC %rot_z); + %this.border_z3.setTransform(%x1 SPC %y2 SPC %center_z SPC %rot_z); + + %maxLen = getMax(getMax(%len_x, %len_y), %len_z); + %width = (7 / 1280) * %maxLen + 1; + + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i].setScale(%width SPC %width SPC %len_x + %width * 0.05); + %this.border_y[%i].setScale(%width SPC %width SPC %len_y + %width * 0.05); + %this.border_z[%i].setScale(%width SPC %width SPC %len_z + %width * 0.05); + } +} diff --git a/classes/server/selection.cs b/classes/server/selection.cs new file mode 100644 index 0000000..1b8d7d7 --- /dev/null +++ b/classes/server/selection.cs @@ -0,0 +1,4494 @@ +// 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; +} diff --git a/classes/server/selectionbox.cs b/classes/server/selectionbox.cs new file mode 100644 index 0000000..9387bec --- /dev/null +++ b/classes/server/selectionbox.cs @@ -0,0 +1,496 @@ +// Resizeable box to select all bricks inside a volume. +// ------------------------------------------------------------------- + +//Create a new selection box +function ND_SelectionBox(%shapeName) +{ + ND_ServerGroup.add( + %this = new ScriptObject(ND_SelectionBox) + ); + + %this.innerBox = new StaticShape(){datablock = ND_SelectionBoxInner;}; + %this.outerBox = new StaticShape(){datablock = ND_SelectionBoxOuter;}; + %this.shapeName = new StaticShape(){datablock = ND_SelectionBoxShapeName;}; + + %this.corner1 = new StaticShape(){datablock = ND_SelectionBoxOuter;}; + %this.corner2 = new StaticShape(){datablock = ND_SelectionBoxOuter;}; + %this.selectedCorner = true; + + %this.innerBox.setScopeAlways(); + %this.outerBox.setScopeAlways(); + %this.shapeName.setScopeAlways(); + %this.corner1.setScopeAlways(); + %this.corner2.setScopeAlways(); + + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i] = new StaticShape(){datablock = ND_SelectionBoxBorder;}; + %this.border_y[%i] = new StaticShape(){datablock = ND_SelectionBoxBorder;}; + %this.border_z[%i] = new StaticShape(){datablock = ND_SelectionBoxBorder;}; + + %this.border_x[%i].setScopeAlways(); + %this.border_y[%i].setScopeAlways(); + %this.border_z[%i].setScopeAlways(); + } + + %this.boxName = %shapeName; + %this.setNormalMode(); + + return %this; +} + +//Destroy static shapes when selection box is removed +function ND_SelectionBox::onRemove(%this) +{ + %this.innerBox.delete(); + %this.outerBox.delete(); + %this.shapeName.delete(); + + %this.corner1.delete(); + %this.corner2.delete(); + + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i].delete(); + %this.border_y[%i].delete(); + %this.border_z[%i].delete(); + } +} + +//Set normal color values and borders +function ND_SelectionBox::setNormalMode(%this) +{ + %this.innerColor = "0 0 0 0.60"; + %this.outerColor = "0 0 0 0.35"; + + %this.borderColor = "1 0.84 0 0.99"; + %this.borderColorSelected = "0 0 1 0.99"; + + %this.cornerColor1 = "0.8 0.74 0 0.99"; + %this.cornerColor2 = "1 0.94 0.1 0.99"; + + %this.cornerColorSelected1 = "0 0.2 1 0.99"; + %this.cornerColorSelected2 = "0 0.1 0.9 0.99"; + + %this.isNormalMode = true; + + //Unhide the corners and inner/outer box (hidden in disabled mode) + %this.innerBox.unHideNode("ALL"); + %this.corner1.unHideNode("ALL"); + %this.corner2.unHideNode("ALL"); + + if(%this.hasVolume()) + %this.outerBox.unHideNode("ALL"); + + //Apply changes + %this.applyColors(); + %this.setSize(%this.point1, %this.point2); + %this.shapeName.setShapeName(%this.boxName); +} + +//Set grayscale color values and slightly smaller border +function ND_SelectionBox::setDisabledMode(%this) +{ + %this.borderColor = "0.1 0.1 0.1 0.4"; + %this.borderColorSelected = "0.1 0.1 0.1 0.4"; + + %this.isNormalMode = false; + + //Hide the corners and inner/outer box (looks better) + %this.innerBox.hideNode("ALL"); + %this.outerBox.hideNode("ALL"); + %this.corner1.hideNode("ALL"); + %this.corner2.hideNode("ALL"); + + //Apply changes + %this.applyColors(); + %this.setSize(%this.point1, %this.point2); + %this.shapeName.setShapeName(""); +} + +//Apply color changes to the selection box +function ND_SelectionBox::applyColors(%this) +{ + %this.innerBox.setNodeColor("ALL", %this.innerColor); + %this.outerBox.setNodeColor("ALL", %this.outerColor); + + %this.shapeName.setShapeNameColor(%this.borderColor); + + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i].setNodeColor("ALL", %this.borderColor); + %this.border_y[%i].setNodeColor("ALL", %this.borderColor); + %this.border_z[%i].setNodeColor("ALL", %this.borderColor); + } + + %bColor = %this.borderColorSelected; + + if(%this.selectedCorner) + { + %this.border_x2.setNodeColor("ALL", %bColor); + %this.border_y2.setNodeColor("ALL", %bColor); + %this.border_z2.setNodeColor("ALL", %bColor); + + %corner1 = %this.corner1; + %corner2 = %this.corner2; + } + else + { + %this.border_x0.setNodeColor("ALL", %bColor); + %this.border_y0.setNodeColor("ALL", %bColor); + %this.border_z0.setNodeColor("ALL", %bColor); + + %corner1 = %this.corner2; + %corner2 = %this.corner1; + } + + %corner1.setNodeColor("out+X", %this.borderColor); + %corner1.setNodeColor("out-X", %this.borderColor); + %corner1.setNodeColor("out+Y", %this.cornerColor1); + %corner1.setNodeColor("out-Y", %this.cornerColor1); + %corner1.setNodeColor("out+Z", %this.cornerColor2); + %corner1.setNodeColor("out-Z", %this.cornerColor2); + + //Illusion of shaded box + %corner2.setNodeColor("out+X", %this.borderColorSelected); + %corner2.setNodeColor("out-X", %this.borderColorSelected); + %corner2.setNodeColor("out+Y", %this.cornerColorSelected1); + %corner2.setNodeColor("out-Y", %this.cornerColorSelected1); + %corner2.setNodeColor("out+Z", %this.cornerColorSelected2); + %corner2.setNodeColor("out-Z", %this.cornerColorSelected2); +} + +//Return current size of selection box +function ND_SelectionBox::getSize(%this) +{ + %x1 = getWord(%this.point1, 0); + %y1 = getWord(%this.point1, 1); + %z1 = getWord(%this.point1, 2); + + %x2 = getWord(%this.point2, 0); + %y2 = getWord(%this.point2, 1); + %z2 = getWord(%this.point2, 2); + + %min = getMin(%x1, %x2) SPC getMin(%y1, %y2) SPC getMin(%z1, %z2); + %max = getMax(%x1, %x2) SPC getMax(%y1, %y2) SPC getMax(%z1, %z2); + + return vectorSub(%max, %min); +} + +//Return current world box of selection box +function ND_SelectionBox::getWorldBox(%this) +{ + %x1 = getWord(%this.point1, 0); + %y1 = getWord(%this.point1, 1); + %z1 = getWord(%this.point1, 2); + + %x2 = getWord(%this.point2, 0); + %y2 = getWord(%this.point2, 1); + %z2 = getWord(%this.point2, 2); + + %min = getMin(%x1, %x2) SPC getMin(%y1, %y2) SPC getMin(%z1, %z2); + %max = getMax(%x1, %x2) SPC getMax(%y1, %y2) SPC getMax(%z1, %z2); + + return %min SPC %max; +} + +//Resize the selection box +function ND_SelectionBox::setSize(%this, %point1, %point2) +{ + if(getWordCount(%point1) == 6) + { + %point2 = getWords(%point1, 3, 5); + %point1 = getWords(%point1, 0, 2); + } + + %this.point1 = %point1; + %this.point2 = %point2; + + %x1 = getWord(%point1, 0); + %y1 = getWord(%point1, 1); + %z1 = getWord(%point1, 2); + + %x2 = getWord(%point2, 0); + %y2 = getWord(%point2, 1); + %z2 = getWord(%point2, 2); + + %len_x = mAbs(%x2 - %x1); + %len_y = mAbs(%y2 - %y1); + %len_z = mAbs(%z2 - %z1); + + %center_x = (%x1 + %x2) / 2; + %center_y = (%y1 + %y2) / 2; + %center_z = (%z1 + %z2) / 2; + + %rot_x = "0 1 0 1.57079"; + %rot_y = "1 0 0 1.57079"; + %rot_z = "0 0 1 0"; + + %this.innerBox.setTransform(%center_x SPC %center_y SPC %center_z); + %this.outerBox.setTransform(%center_x SPC %center_y SPC %center_z); + %this.shapeName.setTransform(%center_X SPC %center_y SPC %z2); + + %this.border_x0.setTransform(%center_x SPC %y1 SPC %z1 SPC %rot_x); + %this.border_x1.setTransform(%center_x SPC %y2 SPC %z1 SPC %rot_x); + %this.border_x2.setTransform(%center_x SPC %y2 SPC %z2 SPC %rot_x); + %this.border_x3.setTransform(%center_x SPC %y1 SPC %z2 SPC %rot_x); + + %this.border_y0.setTransform(%x1 SPC %center_y SPC %z1 SPC %rot_y); + %this.border_y1.setTransform(%x2 SPC %center_y SPC %z1 SPC %rot_y); + %this.border_y2.setTransform(%x2 SPC %center_y SPC %z2 SPC %rot_y); + %this.border_y3.setTransform(%x1 SPC %center_y SPC %z2 SPC %rot_y); + + %this.border_z0.setTransform(%x1 SPC %y1 SPC %center_z SPC %rot_z); + %this.border_z1.setTransform(%x2 SPC %y1 SPC %center_z SPC %rot_z); + %this.border_z2.setTransform(%x2 SPC %y2 SPC %center_z SPC %rot_z); + %this.border_z3.setTransform(%x1 SPC %y2 SPC %center_z SPC %rot_z); + + %this.corner1.setTransform(%x1 SPC %y1 SPC %z1); + %this.corner2.setTransform(%x2 SPC %y2 SPC %z2); + + %this.innerBox.setScale(%len_x - 0.02 SPC %len_y - 0.02 SPC %len_z - 0.02); + %this.outerBox.setScale(%len_x + 0.02 SPC %len_y + 0.02 SPC %len_z + 0.02); + + if(%this.isNormalMode) + { + //Normal mode (box with two colored corners) + %maxLen = getMax(getMax(%len_x, %len_y), %len_z); + %width = (7/1024) * %maxLen + 1; + + for(%i = 0; %i < 4; %i++) + { + %this.border_x[%i].setScale(%width SPC %width SPC %len_x + %width * 0.05); + %this.border_y[%i].setScale(%width SPC %width SPC %len_y + %width * 0.05); + %this.border_z[%i].setScale(%width SPC %width SPC %len_z + %width * 0.05); + } + + if(%this.selectedCorner) + { + %width1 = %width; + %width2 = %width + 0.02; + } + else + { + %width1 = %width + 0.02; + %width2 = %width; + } + + //The borders touching the two corners are thicker to prevent Z fighting + //with the highlight box if it covers the same area as the selection + %this.border_x0.setScale(%width1 SPC %width1 SPC %len_x - %width * 0.05); + %this.border_y0.setScale(%width1 SPC %width1 SPC %len_y - %width * 0.05); + %this.border_z0.setScale(%width1 SPC %width1 SPC %len_z - %width * 0.05); + + %this.border_x2.setScale(%width2 SPC %width2 SPC %len_x - %width * 0.05); + %this.border_y2.setScale(%width2 SPC %width2 SPC %len_y - %width * 0.05); + %this.border_z2.setScale(%width2 SPC %width2 SPC %len_z - %width * 0.05); + + //Corners scale with the border width + %cs1 = 0.35 * %width1; + %cs2 = 0.35 * %width2; + + %this.corner1.setScale(%cs1 SPC %cs1 SPC %cs1); + %this.corner2.setScale(%cs2 SPC %cs2 SPC %cs2); + } + else + { + //Disabled mode (transparent greyscale box) + %maxLen = getMax(getMax(%len_x, %len_y), %len_z); + %width = (21/5120) * %maxLen + 1; + + for(%i = 0; %i < 4; %i++) + { + //Horizontal borders are a bit shorter to prevent z fighting + %this.border_x[%i].setScale(%width SPC %width SPC %len_x - %width * 0.05); + %this.border_y[%i].setScale(%width SPC %width SPC %len_y - %width * 0.05); + + %this.border_z[%i].setScale(%width SPC %width SPC %len_z + %width * 0.05); + } + } +} + +//Resize the selection box and align it to a player +function ND_SelectionBox::setSizeAligned(%this, %point1, %point2, %player) +{ + //Set the selection box to correct orientation + %x1 = getWord(%point1, 0); + %y1 = getWord(%point1, 1); + %z1 = getWord(%point1, 2); + + %x2 = getWord(%point2, 0); + %y2 = getWord(%point2, 1); + %z2 = getWord(%point2, 2); + + switch(getAngleIDFromPlayer(%player)) + { + case 0: + %p1 = %x1 SPC %y2 SPC %z1; + %p2 = %x2 SPC %y1 SPC %z2; + + case 1: + %p1 = %x1 SPC %y1 SPC %z1; + %p2 = %x2 SPC %y2 SPC %z2; + + case 2: + %p1 = %x2 SPC %y1 SPC %z1; + %p2 = %x1 SPC %y2 SPC %z2; + + case 3: + %p1 = %x2 SPC %y2 SPC %z1; + %p2 = %x1 SPC %y1 SPC %z2; + } + + //Select first corner + if(!%this.selectedCorner) + { + %this.selectedCorner = true; + %this.applyColors(); + } + + %this.setSize(%p1, %p2); +} + +//Select one of the two corners +function ND_SelectionBox::switchCorner(%this) +{ + %this.selectedCorner = !%this.selectedCorner; + + if(%this.selectedCorner) + serverPlay3d(BrickRotateSound, %this.point2); + else + serverPlay3d(BrickRotateSound, %this.point1); + + %this.setSize(%this.point1, %this.point2); + %this.applyColors(); +} + +//Move the selected corner +function ND_SelectionBox::shiftCorner(%this, %offset, %limit) +{ + %oldP1 = %this.point1; + %oldP2 = %this.point2; + %limitReached = false; + + //Size of a plate in TU + %unit[0] = 0.5; + %unit[1] = 0.5; + %unit[2] = 0.2; + + for(%dim = 0; %dim < 3; %dim++) + { + //Copy current + %point1[%dim] = getWord(%this.point1, %dim); + %point2[%dim] = getWord(%this.point2, %dim); + + //Get the size of the box in the current axis after resizing + %size = getWord(%this.point2, %dim) - getWord(%this.point1, %dim); + + if(%this.selectedCorner) + { + //Update point2 + %size += getWord(%offset, %dim); + %point2[%dim] += getWord(%offset, %dim); + + //Check limits + if(mAbs(%size) > %limit) + { + %limitReached = true; + %point2[%dim] -= %size - %limit * (mAbs(%size) / %size); + } + } + else + { + //Update point1 + %size -= getWord(%offset, %dim); + %point1[%dim] += getWord(%offset, %dim); + + //Check limits + if(mAbs(%size) > %limit) + { + %limitReached = true; + %point1[%dim] += %size - %limit * (mAbs(%size) / %size); + } + } + } + + //Update corner positions + %point1 = %point1[0] SPC %point1[1] SPC %point1[2]; + %point2 = %point2[0] SPC %point2[1] SPC %point2[2]; + %this.setSize(%point1, %point2); + + //Play sounds + if(%this.selectedCorner) + %soundPoint = %point2; + else + %soundPoint = %point1; + + if(%point1 !$= %oldP1 || %point2 !$= %oldP2) + serverPlay3d(BrickMoveSound, %soundPoint); + else + serverPlay3d(errorSound, %soundPoint); + + //Hide outer box on selection boxes without volume + if(%this.hasVolume()) + %this.outerBox.unHideNode("ALL"); + else + %this.outerBox.hideNode("ALL"); + + return %limitReached; +} + +//Move the entire box +function ND_SelectionBox::shift(%this, %offset) +{ + %this.point1 = vectorAdd(%this.point1, %offset); + %this.point2 = vectorAdd(%this.point2, %offset); + + %this.setSize(%this.point1, %this.point2); + + //Play sounds + if(%this.selectedCorner) + serverPlay3d(BrickMoveSound, %this.point1); + else + serverPlay3d(BrickMoveSound, %this.point2); +} + +//Rotate the entire box +function ND_SelectionBox::rotate(%this, %direction) +{ + %point1 = %this.point1; + %point2 = %this.point2; + %center = vectorScale(vectorAdd(%point1, %point2), 0.5); + + %brickSizeX = mAbs(mFloatLength((getWord(%point2, 0) - getWord(%point1, 0)) * 2, 0)); + %brickSizeY = mAbs(mFloatLength((getWord(%point2, 1) - getWord(%point1, 1)) * 2, 0)); + + %shiftCorrect = "0 0 0"; + + if((%brickSizeX % 2) != (%brickSizeY % 2)) + { + if(%brickSizeX % 2) + %shiftCorrect = "-0.25 -0.25 0"; + else + %shiftCorrect = "0.25 0.25 0"; + } + + %point1 = vectorAdd(ndRotateVector(vectorSub(%point1, %center), %direction), %center); + %point2 = vectorAdd(ndRotateVector(vectorSub(%point2, %center), %direction), %center); + %this.setSize(vectorAdd(%point1, %shiftCorrect), vectorAdd(%point2, %shiftCorrect)); + + //Play sounds + if(%this.selectedCorner) + serverPlay3d(BrickRotateSound, %this.point1); + else + serverPlay3d(BrickRotateSound, %this.point2); +} + +//Check if the box has a volume +function ND_SelectionBox::hasVolume(%this) +{ + if(mAbs(getWord(%this.point1, 0) - getWord(%this.point2, 0)) < 0.05 + || mAbs(getWord(%this.point1, 1) - getWord(%this.point2, 1)) < 0.05 + || mAbs(getWord(%this.point1, 2) - getWord(%this.point2, 2)) < 0.05) + return false; + + return true; +} diff --git a/classes/server/undogrouppaint.cs b/classes/server/undogrouppaint.cs new file mode 100644 index 0000000..bf6d8a1 --- /dev/null +++ b/classes/server/undogrouppaint.cs @@ -0,0 +1,75 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Delete this undo group +function ND_UndoGroupPaint::onRemove(%this) +{ + if(%this.brickCount) + deleteVariables("$NU" @ %this.client @ "_" @ %this @ "_*"); +} + +//Start undo paint +function ND_UndoGroupPaint::ndStartUndo(%this, %client) +{ + %client.ndUndoInProgress = true; + %client.ndLastMessageTime = $Sim::Time; + %this.ndTickUndo(%this.paintType, 0, %client); +} + +//Tick undo paint +function ND_UndoGroupPaint::ndTickUndo(%this, %mode, %start, %client) +{ + %end = %start + $Pref::Server::ND::ProcessPerTick; + + if(%end > %this.brickCount) + %end = %this.brickCount; + + for(%i = %start; %i < %end; %i++) + { + %brick = $NU[%client, %this, "B", %i]; + + if(!isObject(%brick)) + continue; + + %colorID = $NU[%client, %this, "V", %i]; + + switch(%mode) + { + case 0: + //Check whether brick is highlighted + %brick.setColor(%colorID); + + case 1: + //Check whether brick is highlighted + if($NDHN[%brick]) + $NDHF[%brick] = %colorID; + else + %brick.setColorFx(%colorID); + + case 2: + %brick.setShapeFx(%colorID); + } + } + + //If undo is taking long, tell the client how far we get + if(%client.ndLastMessageTime + 0.1 < $Sim::Time) + { + %client.ndLastMessageTime = $Sim::Time; + + %percent = mFloor(%end * 100 / %this.brickCount); + commandToClient(%client, 'centerPrint', "\c6Undo in progress...\n\c3" @ %percent @ "%\c6 finished.", 10); + } + + if(%end >= %this.brickcount) + { + %this.delete(); + %client.ndUndoInProgress = false; + + if(%start != 0) + commandToClient(%client, 'centerPrint', "\c6Undo finished.", 2); + + return; + } + + %this.schedule(30, ndTickUndo, %mode, %end, %client); +} diff --git a/classes/server/undogroupplant.cs b/classes/server/undogroupplant.cs new file mode 100644 index 0000000..2c2bb44 --- /dev/null +++ b/classes/server/undogroupplant.cs @@ -0,0 +1,61 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Start undo bricks +function SimSet::ndStartUndo(%this, %client) +{ + if(!%this.brickCount) + { + %this.delete(); + return; + } + + %client.ndUndoInProgress = true; + %client.ndLastMessageTime = $Sim::Time; + %this.ndTickUndo(%this.brickCount, %client); +} + +//Tick undo bricks +function SimSet::ndTickUndo(%this, %count, %client) +{ + if(%count > %this.getCount()) + %start = %this.getCount(); + else + %start = %count; + + if(%start > $Pref::Server::ND::ProcessPerTick) + %end = %start - $Pref::Server::ND::ProcessPerTick; + else + %end = 0; + + for(%i = %start - 1; %i >= %end; %i--) + { + %brick = %this.getObject(%i); + %brick.killBrick(); + + if(%start > 1024) + %brick.delete(); + } + + //If undo is taking long, tell the client how far we get + if(%client.ndLastMessageTime + 0.1 < $Sim::Time) + { + %client.ndLastMessageTime = $Sim::Time; + + %percent = mFloor(100 - (%end * 100 / %this.brickCount)); + commandToClient(%client, 'centerPrint', "\c6Undo in progress...\n\c3" @ %percent @ "%\c6 finished.", 10); + } + + if(%end <= 0) + { + %this.delete(); + %client.ndUndoInProgress = false; + + if(%start != 0) + commandToClient(%client, 'centerPrint', "\c6Undo finished.", 2); + + return; + } + + %this.schedule(30, ndTickUndo, %end, %client); +} diff --git a/classes/server/undogroupwrench.cs b/classes/server/undogroupwrench.cs new file mode 100644 index 0000000..29da4eb --- /dev/null +++ b/classes/server/undogroupwrench.cs @@ -0,0 +1,197 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Delete this undo group +function ND_UndoGroupWrench::onRemove(%this) +{ + if(%this.brickCount) + deleteVariables("$NU" @ %this.client @ "_" @ %this @ "_*"); +} + +//Start undo wrench +function ND_UndoGroupWrench::ndStartUndo(%this, %client) +{ + %client.ndUndoInProgress = true; + %client.ndLastMessageTime = $Sim::Time; + %this.ndTickUndo(0, %client); +} + +//Tick undo wrench +function ND_UndoGroupWrench::ndTickUndo(%this, %start, %client) +{ + %end = %start + $Pref::Server::ND::ProcessPerTick; + + if(%end > %this.brickCount) + %end = %this.brickCount; + + 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; + + for(%i = %start; %i < %end; %i++) + { + %brick = $NU[%client, %this, "B", %i]; + + if(!isObject(%brick)) + continue; + + //Revert wrench settings + if(%fillWrenchName) + { + %curr = getSubStr(%brick.getName(), 1, 254); + %fillWrenchNameValue = $NU[%client, %this, "N", %i]; + + if(%curr !$= %fillWrenchNameValue) + %brick.setNTObjectName(%fillWrenchNameValue); + } + + if(%fillWrenchLight) + { + if(%tmp = %brick.light | 0) + %curr = %tmp.getDatablock(); + else + %curr = 0; + + %fillWrenchLightValue = $NU[%client, %this, "LDB", %i]; + + if(%curr != %fillWrenchLightValue) + %brick.setLight(%fillWrenchLightValue); + } + + if(%fillWrenchEmitter) + { + if(%tmp = %brick.emitter | 0) + %curr = %tmp.getEmitterDatablock(); + else if(%tmp = %brick.oldEmitterDB | 0) + %curr = %tmp; + else + %curr = 0; + + %fillWrenchEmitterValue = $NU[%client, %this, "EDB", %i]; + + if(%curr != %fillWrenchEmitterValue) + %brick.setEmitter(%fillWrenchEmitterValue); + } + + if(%fillWrenchEmitterDir) + { + %curr = %brick.emitterDirection; + %fillWrenchEmitterDirValue = $NU[%client, %this, "EDIR", %i]; + + if(%curr != %fillWrenchEmitterDirValue) + %brick.setEmitterDirection(%fillWrenchEmitterDirValue); + } + + if(%fillWrenchItem) + { + if(%tmp = %brick.item | 0) + %curr = %tmp.getDatablock(); + else + %curr = 0; + + %fillWrenchItemValue = $NU[%client, %this, "IDB", %i]; + + if(%curr != %fillWrenchItemValue) + %brick.setItem(%fillWrenchItemValue); + } + + if(%fillWrenchItemPos) + { + %curr = %brick.itemPosition; + %fillWrenchItemPosValue = $NU[%client, %this, "IPOS", %i]; + + if(%curr != %fillWrenchItemPosValue) + %brick.setItemPosition(%fillWrenchItemPosValue); + } + + if(%fillWrenchItemDir) + { + %curr = %brick.itemPosition; + %fillWrenchItemDirValue = $NU[%client, %this, "IDIR", %i]; + + if(%curr != %fillWrenchItemDirValue) + %brick.setItemDirection(%fillWrenchItemDirValue); + } + + if(%fillWrenchItemTime) + { + %curr = %brick.itemRespawnTime; + %fillWrenchItemTimeValue = $NU[%client, %this, "IRT", %i]; + + if(%curr != %fillWrenchItemTimeValue) + %brick.setItemRespawnTime(%fillWrenchItemTimeValue); + } + + if(%fillWrenchRaycasting) + { + %curr = %brick.isRaycasting(); + %fillWrenchRaycastingValue = $NU[%client, %this, "RC", %i]; + + if(%curr != %fillWrenchRaycastingValue) + %brick.setRaycasting(%fillWrenchRaycastingValue); + } + + if(%fillWrenchCollision) + { + %curr = %brick.isColliding(); + %fillWrenchCollisionValue = $NU[%client, %this, "C", %i]; + + if(%curr != %fillWrenchCollisionValue) + %brick.setColliding(%fillWrenchCollisionValue); + } + + if(%fillWrenchRendering) + { + %curr = %brick.isRendering(); + %fillWrenchRenderingValue = $NU[%client, %this, "R", %i]; + + if(%curr != %fillWrenchRenderingValue) + { + //Copy emitter ...? + if(!%fillWrenchRenderingValue && (%tmp = %brick.emitter | 0)) + %emitter = %tmp.getEmitterDatablock(); + else + %emitter = 0; + + %brick.setRendering(%fillWrenchRenderingValue); + + if(!%fillWrenchRenderingValue && %emitter) + %brick.setEmitter(%emitter); + } + } + } + + clearCurrentQuotaObject(); + + //If undo is taking long, tell the client how far we get + if(%client.ndLastMessageTime + 0.1 < $Sim::Time) + { + %client.ndLastMessageTime = $Sim::Time; + + %percent = mFloor(%end * 100 / %this.brickCount); + commandToClient(%client, 'centerPrint', "\c6Undo in progress...\n\c3" @ %percent @ "%\c6 finished.", 10); + } + + if(%end >= %this.brickcount) + { + %this.delete(); + %client.ndUndoInProgress = false; + + if(%start != 0) + commandToClient(%client, 'centerPrint', "\c6Undo finished.", 2); + + return; + } + + %this.schedule(30, ndTickUndo, %end, %client); +} diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..4babe57 --- /dev/null +++ b/description.txt @@ -0,0 +1,4 @@ +Title: New Duplicator +Author: Zeblote (1163) +New lag-free duplicator with intelligent selection modes +Redo's mod v1: better dupsave listing and search, fill and supercut logic wires, V20 support, possibly some other stuff I forgot about diff --git a/resources/server/black.png b/resources/server/black.png new file mode 100644 index 0000000..6f455b6 Binary files /dev/null and b/resources/server/black.png differ diff --git a/resources/server/blank.png b/resources/server/blank.png new file mode 100644 index 0000000..02156f9 Binary files /dev/null and b/resources/server/blank.png differ diff --git a/resources/server/blue.png b/resources/server/blue.png new file mode 100644 index 0000000..c1ad4dd Binary files /dev/null and b/resources/server/blue.png differ diff --git a/resources/server/brickBOTTOMEDGE.png b/resources/server/brickBOTTOMEDGE.png new file mode 100644 index 0000000..89a75bf Binary files /dev/null and b/resources/server/brickBOTTOMEDGE.png differ diff --git a/resources/server/brickBOTTOMLOOP.png b/resources/server/brickBOTTOMLOOP.png new file mode 100644 index 0000000..c349775 Binary files /dev/null and b/resources/server/brickBOTTOMLOOP.png differ diff --git a/resources/server/brickSIDE.png b/resources/server/brickSIDE.png new file mode 100644 index 0000000..b821ad9 Binary files /dev/null and b/resources/server/brickSIDE.png differ diff --git a/resources/server/brickTOP.png b/resources/server/brickTOP.png new file mode 100644 index 0000000..db2f93d Binary files /dev/null and b/resources/server/brickTOP.png differ diff --git a/resources/server/duplicator_brick.dts b/resources/server/duplicator_brick.dts new file mode 100644 index 0000000..4da67e3 Binary files /dev/null and b/resources/server/duplicator_brick.dts differ diff --git a/resources/server/duplicator_selection.dts b/resources/server/duplicator_selection.dts new file mode 100644 index 0000000..adb589c Binary files /dev/null and b/resources/server/duplicator_selection.dts differ diff --git a/resources/server/icon.png b/resources/server/icon.png new file mode 100644 index 0000000..831d7e0 Binary files /dev/null and b/resources/server/icon.png differ diff --git a/resources/server/selectionbox_border.dts b/resources/server/selectionbox_border.dts new file mode 100644 index 0000000..aca9e6a Binary files /dev/null and b/resources/server/selectionbox_border.dts differ diff --git a/resources/server/selectionbox_inner.dts b/resources/server/selectionbox_inner.dts new file mode 100644 index 0000000..848308c Binary files /dev/null and b/resources/server/selectionbox_inner.dts differ diff --git a/resources/server/selectionbox_outer.dts b/resources/server/selectionbox_outer.dts new file mode 100644 index 0000000..f2dfaa6 Binary files /dev/null and b/resources/server/selectionbox_outer.dts differ diff --git a/resources/server/transparent.png b/resources/server/transparent.png new file mode 100644 index 0000000..02156f9 Binary files /dev/null and b/resources/server/transparent.png differ diff --git a/resources/server/white.png b/resources/server/white.png new file mode 100644 index 0000000..5492fdc Binary files /dev/null and b/resources/server/white.png differ diff --git a/scripts/common/bytetable.cs b/scripts/common/bytetable.cs new file mode 100644 index 0000000..2546445 --- /dev/null +++ b/scripts/common/bytetable.cs @@ -0,0 +1,225 @@ +// Converts integers to characters and back. Mainly used by save transfer. +// ------------------------------------------------------------------- + +//Binary compression (file version, 241 allowed characters) +/////////////////////////////////////////////////////////////////////////// + +//Creates byte lookup table +function ndCreateByte241Table() +{ + $ND::Byte241Lookup = ""; + + //This will map uints 0-241 to chars 15-255, starting after \r + for(%i = 15; %i < 256; %i++) + { + %char = collapseEscape("\\x" @ + getSubStr("0123456789abcdef", (%i & 0xf0) >> 4, 1) @ + getSubStr("0123456789abcdef", %i & 0x0f, 1)); + + $ND::Byte241ToChar[%i - 15] = %char; + $ND::Byte241Lookup = $ND::Byte241Lookup @ %char; + } + + $ND::Byte241TableCreated = true; +} + +//Packs uint in single byte +function ndPack241_1(%num) +{ + return $ND::Byte241ToChar[%num]; +} + +//Packs uint in two bytes +function ndPack241_2(%num) +{ + return $ND::Byte241ToChar[(%num / 241) | 0] @ $ND::Byte241ToChar[%num % 241]; +} + +//Packs uint in three bytes +function ndPack241_3(%num) +{ + return + $ND::Byte241ToChar[(((%num / 241) | 0) / 241) | 0] @ + $ND::Byte241ToChar[((%num / 241) | 0) % 241] @ + $ND::Byte241ToChar[%num % 241]; +} + +//Packs uint in four bytes +function ndPack241_4(%num) +{ + return + $ND::Byte241ToChar[(((((%num / 241) | 0) / 241) | 0) / 241) | 0] @ + $ND::Byte241ToChar[((((%num / 241) | 0) / 241) | 0) % 241] @ + $ND::Byte241ToChar[((%num / 241) | 0) % 241] @ + $ND::Byte241ToChar[%num % 241]; +} + +//Unpacks uint from single byte +function ndUnpack241_1(%subStr) +{ + return strStr($ND::Byte241Lookup, %subStr); +} + +//Unpacks uint from two bytes +function ndUnpack241_2(%subStr) +{ + return + strStr($ND::Byte241Lookup, getSubStr(%subStr, 0, 1)) * 241 + + strStr($ND::Byte241Lookup, getSubStr(%subStr, 1, 1)); +} + +//Unpacks uint from three bytes +function ndUnpack241_3(%subStr) +{ + return + ((strStr($ND::Byte241Lookup, getSubStr(%subStr, 0, 1)) * 58081) | 0) + + strStr($ND::Byte241Lookup, getSubStr(%subStr, 1, 1)) * 241 + + strStr($ND::Byte241Lookup, getSubStr(%subStr, 2, 1)); +} + +//Unpacks uint from four bytes +function ndUnpack241_4(%subStr) +{ + return + ((strStr($ND::Byte241Lookup, getSubStr(%subStr, 0, 1)) * 13997521) | 0) + + ((strStr($ND::Byte241Lookup, getSubStr(%subStr, 1, 1)) * 58081) | 0) + + strStr($ND::Byte241Lookup, getSubStr(%subStr, 2, 1)) * 241 + + strStr($ND::Byte241Lookup, getSubStr(%subStr, 3, 1)); +} + + + +//Binary compression (command version, 255 allowed characters) +/////////////////////////////////////////////////////////////////////////// + +//Creates byte lookup table +function ndCreateByte255Table() +{ + $ND::Byte255Lookup = ""; + + //This will map uints 0-254 to chars 1-255, starting after \x00 + for(%i = 1; %i < 256; %i++) + { + %char = collapseEscape("\\x" @ + getSubStr("0123456789abcdef", (%i & 0xf0) >> 4, 1) @ + getSubStr("0123456789abcdef", %i & 0x0f, 1)); + + $ND::Byte255ToChar[%i - 1] = %char; + $ND::Byte255Lookup = $ND::Byte255Lookup @ %char; + } + + $ND::Byte255TableCreated = true; +} + +//Packs uint in single byte +function ndPack255_1(%num) +{ + return $ND::Byte255ToChar[%num]; +} + +//Packs uint in two bytes +function ndPack255_2(%num) +{ + return $ND::Byte255ToChar[(%num / 255) | 0] @ $ND::Byte255ToChar[%num % 255]; +} + +//Packs uint in three bytes +function ndPack255_3(%num) +{ + return + $ND::Byte255ToChar[(((%num / 255) | 0) / 255) | 0] @ + $ND::Byte255ToChar[((%num / 255) | 0) % 255] @ + $ND::Byte255ToChar[%num % 255]; +} + +//Packs uint in four bytes +function ndPack255_4(%num) +{ + return + $ND::Byte255ToChar[(((((%num / 255) | 0) / 255) | 0) / 255) | 0] @ + $ND::Byte255ToChar[((((%num / 255) | 0) / 255) | 0) % 255] @ + $ND::Byte255ToChar[((%num / 255) | 0) % 255] @ + $ND::Byte255ToChar[%num % 255]; +} + +//Unpacks uint from single byte +function ndUnpack255_1(%subStr) +{ + return strStr($ND::Byte255Lookup, %subStr); +} + +//Unpacks uint from two bytes +function ndUnpack255_2(%subStr) +{ + return + strStr($ND::Byte255Lookup, getSubStr(%subStr, 0, 1)) * 255 + + strStr($ND::Byte255Lookup, getSubStr(%subStr, 1, 1)); +} + +//Unpacks uint from three bytes +function ndUnpack255_3(%subStr) +{ + return + ((strStr($ND::Byte255Lookup, getSubStr(%subStr, 0, 1)) * 65025) | 0) + + strStr($ND::Byte255Lookup, getSubStr(%subStr, 1, 1)) * 255 + + strStr($ND::Byte255Lookup, getSubStr(%subStr, 2, 1)) | 0; +} + +//Unpacks uint from four bytes +function ndUnpack255_4(%subStr) +{ + return + ((strStr($ND::Byte255Lookup, getSubStr(%subStr, 0, 1)) * 16581375) | 0) + + ((strStr($ND::Byte255Lookup, getSubStr(%subStr, 1, 1)) * 65025) | 0) + + strStr($ND::Byte255Lookup, getSubStr(%subStr, 2, 1)) * 255 + + strStr($ND::Byte255Lookup, getSubStr(%subStr, 3, 1)) | 0; +} + +//Some tests for the packing functions +function ndTestPack255() +{ + echo("Testing 1 byte"); + echo(ndUnpack255_1(ndPack255_1(0)) == 0); + echo(ndUnpack255_1(ndPack255_1(123)) == 123); + echo(ndUnpack255_1(ndPack255_1(231)) == 231); + echo(ndUnpack255_1(ndPack255_1(254)) == 254); + + echo("Testing 2 byte"); + echo(ndUnpack255_2(ndPack255_2(0)) == 0); + echo(ndUnpack255_2(ndPack255_2(123)) == 123); + echo(ndUnpack255_2(ndPack255_2(231)) == 231); + echo(ndUnpack255_2(ndPack255_2(254)) == 254); + echo(ndUnpack255_2(ndPack255_2(12345)) == 12345); + echo(ndUnpack255_2(ndPack255_2(32145)) == 32145); + echo(ndUnpack255_2(ndPack255_2(65024)) == 65024); + + echo("Testing 3 byte"); + echo(ndUnpack255_3(ndPack255_3(0)) == 0); + echo(ndUnpack255_3(ndPack255_3(123)) == 123); + echo(ndUnpack255_3(ndPack255_3(231)) == 231); + echo(ndUnpack255_3(ndPack255_3(254)) == 254); + echo(ndUnpack255_3(ndPack255_3(12345)) == 12345); + echo(ndUnpack255_3(ndPack255_3(32145)) == 32145); + echo(ndUnpack255_3(ndPack255_3(65024)) == 65024); + echo(ndUnpack255_3(ndPack255_3(11234567)) == 11234567); + echo(ndUnpack255_3(ndPack255_3(14132451)) == 14132451); + echo(ndUnpack255_3(ndPack255_3(16581374)) == 16581374); + + echo("Testing 4 byte"); + echo(ndUnpack255_4(ndPack255_4(0)) == 0); + echo(ndUnpack255_4(ndPack255_4(123)) == 123); + echo(ndUnpack255_4(ndPack255_4(231)) == 231); + echo(ndUnpack255_4(ndPack255_4(254)) == 254); + echo(ndUnpack255_4(ndPack255_4(12345)) == 12345); + echo(ndUnpack255_4(ndPack255_4(32145)) == 32145); + echo(ndUnpack255_4(ndPack255_4(65024)) == 65024); + echo(ndUnpack255_4(ndPack255_4(11234567)) == 11234567); + echo(ndUnpack255_4(ndPack255_4(14132451)) == 14132451); + echo(ndUnpack255_4(ndPack255_4(16581374)) == 16581374); + echo(ndUnpack255_4(ndPack255_4(1234567890)) == 1234567890); + + //Appearantly tork uses uint and normal int randomly in + //seperate places so we can't use the full uint range + echo(ndUnpack255_4(ndPack255_4(2147483647)) == 2147483647); + echo(ndUnpack255_4(ndPack255_4(2147483648)) != 2147483648); +} diff --git a/scripts/server/commands.cs b/scripts/server/commands.cs new file mode 100644 index 0000000..5837d0b --- /dev/null +++ b/scripts/server/commands.cs @@ -0,0 +1,1151 @@ +// General server commands used to control the new duplicator. +// ------------------------------------------------------------------- + +//Information commands +/////////////////////////////////////////////////////////////////////////// + +//Shows version of blockland and the new duplicator +function serverCmdDupVersion(%client) +{ + messageClient(%client, '', "\c6Blockland version: \c3r" @ getBuildNumber()); + messageClient(%client, '', "\c6New duplicator version: \c3" @ $ND::Version); + + if(%client.ndClient) + messageClient(%client, '', "\c6Your new duplicator version: \c3" @ %client.ndVersion); + else + messageClient(%client, '', "\c6You don't have the new duplicator installed"); +} + +//Shows versions of other clients +function serverCmdDupClients(%client) +{ + messageClient(%client, '', "\c6New duplicator versions:"); + + %cnt = ClientGroup.getCount(); + for(%i = 0; %i < %cnt; %i++) + { + %cl = ClientGroup.getObject(%i); + + if(%cl.ndClient) + messageClient(%client, '', "\c3" @ %cl.name @ "\c6 has \c3" @ %cl.ndVersion); + } +} + +//Shows list of commands +function serverCmdDupHelp(%client) +{ + messageClient(%client, '', " "); + messageClient(%client, '', "\c6You can use the following commands with your new duplicator:"); + messageClient(%client, '', "\c7--------------------------------------------------------------------------------"); + + messageClient(%client, '', "\c3/Duplicator\t\c6 Equip a new duplicator!"); + messageClient(%client, '', " "); + + messageClient(%client, '', "\c3/ForcePlant\t\c6 Plant a selection in mid air; bricks can float."); + messageClient(%client, '', "\c3/ToggleForcePlant\t\c6 Enable force plant for normal planting, so you dont have to type it all the time."); + messageClient(%client, '', "\c3/PlantAs\c6 [\c3target\c6]\t\c6 Plant bricks in a different brick group. Target can be a name or blid."); + messageClient(%client, '', " "); + + messageClient(%client, '', "\c3/FillWrench\t\c6 Open the fill wrench gui to change settings on all selected bricks."); + messageClient(%client, '', " "); + + messageClient(%client, '', "\c3/MirrorX\t\c6 Mirror your ghost selection left/right on screen."); + messageClient(%client, '', "\c3/MirrorY\t\c6 Mirror your ghost selection front/back on screen."); + messageClient(%client, '', "\c3/MirrorZ\t\c6 Mirror your ghost selection up/down on screen."); + messageClient(%client, '', "\c3/MirErrors\t\c6 List potential mirror errors after planting a mirrored ghost selection."); + messageClient(%client, '', " "); + + messageClient(%client, '', "\c3/SuperCut\t\c6 Delete everything in your selection box, cutting bricks in half on its sides!"); + messageClient(%client, '', "\c3/FillBricks\t\c6 First supercut, then completely fill your selection box with few bricks."); + messageClient(%client, '', " "); + + messageClient(%client, '', "\c3/SaveDup\c6 [\c3name\c6]\t\c6 Save your current selection to a file."); + messageClient(%client, '', "\c3/LoadDup\c6 [\c3name\c6]\t\c6 Load a selection from a file. Your current selection will be deleted."); + messageClient(%client, '', "\c3/AllDups\c6 [\c3filter\c6]\t\c6 Show all known saved duplications that match the filter. Leave blank to show all."); + messageClient(%client, '', " "); + + messageClient(%client, '', "\c3/DupVersion\t\c6 Show the duplicator and blockland versions running on the server."); + messageClient(%client, '', "\c3/DupClients\t\c6 Show the duplicator versions of other clients on the server."); + + messageClient(%client, '', "\c7--------------------------------------------------------------------------------"); + messageClient(%client, '', "\c6All of the commands can be shortened by just typing a \c3/\c6 and the capital letters!"); + messageClient(%client, '', "\c6You might have to use \c3PageUp\c6/\c3PageDown\c6 to see all of them."); + messageClient(%client, '', " "); +} + +//Alternative short commands +function serverCmdDV(%client){serverCmdDupVersion(%client);} +function serverCmdDC(%client){serverCmdDupClients(%client);} +function serverCmdDH(%client){serverCmdDupHelp(%client);} + + + +//Equip commands +/////////////////////////////////////////////////////////////////////////// + +//Command to equip the new duplicator +function serverCmdNewDuplicator(%client) +{ + //Check admin + if($Pref::Server::ND::AdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6The new duplicator is admin only. Ask an admin for help."); + return; + } + + //Check minigame + if(isObject(%client.minigame) && !%client.minigame.enablebuilding) + { + messageClient(%client, '', "\c6You cannot use the new duplicator while building is disabled in your minigame."); + return; + } + + //Check player + if(!isObject(%player = %client.player)) + { + messageClient(%client, '', "\c6You must be spawned to equip the new duplicator."); + return; + } + + //Hide brick selector and tool gui + %client.ndLastEquipTime = $Sim::Time; + commandToClient(%client, 'setScrollMode', 3); + + //Give player a duplicator + %image = %client.ndImage; + + if(!isObject(%image)) + %image = ND_Image; + + %player.updateArm(%image); + %player.mountImage(%image, 0); + %client.ndEquippedFromItem = false; +} + +//Alternative commands to equip the new duplicator (override old duplicators) +package NewDuplicator_Server_Final +{ + function serverCmdDuplicator(%client){serverCmdNewDuplicator(%client);} + function serverCmdDuplicato (%client){serverCmdNewDuplicator(%client);} + function serverCmdDuplicat (%client){serverCmdNewDuplicator(%client);} + function serverCmdDuplica (%client){serverCmdNewDuplicator(%client);} + function serverCmdDuplic (%client){serverCmdNewDuplicator(%client);} + function serverCmdDupli (%client){serverCmdNewDuplicator(%client);} + function serverCmdDupl (%client){serverCmdNewDuplicator(%client);} + function serverCmdDup (%client){serverCmdNewDuplicator(%client);} + function serverCmdDu (%client){serverCmdNewDuplicator(%client);} + function serverCmdD (%client){serverCmdNewDuplicator(%client);} +}; + + + +//Default keybind commands +/////////////////////////////////////////////////////////////////////////// +package NewDuplicator_Server +{ + //Light key (default: R) + function serverCmdLight(%client) + { + if(%client.ndModeIndex) + %client.ndMode.onLight(%client); + else + parent::serverCmdLight(%client); + } + + //Next seat (default: .) + function serverCmdNextSeat(%client) + { + if(%client.ndModeIndex) + %client.ndMode.onNextSeat(%client); + else + parent::serverCmdNextSeat(%client); + } + + //Previous seat (default: ,) + function serverCmdPrevSeat(%client) + { + if(%client.ndModeIndex) + %client.ndMode.onPrevSeat(%client); + else + parent::serverCmdPrevSeat(%client); + } + + //Shifting the ghost brick (default: numpad 2468/13/5+) + function serverCmdShiftBrick(%client, %x, %y, %z) + { + if(%client.ndModeIndex) + %client.ndMode.onShiftBrick(%client, %x, %y, %z); + + //Call parent to play animation + parent::serverCmdShiftBrick(%client, %x, %y, %z); + } + + //Super-shifting the ghost brick (default: alt numpad 2468/5+) + function serverCmdSuperShiftBrick(%client, %x, %y, %z) + { + if(%client.ndModeIndex) + %client.ndMode.onSuperShiftBrick(%client, %x, %y, %z); + + //Call parent to play animation + parent::serverCmdSuperShiftBrick(%client, %x, %y, %z); + } + + //Rotating the ghost brick (default: numpad 79) + function serverCmdRotateBrick(%client, %direction) + { + if(%client.ndModeIndex) + %client.ndMode.onRotateBrick(%client, %direction); + + //Call parent to play animation + parent::serverCmdRotateBrick(%client, %direction); + } + + //Undo bricks (default: ctrl z) + function serverCmdUndoBrick(%client) + { + if(%client.ndUndoInProgress) + { + messageClient(%client, '', "\c6Please wait for the current undo task to finish."); + return; + } + + //This really needs a better api. + //Wtf were you thinking, badspot? + %state = %client.undoStack.pop(); + %type = getField(%state, 1); + + if( + %type $= "ND_PLANT" + || %type $= "ND_PAINT" + || %type $= "ND_WRENCH" + ){ + %obj = getField(%state, 0); + if(isObject(%obj)){ + + if(%obj.brickCount > 10 && %client.ndUndoConfirm != %obj) + { + messageClient(%client, '', "\c6Next undo will affect \c3" @ %obj.brickCount @ "\c6 bricks. Press undo again to continue."); + %client.undoStack.push(%state); + %client.ndUndoConfirm = %obj; + return; + } + + %obj.ndStartUndo(%client); + + if(isObject(%client.player)) + %client.player.playThread(3, "undo"); + + %client.ndUndoConfirm = 0; + }else{ + talk("serverCmdUndoBrick(" @ %client.name @ ") - Nonexistent undo state " @ %state); + } + return; + } + + %client.ndUndoConfirm = 0; + %client.undoStack.push(%state); + parent::serverCmdUndoBrick(%client); + } +}; + +package NewDuplicator_Server_Final +{ + //Planting the ghost brick (default: numpad enter) + function serverCmdPlantBrick(%client) + { + if(%client.ndModeIndex) + %client.ndMode.onPlantBrick(%client); + else + parent::serverCmdPlantBrick(%client); + } + + //Removing the ghost brick (default: numpad 0) + function serverCmdCancelBrick(%client) + { + if(%client.ndModeIndex) + %client.ndMode.onCancelBrick(%client); + else + parent::serverCmdCancelBrick(%client); + } +}; + + + +//Custom keybind commands +/////////////////////////////////////////////////////////////////////////// + +//Copy selection (ctrl c) +function serverCmdNdCopy(%client) +{ + if(%client.ndModeIndex) + %client.ndMode.onCopy(%client); +} + +//Paste selection (ctrl v) +function serverCmdNdPaste(%client) +{ + if(%client.ndModeIndex) + %client.ndMode.onPaste(%client); +} + +//Cut selection (ctrl x) +function serverCmdNdCut(%client) +{ + if(%client.ndModeIndex) + %client.ndMode.onCut(%client); +} + +//Cut selection +function serverCmdCut(%client) +{ + serverCmdNdCut(%client); +} + +//Supercut selection +function serverCmdSuperCut(%client) +{ + if(%client.ndModeIndex != $NDM::BoxSelect) + { + messageClient(%client, '', "\c6Supercut can only be used on box selection mode."); + return; + } + + if(!isObject(%client.ndSelectionBox)) + { + messageClient(%client, '', "\c6Supercut can only be used with a selection box."); + return; + } + + if(%client.ndSelectionAvailable) + { + messageClient(%client, '', "\c6Supercut can not be used with any bricks selected."); + return; + } + + commandToClient(%client, 'messageBoxOkCancel', "New Duplicator | Supercut", + "Supercut is destructive and does\nNOT support undo at this time." @ + "\n\nPlease make sure the box is correct,\nthen press OK below.", + 'ndConfirmSuperCut'); +} + +//Confirm Supercut selection +function serverCmdNdConfirmSuperCut(%client) +{ + if(%client.ndModeIndex != $NDM::BoxSelect) + { + messageClient(%client, '', "\c6Supercut can only be used on box selection mode."); + return; + } + + if(!isObject(%client.ndSelectionBox)) + { + messageClient(%client, '', "\c6Supercut can only be used with a selection box."); + return; + } + + if(%client.ndSelectionAvailable) + { + messageClient(%client, '', "\c6Supercut can not be used with any bricks selected."); + return; + } + + %client.NDFillBrickSubset = $ND::SubsetDefault; + + %client.fillBricksAfterSuperCut = false; + %client.ndMode.onSuperCut(%client); +} + +//Alternative short command +function serverCmdSC(%client){serverCmdSuperCut(%client);} + +//Fill volume with bricks +function serverCmdFillBricks(%client, %subsetname) +{ + if($Pref::Server::ND::FillBricksAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Fill Bricks is admin only. Ask an admin for help."); + return; + } + + if(!isObject(%client.ndSelectionBox)) + { + messageClient(%client, '', "\c6The fillBricks command can only be used with a selection box."); + return; + } + + if(%client.ndSelectionAvailable) + { + messageClient(%client, '', "\c6The fillBricks command can not be used with any bricks selected."); + return; + } + + if(!%client.ndSelectionBox.hasVolume()) + { + messageClient(%client, '', "\c6The fillBricks command can only be used with a selection box that has a volume."); + return; + } + + %client.NDFillBrickSubset = (%subsetname !$= "") ? ndLookupSubsetName(%subsetname) : $ND::SubsetDefault; + + commandToClient(%client, 'messageBoxOkCancel', "New Duplicator | /FillBricks", + "/FillBricks will first do a Supercut\nbefore placing bricks, to fix overlap." @ + "\n\nSupercut is destructive and does\nNOT support undo at this time." @ + "\n\nPlease make sure the box is correct,\nthen press OK below to continue.", + 'ndConfirmFillBricks'); +} + +//Confirm fill volume with bricks +function serverCmdNdConfirmFillBricks(%client, %subsetname) +{ + if($Pref::Server::ND::FillBricksAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Fill Bricks is admin only. Ask an admin for help."); + return; + } + + if(!isObject(%client.ndSelectionBox)) + { + messageClient(%client, '', "\c6The fillBricks command can only be used with a selection box."); + return; + } + + if(%client.ndSelectionAvailable) + { + messageClient(%client, '', "\c6The fillBricks command can not be used with any bricks selected."); + return; + } + + if(!%client.ndSelectionBox.hasVolume()) + { + messageClient(%client, '', "\c6The fillBricks command can only be used with a selection box that has a volume."); + return; + } + + %client.NDFillBrickSubset = (%subsetname !$= "") ? + ndLookupSubsetName(%subsetname) : + ((%client.NDFillBrickSubset !$= "") ? + %client.NDFillBrickSubset : + $ND::SubsetDefault + ) + ; + + %client.fillBricksAfterSuperCut = true; + %client.ndMode.onSuperCut(%client); +} + +//Alternative short command +function serverCmdFB(%client){serverCmdFillBricks(%client);} +function serverCmdFBW(%client) { serverCmdFillBricks(%client, "LogicWire"); } + + +//MultiSelect toggle (ctrl) +function serverCmdNdMultiSelect(%client, %bool) +{ + %client.ndMultiSelect = !!%bool; + + if(%client.ndModeIndex == $NDM::StackSelect || %client.ndModeIndex == $NDM::BoxSelect) + %client.ndUpdateBottomPrint(); +} + + + +//Mirror commands +/////////////////////////////////////////////////////////////////////////// + +//Mirror selection on X relative to player +function serverCmdMirrorX(%client) +{ + if((getAngleIDFromPlayer(%client.getControlObject()) - %client.ndSelection.ghostAngleID) % 2 == 1) + %client.ndMirror(0); + else + %client.ndMirror(1); +} + +//Mirror selection on Y relative to player +function serverCmdMirrorY(%client) +{ + if((getAngleIDFromPlayer(%client.getControlObject()) - %client.ndSelection.ghostAngleID) % 2 == 1) + %client.ndMirror(1); + else + %client.ndMirror(0); +} + +//Mirror selection on Z +function serverCmdMirrorZ(%client) +{ + %client.ndMirror(2); +} + +//Alternative short commands +function serverCmdMX(%client){serverCmdMirrorX(%client);} +function serverCmdMY(%client){serverCmdMirrorY(%client);} +function serverCmdMZ(%client){serverCmdMirrorZ(%client);} + +//Attempt to mirror selection on axis +function GameConnection::ndMirror(%client, %axis) +{ + //Make sure symmetry table is created + if(!$ND::SymmetryTableCreated) + { + if(!$ND::SymmetryTableCreating) + ndCreateSymmetryTable(); + + messageClient(%client, '', "\c6Please wait for the symmetry table to finish, then mirror again."); + return; + } + + //If we're in plant mode, mirror the selection + if(isObject(%client.ndSelection) && %client.ndModeIndex == $NDM::PlantCopy) + { + %client.ndSelection.mirrorGhostBricks(%axis); + return; + } + + //If we have a ghost brick, mirror that instead + if(isObject(%client.player) && isObject(%client.player.tempBrick)) + { + %client.player.tempBrick.ndMirrorGhost(%client, %axis); + return; + } + + //We didn't mirror anything + messageClient(%client, '', "\c6The mirror command can only be used in plant mode or with a ghost brick."); +} + +//List potential mirror errors in last plant +function serverCmdMirErrors(%client) +{ + %xerr = $NS[%client, "MXC"]; + %zerr = $NS[%client, "MZC"]; + + if(%xerr) + { + messageClient(%client, '', " "); + messageClient(%client, '', "\c6These bricks are asymmetric and probably mirrored incorrectly:"); + + for(%i = 0; %i < %xerr; %i++) + { + %db = $NS[%client, "MXE", %i]; + messageClient(%client, '', "\c7 -" @ %i + 1 @ "- \c6" @ %db.category @ "/" @ %db.subCategory @ "/" @ %db.uiName); + } + } + + if(%zerr) + { + messageClient(%client, '', " "); + messageClient(%client, '', "\c6These bricks are not vertically symmetric and probably incorrect:"); + + for(%i = 0; %i < %zerr; %i++) + { + %db = $NS[%client, "MZE", %i]; + messageClient(%client, '', "\c7 -" @ %i + 1 @ "- \c6" @ %db.category @ "/" @ %db.subCategory @ "/" @ %db.uiName); + } + } + + if(!%xerr && !%zerr) + messageClient(%client, '', "\c6There were no mirror errors in your last plant attempt."); +} + +//Alternative short command +function serverCmdME(%client){serverCmdMirErrors(%client);} + + + +//Force plant +/////////////////////////////////////////////////////////////////////////// + +//Force plant one time +function serverCmdForcePlant(%client) +{ + //Check mode + if(%client.ndModeIndex != $NDM::PlantCopy) + { + messageClient(%client, '', "\c6Force Plant can only be used in Plant Mode."); + return; + } + + //Check admin + if($Pref::Server::ND::FloatAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Force Plant is admin only. Ask an admin for help."); + return; + } + + NDM_PlantCopy.conditionalPlant(%client, true); +} + +//Alternative short command +function serverCmdFP(%client){serverCmdForcePlant(%client);} + +//Keep force plant enabled +function serverCmdToggleForcePlant(%client) +{ + //Check admin + if($Pref::Server::ND::FloatAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Force Plant is admin only. Ask an admin for help."); + return; + } + + %client.ndForcePlant = !%client.ndForcePlant; + + if(%client.ndForcePlant) + messageClient(%client, '', "\c6Force Plant has been enabled. Use \c3/toggleForcePlant\c6 to disable it."); + else + messageClient(%client, '', "\c6Force Plant has been disabled. Use \c3/toggleForcePlant\c6 to enable it again."); +} + +//Alternative short command +function serverCmdTFP(%client){serverCmdToggleForcePlant(%client);} + + + +//Fill color +/////////////////////////////////////////////////////////////////////////// + +package NewDuplicator_Server +{ + //Enable fill color mode or show the current color + function serverCmdUseSprayCan(%client, %index) + { + %mode = %client.ndModeIndex; + + if(%mode == $NDM::StackSelect || %mode == $NDM::BoxSelect) + { + if(isObject(%client.ndSelection) && %client.ndSelection.brickCount) + { + %client.currentColor = %index; + %client.currentFxColor = ""; + %client.ndSetMode(NDM_FillColor); + return; + } + } + else if(%mode == $NDM::FillColor || %client.ndModeIndex == $NDM::FillColorProgress) + { + %client.currentColor = %index; + %client.currentFxColor = ""; + %client.ndUpdateBottomPrint(); + return; + } + + cancel(%client.ndToolSchedule); + parent::serverCmdUseSprayCan(%client, %index); + } + + //Enable fill color mode or show the current color + function serverCmdUseFxCan(%client, %index) + { + %mode = %client.ndModeIndex; + + if(%mode == $NDM::StackSelect || %mode == $NDM::BoxSelect) + { + if(isObject(%client.ndSelection) && %client.ndSelection.brickCount) + { + %client.currentFxColor = %index; + %client.ndSetMode(NDM_FillColor); + } + else + parent::serverCmdUseFxCan(%client, %index); + } + else if(%mode == $NDM::FillColor || %client.ndModeIndex == $NDM::FillColorProgress) + { + %client.currentFxColor = %index; + %client.ndUpdateBottomPrint(); + } + else + parent::serverCmdUseFxCan(%client, %index); + } +}; + + + +//Fill wrench +/////////////////////////////////////////////////////////////////////////// + +//Open the fill wrench gui +function serverCmdFillWrench(%client) +{ + //Check version + if(!%client.ndClient) + { + messageClient(%client, '', "\c6You need to have the new duplicator installed to use Fill Wrench."); + return; + } + + if(ndCompareVersion("1.2.0", %client.ndVersion) == 1) + { + messageClient(%client, '', "\c6Your version of the new duplicator is too old to use Fill Wrench."); + return; + } + + //Check admin + if($Pref::Server::ND::WrenchAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Fill Wrench is admin only. Ask an admin for help."); + return; + } + + //Check mode + if(%client.ndModeIndex != $NDM::StackSelect && %client.ndModeIndex != $NDM::BoxSelect) + { + messageClient(%client, '', "\c6Fill Wrench can only be used in Selection Mode."); + return; + } + + //Check selection + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + { + messageClient(%client, '', "\c6Fill Wrench can only be used with a selection."); + return; + } + + //Open fill wrench gui + commandToClient(%client, 'ndOpenWrenchGui'); +} + +//Short command +function serverCmdFW(%client) {serverCmdFillWrench(%client);} + +//Send data from gui +function serverCmdNdStartFillWrench(%client, %data) +{ + //Check admin + if($Pref::Server::ND::WrenchAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Fill Wrench is admin only. Ask an admin for help."); + return; + } + + //Check mode + if(%client.ndModeIndex != $NDM::StackSelect && %client.ndModeIndex != $NDM::BoxSelect) + { + messageClient(%client, '', "\c6Fill Wrench can only be used in Selection Mode."); + return; + } + + //Check selection + if(!isObject(%client.ndSelection) || !%client.ndSelection.brickCount) + { + messageClient(%client, '', "\c6Fill Wrench can only be used with a selection."); + return; + } + + //Change mode + %client.ndSetMode(NDM_WrenchProgress); + %client.ndSelection.startFillWrench(%data); +} + + + +//Saving and loading +/////////////////////////////////////////////////////////////////////////// + +package NewDuplicator_Server_Final +{ + //Save current selection to file + function serverCmdSaveDup(%client, %f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7) + { + //Check timeout + if(!%client.isAdmin && %client.ndLastSaveTime + 10 > $Sim::Time) + { + %remain = mCeil(%client.ndLastSaveTime + 10 - $Sim::Time); + + if(%remain != 1) + %s = "s"; + + messageClient(%client, '', "\c6Please wait\c3 " @ %remain @ "\c6 second" @ %s @ " before saving again!"); + return; + } + + //Check admin + if($Pref::Server::ND::SaveAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Saving duplications is admin only. Ask an admin for help."); + return; + } + + //Check mode + if(%client.ndModeIndex != $NDM::PlantCopy) + { + messageClient(%client, '', "\c6Saving duplications can only be used in Plant Mode."); + return; + } + + //Filter file name + %allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ._-()"; + %fileName = trim(%f0 SPC %f1 SPC %f2 SPC %f3 SPC %f4 SPC %f5 SPC %f6 SPC %f7); + %filePath = $ND::ConfigPath @ "Saves/" @ %fileName @ ".bls"; + %filePath = strReplace(%filePath, ".bls.bls", ".bls"); + + for(%i = 0; %i < strLen(%fileName); %i++) + { + if(strStr(%allowed, getSubStr(%fileName, %i, 1)) == -1) + { + %forbidden = true; + break; + } + } + + if(%forbidden || !strLen(%fileName) || strLen(%fileName) > 50) + { + messageClient(%client, '', "\c6Bad save name \"\c3" @ %fileName @ "\c6\", please try again."); + messageClient(%client, '', "\c6Only \c3a-z A-Z 0-9 ._-()\c6 and \c3space\c6 are allowed, with a max length of 50 characters."); + return; + } + + //Check overwrite + if(isFile(%filePath) && %client.ndPotentialOverwrite !$= %fileName) + { + messageClient(%client, '', "\c6Save \"\c3" @ %fileName @ "\c6\" already exists. Repeat the command to overwrite."); + %client.ndPotentialOverwrite = %fileName; + return; + } + + %client.ndPotentialOverwrite = ""; + + //Check writeable + if(!isWriteableFileName(%filePath)) + { + messageClient(%client, '', "\c6File \"\c3" @ %fileName @ "\c6\" is not writeable. Ask the host for help."); + return; + } + + messageClient(%client, '', "\c6Saving selection to \"\c3" @ %fileName @ "\c6\"..."); + + //Notify admins + if(!%client.isAdmin) + { + for(%i = 0; %i < ClientGroup.getCount(); %i++) + { + %cl = ClientGroup.getObject(%i); + + if(%cl.isAdmin && %cl != %client) + messageClient(%cl, '', "\c3" @ %client.name @ "\c6 is saving duplication \"\c3" @ %fileName @ "\c6\""); + } + } + + //Write log + echo("ND: " @ %client.name @ " (" @ %client.bl_id @ ") is saving duplication \"" @ %fileName @ "\""); + + // Uncache saved file info + $ND::FileDate[%filePath] = ""; + + //Change mode + %client.ndSetMode(NDM_SaveProgress); + + if(!%client.ndSelection.startSaving(%filePath)) + { + messageClient(%client, '', "\c6Failed to write save \"\c3" @ %fileName @ "\c6\". Ask the host for help."); + %client.ndSetMode(NDM_PlantCopy); + } + } + + //Load selection from file + function serverCmdLoadDup(%client, %f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7) + { + //Check timeout + if(!%client.isAdmin && %client.ndLastLoadTime + 5 > $Sim::Time) + { + %remain = mCeil(%client.ndLastLoadTime + 5 - $Sim::Time); + + if(%remain != 1) + %s = "s"; + + messageClient(%client, '', "\c6Please wait\c3 " @ %remain @ "\c6 second" @ %s @ " before loading again!"); + return; + } + + //Check admin + if($Pref::Server::ND::LoadAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Loading duplications is admin only. Ask an admin for help."); + return; + } + + //Attempt to get a duplicator + if(!%client.ndEquipped) + { + serverCmdNewDuplicator(%client); + + if(!%client.ndEquipped) + return; + } + + //Check mode + %mode = %client.ndModeIndex; + + if(%mode != $NDM::StackSelect && %mode != $NDM::BoxSelect && %mode != $NDM::PlantCopy) + { + messageClient(%client, '', "\c6Loading duplications can only be used in Plant or Selection Mode."); + return; + } + + //Filter file name + %allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ._-()"; + %fileName = trim(%f0 SPC %f1 SPC %f2 SPC %f3 SPC %f4 SPC %f5 SPC %f6 SPC %f7); + %filePath = $ND::ConfigPath @ "Saves/" @ %fileName @ ".bls"; + %filePath = strReplace(%filePath, ".bls.bls", ".bls"); + + for(%i = 0; %i < strLen(%fileName); %i++) + { + if(strStr(%allowed, getSubStr(%fileName, %i, 1)) == -1) + { + %forbidden = true; + break; + } + } + + if(%forbidden || !strLen(%fileName) || strLen(%fileName) > 50) + { + messageClient(%client, '', "\c6Bad save name \"\c3" @ %fileName @ "\c6\", please try again."); + messageClient(%client, '', "\c6Only \c3a-z A-Z 0-9 ._-()\c6 and \c3space\c6 are allowed, with a max length of 50 characters."); + return; + } + + //Check if file exists + if(!isFile(%filePath)) + { + messageClient(%client, '', "\c6Save \"\c3" @ %fileName @ "\c6\" does not exist, please try again."); + return; + } + + messageClient(%client, '', "\c6Loading selection from \"\c3" @ %fileName @ "\c6\"..."); + + //Notify admins + if(!%client.isAdmin) + { + for(%i = 0; %i < ClientGroup.getCount(); %i++) + { + %cl = ClientGroup.getObject(%i); + + if(%cl.isAdmin && %cl != %client) + messageClient(%cl, '', "\c3" @ %client.name @ "\c6 is loading duplication \"\c3" @ %fileName @ "\c6\""); + } + } + + //Write log + echo("ND: " @ %client.name @ " (" @ %client.bl_id @ ") is loading duplication \"" @ %fileName @ "\""); + + //Change mode + %client.ndSetMode(NDM_LoadProgress); + + if(!%client.ndSelection.startLoading(%filePath)) + { + messageClient(%client, '', "\c6Failed to read save \"\c3" @ %fileName @ "\c6\". Ask the host for help."); + %client.ndSetMode(%client.ndLastSelectMode); + } + } +}; + +function ND_SaveFileInfo(%filename) { + // Get date from file if not cached + if($ND::FileDate[%filename] $= "") { + %file = new FileObject(); + %file.openForRead(%filename); + %file.readLine(); + %file.readLine(); + %dateline = %file.readLine(); + %file.close(); + %file.delete(); + $ND::FileDate[%filename] = %dateline; + } + + // Construct table line + %info = $ND::FileDate[%filename]; + %info_aftername = getSubStr(%info, strStr(%info, " (")+2, strLen(%info)); + %date = getSubStr(%info_aftername, strLen(%info_aftername)-17, 17); + %blid = getSubStr(%info_aftername, 0, strStr(%info_aftername, ")")); + %name = getSubStr(%info, 9, strStr(%info, " (") - 9); + + // Fix date format for sorting + %sort = strReplace(strReplace(%date, "/", " "), ":", " "); + %sort = getWord(%sort, 2) SPC getWord(%sort, 0) SPC getWord(%sort, 1) SPC getWords(%sort, 3, 5) @ " "; + + %namepart = fileBase(%filename); + + %filetext = %sort @ + "\c3" @ %namepart TAB + "\c3" @ %name TAB + "\c6" @ %blid TAB + "\c6" @ %date + ; + + return %filetext; +} + +//Get list of all available dups +function serverCmdAllDups(%client, %pattern) +{ + //Check admin + if($Pref::Server::ND::LoadAdminOnly && !%client.isAdmin) + { + messageClient(%client, '', "\c6Loading duplications is admin only. Ask an admin for help."); + return; + } + + if(strLen(%pattern)) + { + //Filter pattern + %allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-()"; + %pattern = trim(%pattern); + + for(%i = 0; %i < strLen(%pattern); %i++) + { + if(strStr(%allowed, getSubStr(%pattern, %i, 1)) == -1) + { + messageClient(%client, '', "\c6Bad pattern \"\c3" @ %pattern @ "\c6\", please try again."); + messageClient(%client, '', "\c6Only \c3a-z A-Z 0-9 ._-()\c6 are allowed."); + return; + } + } + + %p = $ND::ConfigPath @ "Saves/*" @ %pattern @ "*.bls"; + } else { + %p = $ND::ConfigPath @ "Saves/*.bls"; + } + + //Get sorted list of files + %sort = new GuiTextListCtrl(); + + for(%filename = findFirstFile(%p); isFile(%filename); %filename = findNextFile(%p)) { + %info = ND_SaveFileInfo(%filename); + %sort.addRow(0, %info); + } + + %fileCount = %sort.rowCount(); + %sort.sort(0, true); + + //Dump list to client + if(%fileCount) + { + %s = (%fileCount == 1) ? " is" : "s are"; + + if(strLen(%pattern)) + messageClient(%client, '', "\c3" @ %fileCount @ "\c6 saved duplication" @ %s @ " available for filter \"\c3" @ %pattern @ "\c6\":"); + else + messageClient(%client, '', "\c3" @ %fileCount @ "\c6 saved duplication" @ %s @ " available:"); + + %format = ""; + if(%fileCount>0) { + messageClient(%client, '', %format @ "\c6Name\t\c6Saved By\t\c6BL_ID\t\c6Date"); + } + for(%i = 0; %i < %fileCount; %i++) { + %text = %sort.getRowText(%i); + messageClient(%client, '', %format @ getSubStr(%text, 20, strLen(%text))); + } + } + else + { + if(strLen(%pattern)) + messageClient(%client, '', "\c6No saved duplications are available for filter \"\c3" @ %pattern @ "\c6\"."); + else + messageClient(%client, '', "\c6No saved duplications are available."); + } + + %sort.delete(); + + messageClient(%client, '', "\c6Scroll using \c3PageUp\c6 and \c3PageDown\c6 if you can't see the whole list."); +} + +//Alternative short commands +function serverCmdSD(%client, %f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7) {serverCmdSaveDup(%client, %f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7);} +function serverCmdLD(%client, %f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7) {serverCmdLoadDup(%client, %f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7);} +function serverCmdAD(%client, %pattern) {serverCmdAllDups(%client, %pattern);} + + + +//Admin commands +/////////////////////////////////////////////////////////////////////////// + +//Cancel all active dups in case of spamming +function serverCmdClearDups(%client) +{ + if(!%client.isAdmin) + { + messageClient(%client, '', "\c6Canceling all duplicators is admin only. Ask an admin for help."); + return; + } + + messageAll('MsgClearBricks', "\c3" @ %client.getPlayerName() @ "\c0 canceled all duplicators."); + + for(%i = 0; %i < ClientGroup.getCount(); %i++) + { + %cl = ClientGroup.getObject(%i); + + if(%cl.ndModeIndex) + %cl.ndKillMode(); + } +} + +//Plant as a different brick group +function serverCmdPlantAs(%client, %t0, %t1, %t2, %t3, %t4) +{ + //Check mode + if(%client.ndModeIndex != $NDM::PlantCopy) + { + messageClient(%client, '', "\c6This command can only be used in Plant Mode."); + return; + } + + //Empty target to clear + if(!strLen(%t0)) + { + messageClient(%client, '', "\c6Bricks will be planted in your own group!"); + %client.ndSelection.targetGroup = ""; + %client.ndSelection.targetBlid = ""; + %client.ndUpdateBottomPrint(); + return; + } + + for(%i = 0; strLen(%t[%i]); %i++) + %target = %target SPC %t[%i]; + + //Attempt to find the brick group by name or blid + %target = trim(%target); + %targetClient = findClientByName(%target); + + if(!isObject(%targetClient)) + %targetClient = findClientByBL_ID(%target); + + if(isObject(%targetClient)) + %targetGroup = %targetClient.brickGroup; + else if((%target | 0) $= %target) + %targetGroup = nameToId("BrickGroup_" @ %target); + + if(!isObject(%targetGroup)) + { + messageClient(%client, '', "\c6No brick group was found for \"\c3" @ %target @ "\c6\"."); + messageClient(%client, '', "\c6Bricks will be planted in your own group!"); + %client.ndSelection.targetGroup = ""; + %client.ndSelection.targetBlid = ""; + %client.ndUpdateBottomPrint(); + return; + } + + //Check whether we have trust to the target group + if(getTrustLevel(%client, %targetGroup) < 1 && + (!%client.isAdmin || !$Pref::Server::ND::AdminTrustBypass2)) + { + messageClient(%client, '', "\c6You need build trust with \c3" + @ %targetGroup.name @ "\c6 to plant bricks in their group."); + + %client.ndSelection.targetGroup = ""; + %client.ndSelection.targetBlid = ""; + %client.ndUpdateBottomPrint(); + return; + } + + //Should be good to go + %name = %targetGroup.name; + if(getSubStr(%name, strLen(%name) - 1, 1) $= "s") + messageClient(%client, '', "\c6Bricks will now be planted in \c3" @ %name @ "\c6' group!"); + else + messageClient(%client, '', "\c6Bricks will now be planted in \c3" @ %name @ "\c6's group!"); + + %client.ndSelection.targetGroup = %targetGroup; + %client.ndSelection.targetBlid = %targetGroup.bl_id; + %client.ndUpdateBottomPrint(); +} + +//Alternative short command +function serverCmdPA(%client, %t0, %t1, %t2, %t3, %t4) {serverCmdPlantAs(%client, %t0, %t1, %t2, %t3, %t4);} diff --git a/scripts/server/datablocks.cs b/scripts/server/datablocks.cs new file mode 100644 index 0000000..4063005 --- /dev/null +++ b/scripts/server/datablocks.cs @@ -0,0 +1,344 @@ +// Creates datablocks for the handheld items and the selection box. +// ------------------------------------------------------------------- + +//Basic golden duplicator +/////////////////////////////////////////////////////////////////////////// + +//Duplicator Item +datablock ItemData(ND_Item) +{ + cameraMaxDist = 0.1; + canDrop = 1; + category = "Tools"; + className = "Weapon"; + density = 0.2; + doColorShift = false; + colorShiftColor = "1 0.84 0 1"; + elasticity = 0.2; + emap = 1; + friction = 0.6; + iconName = $ND::ResourcePath @ "server/icon"; + image = "ND_Image"; + shapeFile = $ND::ResourcePath @ "server/duplicator_brick.dts"; + uiName = "Duplicator"; +}; + +//Particles for explosion +datablock ParticleData(ND_HitParticle) +{ + colors[0] = "1 0.84 0 0.9"; + colors[1] = "1 0.84 0 0.7"; + colors[2] = "1 0.84 0 0.5"; + gravityCoefficient = 0.7; + lifetimeMS = 600; + lifetimeVarianceMS = 200; + sizes[0] = 0.6; + sizes[1] = 0.4; + sizes[2] = 0.3; + spinRandomMax = 90; + spinRandomMin = -90; + textureName = "base/client/ui/brickIcons/2x2"; + times[1] = 0.8; + times[2] = 1; +}; + +//Emitter for explosion +datablock ParticleEmitterData(ND_HitEmitter) +{ + lifetimeMS = 20; + ejectionPeriodMS = 1; + periodVarianceMS = 0; + ejectionVelocity = 3; + ejectionOffset = 0.2; + particles = ND_HitParticle; + thetaMin = 20; + thetaMax = 80; + velocityVariance = 0; +}; + +//Explosion +datablock ExplosionData(ND_HitExplosion) +{ + camShakeDuration = 0.5; + camShakeFreq = "1 1 1"; + emitter[0] = ND_HitEmitter; + faceViewer = 1; + lifetimeMS = 180; + lightEndRadius = 0; + lightStartColor = "0 0 0 0"; + lightStartRadius = 0; + shakeCamera = 1; + soundProfile = "wandHitSound"; +}; + +//Projectile to make explosion +datablock ProjectileData(ND_HitProjectile) +{ + bounceElasticity = 0; + bounceFriction = 0; + explodeOnDeath = 1; + explosion = ND_HitExplosion; + fadeDelay = 2; + gravityMod = 0; + lifetime = 0; + range = 10; +}; + +//Swing particles +datablock ParticleData(ND_WaitParticle) +{ + colors[0] = "1 0.84 0 0.9"; + colors[1] = "1 0.84 0 0.7"; + colors[2] = "1 0.84 0 0.5"; + gravityCoefficient = -0.4; + dragCoefficient = 2; + lifetimeMS = 400; + lifetimeVarianceMS = 200; + sizes[0] = 0.5; + sizes[1] = 0.8; + sizes[2] = 0; + spinRandomMax = 0; + spinRandomMin = 0; + textureName = "base/client/ui/brickIcons/1x1"; + times[1] = 0.5; + times[2] = 1; +}; + +//Swing emitter +datablock ParticleEmitterData(ND_WaitEmitter) +{ + lifetimeMS = 5000; + ejectionPeriodMS = 10; + periodVarianceMS = 0; + ejectionVelocity = 1; + ejectionOffset = 0.01; + particles = ND_WaitParticle; + thetaMin = 20; + thetaMax = 80; + velocityVariance = 0; +}; + +//Spin particles +datablock ParticleData(ND_SpinParticle : ND_WaitParticle) +{ + colors[0] = "1 0.65 0 0.9"; + colors[1] = "1 0.65 0 0.7"; + colors[2] = "1 0.65 0 0.5"; + gravityCoefficient = 0; + sizes[0] = 0.3; + sizes[1] = 0.5; + sizes[2] = 0; + textureName = "base/client/ui/brickIcons/1x1"; +}; + +//Spin emitter +datablock ParticleEmitterData(ND_SpinEmitter : ND_WaitEmitter) +{ + particles = ND_SpinParticle; + ejectionPeriodMS = 15; + thetaMin = 40; + thetaMax = 140; + ejectionVelocity = 2; +}; + +//Duplicator image +datablock ShapeBaseImageData(ND_Image) +{ + shapeFile = $ND::ResourcePath @ "server/duplicator_brick.dts"; + className = "WeaponImage"; + emap = true; + mountPoint = 0; + offset = "0 0 0"; + eyeOffset = "0.7 1.4 -0.9"; + armReady = true; + showBricks = true; + doColorShift = true; + colorShiftColor = "1 0.84 0 1"; + item = ND_Item; + projectile = ND_HitProjectile; + loaded = false; + + //Image states + stateName[0] = "Activate"; + stateSpinThread[0] = "Stop"; + stateTimeoutValue[0] = 0; + stateAllowImageChange[0] = false; + stateTransitionOnTimeout[0] = "Idle"; + + stateName[1] = "Idle"; + stateSpinThread[1] = "Stop"; + stateAllowImageChange[1] = true; + stateTransitionOnNotLoaded[1] = "StartSpinning"; + stateTransitionOnTriggerDown[1] = "PreFire"; + + stateName[2] = "PreFire"; + stateScript[2] = "onPreFire"; + stateTimeoutValue[2] = 0.01; + stateAllowImageChange[2] = false; + stateTransitionOnTimeout[2] = "Fire"; + + stateName[3] = "Fire"; + stateFire[3] = true; + stateScript[3] = "onFire"; + stateEmitter[3] = ND_WaitEmitter; + stateSequence[3] = "swing"; + stateEmitterNode[3] = "muzzlePoint"; + stateEmitterTime[3] = 0.01; + stateTimeoutValue[3] = 0.01; + stateWaitForTimeout[3] = true; + stateAllowImageChange[3] = false; + stateTransitionOnTimeout[3] = "CheckFire"; + + stateName[4] = "CheckFire"; + stateSpinThread[4] = "Stop"; + stateTransitionOnNotLoaded[4] = "StartSpinning_TDown"; + stateTransitionOnTriggerUp[4] = "Idle"; + + //Spinning states (from idle) + stateName[5] = "StartSpinning"; + stateSpinThread[5] = "SpinUp"; + stateTimeoutValue[5] = 0.25; + stateTransitionOnTimeout[5] = "IdleSpinning"; + + stateName[6] = "IdleSpinning"; + stateEmitter[6] = ND_SpinEmitter; + stateSpinThread[6] = "FullSpeed"; + stateEmitterNode[6] = "muzzlePoint"; + stateEmitterTime[6] = 0.35; + stateTimeoutValue[6] = 0.35; + stateTransitionOnLoaded[6] = "StopSpinning"; + stateTransitionOnTimeout[6] = "IdleSpinning"; + + stateName[7] = "StopSpinning"; + stateSpinThread[7] = "SpinDown"; + stateTimeoutValue[7] = 0.25; + stateTransitionOnTimeout[7] = "Idle"; + + //Spinning states (from checkfire, trigger is still down) + stateName[8] = "StartSpinning_TDown"; + stateSpinThread[8] = "SpinUp"; + stateTimeoutValue[8] = 0.25; + stateTransitionOnTimeout[8] = "IdleSpinning_TDown"; + + stateName[9] = "IdleSpinning_TDown"; + stateEmitter[9] = ND_SpinEmitter; + stateSpinThread[9] = "FullSpeed"; + stateEmitterNode[9] = "muzzlePoint_TDown"; + stateEmitterTime[9] = 0.4; + stateTimeoutValue[9] = 0.4; + stateTransitionOnLoaded[9] = "StopSpinning_TDown"; + stateTransitionOnTimeout[9] = "IdleSpinning_TDown"; + + stateName[10] = "StopSpinning_TDown"; + stateSpinThread[10] = "SpinDown"; + stateTimeoutValue[10] = 0.25; + stateTransitionOnTimeout[10] = "CheckFire"; +}; + + +//Spinning selection box for box mode +/////////////////////////////////////////////////////////////////////////// + +//Duplicator image +datablock ShapeBaseImageData(ND_Image_Box : ND_Image) +{ + shapeFile = $ND::ResourcePath @ "server/duplicator_selection.dts"; +}; + + +//Blue duplicator for plant mode +/////////////////////////////////////////////////////////////////////////// + +//Particles for explosion +datablock ParticleData(ND_HitParticle_Blue : ND_HitParticle) +{ + colors[0] = "0 0.25 1 0.9"; + colors[1] = "0 0.25 1 0.7"; + colors[2] = "0 0.25 1 0.5"; +}; + +//Emitter for explosion +datablock ParticleEmitterData(ND_HitEmitter_Blue : ND_HitEmitter) +{ + particles = ND_HitParticle_Blue; +}; + +//Explosion +datablock ExplosionData(ND_HitExplosion_Blue : ND_HitExplosion) +{ + emitter[0] = ND_HitEmitter_Blue; +}; + +//Projectile to make explosion +datablock ProjectileData(ND_HitProjectile_Blue : ND_HitProjectile) +{ + explosion = ND_HitExplosion_Blue; +}; + +//Swing particles +datablock ParticleData(ND_WaitParticle_Blue : ND_WaitParticle) +{ + colors[0] = "0 0.25 1 0.9"; + colors[1] = "0 0.25 1 0.7"; + colors[2] = "0 0.25 1 0.5"; +}; + +//Swing emitter +datablock ParticleEmitterData(ND_WaitEmitter_Blue : ND_WaitEmitter) +{ + particles = ND_WaitParticle_Blue; +}; + +//Spin particles +datablock ParticleData(ND_SpinParticle_Blue : ND_SpinParticle) +{ + colors[0] = "0 0.25 0.75 0.9"; + colors[1] = "0 0.25 0.75 0.7"; + colors[2] = "0 0.25 0.75 0.5"; +}; + +//Spin emitter +datablock ParticleEmitterData(ND_SpinEmitter_Blue : ND_SpinEmitter) +{ + particles = ND_SpinParticle_Blue; +}; + +//Duplicator image +datablock ShapeBaseImageData(ND_Image_Blue : ND_Image) +{ + colorShiftColor = "0 0.25 1 1"; + projectile = ND_HitProjectile_Blue; + + //Image states + stateEmitter[3] = ND_WaitEmitter_Blue; + stateEmitter[6] = ND_SpinEmitter_Blue; + stateEmitter[9] = ND_SpinEmitter_Blue; +}; + + +//Resizable selection and highlight box +/////////////////////////////////////////////////////////////////////////// + +//Transparent box to visualize bricks intersecting selection box +datablock StaticShapeData(ND_SelectionBoxOuter) +{ + shapeFile = $ND::ResourcePath @ "server/selectionbox_outer.dts"; +}; + +//Inside box (inverted normals) to visualize backfaces +datablock StaticShapeData(ND_SelectionBoxInner) +{ + shapeFile = $ND::ResourcePath @ "server/selectionbox_inner.dts"; +}; + +//Small box to create solid edges +datablock StaticShapeData(ND_SelectionBoxBorder) +{ + shapeFile = $ND::ResourcePath @ "server/selectionbox_border.dts"; +}; + +//Empty shape to hold shapename +datablock StaticShapeData(ND_SelectionBoxShapeName) +{ + shapeFile = "base/data/shapes/empty.dts"; +}; diff --git a/scripts/server/functions.cs b/scripts/server/functions.cs new file mode 100644 index 0000000..6decff2 --- /dev/null +++ b/scripts/server/functions.cs @@ -0,0 +1,734 @@ +// This file should not exist. Fix later... +// ------------------------------------------------------------------- + +//Math functions +/////////////////////////////////////////////////////////////////////////// + +//Rotate vector around +Z in 90 degree steps +function ndRotateVector(%vector, %steps) +{ + switch(%steps % 4) + { + case 0: return %vector; + case 1: return getWord(%vector, 1) SPC -getWord(%vector, 0) SPC getWord(%vector, 2); + case 2: return -getWord(%vector, 0) SPC -getWord(%vector, 1) SPC getWord(%vector, 2); + case 3: return -getWord(%vector, 1) SPC getWord(%vector, 0) SPC getWord(%vector, 2); + } +} + +//Rotate and mirror a direction +function ndTransformDirection(%dir, %steps, %mirrX, %mirrY, %mirrZ) +{ + if(%dir > 1) + { + if(%mirrX && %dir % 2 == 1 + || %mirrY && %dir % 2 == 0) + %dir += 2; + + %dir = (%dir + %steps - 2) % 4 + 2; + } + else if(%mirrZ) + %dir = !%dir; + + return %dir; +} + +//Get the closest paint color to an rgb value +function ndGetClosestColorID(%rgb) +{ + //Set initial value + %best = 0; + %bestDiff = 999999; + + for(%i = 0; %i < 64; %i++) + { + %color = getColorI(getColorIdTable(%i)); + + %diff = vectorLen(vectorSub(%rgb, %color)); + + if(getWord(%color, 3) != 255) + %diff += 1000; + + if(%diff < %bestDiff) + { + %best = %i; + %bestDiff = %diff; + } + } + + return %best; +} + +//Get the closest paint color to an rgba value +function ndGetClosestColorID2(%rgba) +{ + %rgb = getWords(%rgba, 0, 2); + %a = getWord(%rgba, 3); + + //Set initial value + %best = 0; + %bestDiff = 999999; + + for(%i = 0; %i < 64; %i++) + { + %color = getColorI(getColorIdTable(%i)); + %alpha = getWord(%color, 3); + + %diff = vectorLen(vectorSub(%rgb, %color)); + + if((%alpha > 254) != (%a > 254)) + %diff += 1000; + else + %diff += mAbs(%alpha - %a) * 0.5; + + if(%diff < %bestDiff) + { + %best = %i; + %bestDiff = %diff; + } + } + + return %best; +} + +//Convert a paint color to a code +function ndGetPaintColorCode(%id) +{ + %rgb = getColorI(getColorIdTable(%id)); + %chars = "0123456789abcdef"; + + %r = getWord(%rgb, 0); + %g = getWord(%rgb, 1); + %b = getWord(%rgb, 2); + + %r1 = getSubStr(%chars, (%r / 16) | 0, 1); + %r2 = getSubStr(%chars, %r % 16 , 1); + + %g1 = getSubStr(%chars, (%g / 16) | 0, 1); + %g2 = getSubStr(%chars, %g % 16 , 1); + + %b1 = getSubStr(%chars, (%b / 16) | 0, 1); + %b2 = getSubStr(%chars, %b % 16 , 1); + + return ""; +} + +//Get a plate world box from a raycast +function ndGetPlateBoxFromRayCast(%pos, %normal) +{ + //Get half size of world box for offset + %halfSize = "0.25 0.25 0.1"; + + //Point offset in correct direction based on normal + %offX = getWord(%halfSize, 0) * mFloatLength(-getWord(%normal, 0), 0); + %offY = getWord(%halfSize, 1) * mFloatLength(-getWord(%normal, 1), 0); + %offZ = getWord(%halfSize, 2) * mFloatLength(-getWord(%normal, 2), 0); + %offset = %offX SPC %offY SPC %offZ; + + //Get offset position + %newPos = vectorAdd(%pos, %offset); + + //Get the plate box around the position + %x1 = mFloor(getWord(%newPos, 0) * 2) / 2; + %y1 = mFloor(getWord(%newPos, 1) * 2) / 2; + %z1 = mFloor(getWord(%newPos, 2) * 5) / 5; + + %x2 = mCeil(getWord(%newPos, 0) * 2) / 2; + %y2 = mCeil(getWord(%newPos, 1) * 2) / 2; + %z2 = mCeil(getWord(%newPos, 2) * 5) / 5; + + return %x1 SPC %y1 SPC %z1 SPC %x2 SPC %y2 SPC %z2; +} + + + +//Trust checks +/////////////////////////////////////////////////////////////////////////// + +//Send a message if a client doesn't have select trust to a brick +function ndTrustCheckMessage(%obj, %client) +{ + %group = %client.brickGroup.getId(); + %bl_id = %client.bl_id; + %admin = %client.isAdmin; + + if(ndTrustCheckSelect(%obj, %group, %bl_id, %admin)) + return true; + + if(%obj.getGroup().bl_id == 888888 && !$Pref::Server::ND::SelectPublicBricks) + return false; + + messageClient(%client, 'MsgError', ""); + commandToClient(%client, 'centerPrint', "\c6You don't have enough trust to do that!", 5); + return false; +} + +//Check whether a client has enough trust to select a brick +function ndTrustCheckSelect(%obj, %group2, %bl_id, %admin) +{ + %group1 = %obj.getGroup(); + + //Client owns brick + if(%group1 == %group2) + return true; + + //Client owns stack + if(%obj.stackBL_ID == %bl_id) + return true; + + //Client has trust to the brick + if(%group1.Trust[%bl_id] >= $Pref::Server::ND::TrustLimit) + return true; + + //Client has trust to the stack of the brick + if(%group2.Trust[%obj.stackBL_ID] >= $Pref::Server::ND::TrustLimit) + return true; + + //Client is admin + if(%admin && $Pref::Server::ND::AdminTrustBypass1) + return true; + + //Client can duplicate public bricks + if(%group1.bl_id == 888888 && $Pref::Server::ND::SelectPublicBricks) + return true; + + return false; +} + +//Check whether a client has enough trust to modify a brick +function ndTrustCheckModify(%obj, %group2, %bl_id, %admin) +{ + %group1 = %obj.getGroup(); + + //Client owns brick + if(%group1 == %group2) + return true; + + //Client owns stack + if(%obj.stackBL_ID == %bl_id) + return true; + + //Client has trust to the brick + if(%group1.Trust[%bl_id] >= 2) + return true; + + //Client has trust to the stack of the brick + if(%group2.Trust[%obj.stackBL_ID] >= 2) + return true; + + //Client is admin + if(%admin && $Pref::Server::ND::AdminTrustBypass2) + return true; + + return false; +} + +//Fast check whether a client has enough trust to plant on a brick +function ndFastTrustCheck(%brick, %bl_id, %brickGroup) +{ + %group = %brick.getGroup(); + + if(%group == %brickGroup) + return true; + + if(%group.Trust[%bl_id] > 0) + return true; + + if(%group.bl_id == 888888) + return true; + + return false; +} + + + +//General stuff +/////////////////////////////////////////////////////////////////////////// + +//Setup list of spawned clients +function ndUpdateSpawnedClientList() +{ + $ND::NumSpawnedClients = 0; + + for(%i = 0; %i < ClientGroup.getCount(); %i++) + { + %cl = ClientGroup.getObject(%i); + + if(%cl.hasSpawnedOnce) + { + $ND::SpawnedClient[$ND::NumSpawnedClients] = %cl; + $ND::NumSpawnedClients++; + } + } +} + +//Applies mirror effect to a single ghost brick +function FxDtsBrick::ndMirrorGhost(%brick, %client, %axis) +{ + //Offset position + %bPos = %brick.position; + + //Rotated local angle id + %bAngle = %brick.angleID; + + //Apply mirror effects (ugh) + %datablock = %brick.getDatablock(); + + if(%axis == 0) + { + //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 + { + messageClient(%client, '', "\c6Sorry, your ghost brick is asymmetric and cannot be mirrored."); + return; + } + + //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(%axis == 1) + { + //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 + { + messageClient(%client, '', "\c6Sorry, your ghost brick is asymmetric and cannot be mirrored."); + return; + } + + //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; + } + } + else + { + //Change datablock if asymmetric + if(!$ND::SymmetryZ[%datablock]) + { + if(%db = $ND::SymmetryZDatablock[%datablock]) + { + %datablock = %db; + %bAngle = (%bAngle + $ND::SymmetryZOffset[%datablock]) % 4; + } + else + { + messageClient(%client, '', "\c6Sorry, your ghost brick is not vertically symmetric and cannot be mirrored."); + return; + } + } + } + + //Apply datablock + if(%brick.getDatablock() != %datablock) + %brick.setDatablock(%datablock); + + 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); +} + + + +//Supercut helpers +/////////////////////////////////////////////////////////////////////////// + +//Creates simple brick lookup table +function ndCreateSimpleBrickTable() +{ + deleteVariables("$ND::SimpleBrick*"); + %max = getDatablockGroupSize(); + + %file = new FileObject(); + %sorter = new GuiTextListCtrl(); + + for(%i = 0; %i < %max; %i++) + { + %db = getDatablock(%i); + + if(%db.getClassName() $= "FxDtsBrickData") + { + //Skip unsuitable bricks + if(( + %db.isWaterBrick + || %db.hasPrint + || %db.isSlyrBrick + || %db.uiName $= "" + || %db.ndDontUseForFill + || %db.category $= "Special" + || %db.isLogicGate + ) && ( + ndSubsetOfDatablock(%db)==$ND::SubsetDefault + ) + ){ + continue; + } + + %db.ndSubset = ndSubsetOfDatablock(%db); + + %file.openForRead(%db.brickFile); + %file.readLine(); + + //We only want simple bricks here + if(%file.readLine() $= "BRICK") + { + //Skip brick sizes that we already have + if(!$ND::SimpleBrickBlock[%db.brickSizeX, %db.brickSizeY, %db.BrickSizeZ, %db.ndSubset]){ + %sorter.addRow(%db, %db.getVolume()); + } + + $ND::SimpleBrickBlock[%db.brickSizeX, %db.brickSizeY, %db.BrickSizeZ, %db.ndSubset] = true; + } + + %file.close(); + } + } + + %file.delete(); + + //Sort the bricks by volume + %sorter.sortNumerical(0, 1); + + //Copy sorted bricks to global variable array + $ND::SimpleBrickCount = %sorter.rowCount(); + for(%i = 0; %i < $ND::SimpleBrickCount; %i++) + { + %db = %sorter.getRowId(%i); + %volume = %sorter.getRowText(%i); + + $ND::SimpleBrick[%i] = %db; + $ND::SimpleBrickVolume[%i] = %volume; + + $ND::SimpleBrickSubset[%i] = %db.ndSubset; + + //Ensure X < Y in lookup table + if(%db.brickSizeX <= %db.brickSizeY) + { + $ND::SimpleBrickSizeX[%i] = %db.brickSizeX; + $ND::SimpleBrickSizeY[%i] = %db.brickSizeY; + $ND::SimpleBrickRotated[%i] = false; + } + else + { + $ND::SimpleBrickSizeX[%i] = %db.brickSizeY; + $ND::SimpleBrickSizeY[%i] = %db.brickSizeX; + $ND::SimpleBrickRotated[%i] = true; + } + + $ND::SimpleBrickSizeZ[%i] = %db.brickSizeZ; + } + + %sorter.delete(); + $ND::SimpleBrickTableCreated = true; +} + +//Find the largest (volume) brick that fits inside the area +function ndGetLargestBrickId(%x, %y, %z, %subset) +{ + if(!$ND::SimpleBrickTableCreated) + ndCreateSimpleBrickTable(); + + %maxVolume = %x * %y * %z; + %start = $ND::SimpleBrickCount - 1; + + if($ND::SimpleBrickVolume[%start] > %maxVolume) + { + //Use binary search to find the largest volume that + //is smaller or equal to the volume of the area + %bound1 = 0; + %bound2 = %start; + + while(%bound1 < %bound2) + { + %i = mCeil((%bound1 + %bound2) / 2); + %volume = $ND::SimpleBrickVolume[%i]; + + if(%volume > %maxVolume) + { + %bound2 = %i - 1; + continue; + } + + if(%volume <= %maxVolume) + { + %bound1 = %i + 1; + continue; + } + } + + %start = %bound2; + } + + %bestIndex = -1; + + //Go down the list until a brick fits on all 3 axis + for(%i = %start; %i >= 0; %i--) + { + if( + $ND::SimpleBrickSizeX[%i] <= %x + && $ND::SimpleBrickSizeY[%i] <= %y + && $ND::SimpleBrickSizeZ[%i] <= %z + && $ND::SimpleBrickSubset[%i] == %subset + ) { + return %i; + } + } + + return -1; +} + +//Fill an area with bricks +function ndFillAreaWithBricks(%pos1, %pos2) +{ + %pos1_x = getWord(%pos1, 0); + %pos1_y = getWord(%pos1, 1); + %pos1_z = getWord(%pos1, 2); + + %pos2_x = getWord(%pos2, 0); + %pos2_y = getWord(%pos2, 1); + %pos2_z = getWord(%pos2, 2); + + %size_x = %pos2_x - %pos1_x; + %size_y = %pos2_y - %pos1_y; + %size_z = %pos2_z - %pos1_z; + + if(%size_x < 0.05 || %size_y < 0.05 || %size_z < 0.05) + return; + + if(%size_x > %size_y) + { + %tmp = %size_y; + %size_y = %size_x; + %size_x = %tmp; + %rotated = true; + } + + %brickId = ndGetLargestBrickId(%size_x * 2 + 0.05, %size_y * 2 + 0.05, %size_z * 5 + 0.02, $ND::FillBrickSubset); + + if(!%rotated) + { + %pos3_x = %pos1_x + $ND::SimpleBrickSizeX[%brickId] / 2; + %pos3_y = %pos1_y + $ND::SimpleBrickSizeY[%brickId] / 2; + } + else + { + %pos3_x = %pos1_x + $ND::SimpleBrickSizeY[%brickId] / 2; + %pos3_y = %pos1_y + $ND::SimpleBrickSizeX[%brickId] / 2; + } + + %pos3_z = %pos1_z + $ND::SimpleBrickSizeZ[%brickId] / 5; + %plantPos = (%pos1_x + %pos3_x) / 2 SPC (%pos1_y + %pos3_y) / 2 SPC (%pos1_z + %pos3_z) / 2; + + if(!isObject($ND::SimpleBrick[%brickId])) + return; + + %brick = new FxDTSBrick() + { + datablock = $ND::SimpleBrick[%brickId]; + isPlanted = true; + client = $ND::FillBrickClient; + + position = %plantPos; + rotation = (%rotated ^ $ND::SimpleBrickRotated[%brickId]) ? "0 0 1 90.0002" : "1 0 0 0"; + angleID = %rotated; + + colorID = $ND::FillBrickColorID; + colorFxID = $ND::FillBrickColorFxID; + shapeFxID = $ND::FillBrickShapeFxID; + + printID = 0; + }; + + //This will call ::onLoadPlant instead of ::onPlant + %prev1 = $Server_LoadFileObj; + %prev2 = $LastLoadedBrick; + $Server_LoadFileObj = %brick; + $LastLoadedBrick = %brick; + + //Add to brickgroup + $ND::FillBrickGroup.add(%brick); + + //Attempt plant + %error = %brick.plant(); + + //Restore variable + $Server_LoadFileObj = %prev1; + $LastLoadedBrick = %prev2; + + if(!%error || %error == 2) + { + //Set trusted + if(%brick.getNumDownBricks()) + %brick.stackBL_ID = %brick.getDownBrick(0).stackBL_ID; + else if(%brick.getNumUpBricks()) + %brick.stackBL_ID = %brick.getUpBrick(0).stackBL_ID; + else + %brick.stackBL_ID = $ND::FillBrickBL_ID; + + %brick.trustCheckFinished(); + + %brick.setRendering($ND::FillBrickRendering); + %brick.setColliding($ND::FillBrickColliding); + %brick.setRayCasting($ND::FillBrickRayCasting); + + //Instantly ghost the brick to all spawned clients (wow hacks) + for(%j = 0; %j < $ND::NumSpawnedClients; %j++) + { + %cl = $ND::SpawnedClient[%j]; + %brick.scopeToClient(%cl); + %brick.clearScopeToClient(%cl); + } + + $ND::FillBrickCount++; + } + else + %brick.delete(); + + if((%pos3_x + 0.05) < %pos2_x) + ndFillAreaWithBricks(%pos3_x SPC %pos1_y SPC %pos1_z, %pos2_x SPC %pos2_y SPC %pos2_z); + + if((%pos3_y + 0.05) < %pos2_y) + ndFillAreaWithBricks(%pos1_x SPC %pos3_y SPC %pos1_z, %pos3_x SPC %pos2_y SPC %pos2_z); + + if((%pos3_z + 0.02) < %pos2_z) + ndFillAreaWithBricks(%pos1_x SPC %pos1_y SPC %pos3_z, %pos3_x SPC %pos3_y SPC %pos2_z); + +} + +//Client finished supercut, now fill bricks +function GameConnection::doFillBricks(%this, %subset) +{ + //Set variables for the fill brick function + $ND::FillBrickGroup = %this.brickGroup; + $ND::FillBrickClient = %this; + $ND::FillBrickBL_ID = %this.bl_id; + + $ND::FillBrickColorID = %this.currentColor; + $ND::FillBrickColorFxID = 0; + $ND::FillBrickShapeFxID = 0; + + $ND::FillBrickRendering = true; + $ND::FillBrickColliding = true; + $ND::FillBrickRayCasting = true; + + $ND::FillBrickSubset = %subset; + + %box = %this.ndSelectionBox.getWorldBox(); + $ND::FillBrickCount = 0; + ndUpdateSpawnedClientList(); + ndFillAreaWithBricks(getWords(%box, 0, 2), getWords(%box, 3, 5)); + + //%s = ($ND::FillBrickCount == 1 ? "" : "s"); + //messageClient(%this, '', "\c6Filled in \c3" @ $ND::FillBrickCount @ "\c6 brick" @ %s); +} + +$ND::SubsetDefault = 0; +$ND::SubsetLogicWire = 1; +$ND::SubsetLogicBuffer = 2; +$ND::SubsetLogicBufferAl = 3; +$ND::SubsetLogicDff = 4; +$ND::SubsetLogicDffAl = 5; +$ND::SubsetLogicEnabler = 6; +$ND::SubsetLogicEnablerAl = 7; + +// Which subset of fill bricks to use - normal or wire +function ndSubsetOfDatablock(%data){ + if(%data.isLogic) { + if(%data.isLogicWire) { + return $ND::SubsetLogicWire; + } else if(strStr(%data.uiName, "Buffer") == 0) { + return (strStr(%data.uiName, "Active Low")==-1) ? $ND::SubsetLogicBuffer : $ND::SubsetLogicBufferAl; + } else if(strStr(%data.uiName, "D FlipFlop") == 0) { + return (strStr(%data.uiName, "Active Low")==-1) ? $ND::SubsetLogicDff : $ND::SubsetLogicDffAl; + }else if(strStr(%data.uiName, "Enabler") == 0) { + return (strStr(%data.uiName, "Active Low")==-1) ? $ND::SubsetLogicEnabler : $ND::SubsetLogicEnablerAl; + } else { + return $ND::SubsetDefault; + } + } else { + return $ND::SubsetDefault; + } +} + +function ndLookupSubsetName(%name) { + %subset = $ND::Subset[%name]; + return (%subset !$= "") ? %subset : $ND::SubsetDefault; +} diff --git a/scripts/server/handshake.cs b/scripts/server/handshake.cs new file mode 100644 index 0000000..2108f6c --- /dev/null +++ b/scripts/server/handshake.cs @@ -0,0 +1,71 @@ +// Sends a handshake to new clients to obtain their duplicator version. +// ------------------------------------------------------------------- + +package NewDuplicator_Server +{ + //Send handshake request to new client + function GameConnection::autoAdminCheck(%this) + { + %this.ndClient = false; + %this.ndVersion = "0.0.0"; + + commandToClient(%this, 'ndHandshake', $ND::Version); + return parent::autoAdminCheck(%this); + } +}; + +//Client responded, so he has new duplicator +function serverCmdNdHandshake(%this, %version) +{ + cancel(%this.ndHandshakeTimeout); + + %this.ndClient = true; + %this.ndVersion = %version; + + //Inform client whether he has an outdated version + switch(ndCompareVersion($ND::Version, %version)) + { + case 1: + %m = "\c6Your version of the \c3New Duplicator\c6 is outdated! Some features might not work. "; + %m = %m @ "(Server Version: \c3" @ $ND::Version @ "\c6 | Your Version: \c0" @ %version @ "\c6)"; + //messageClient(%this, '', %m); + + case 2: + //Hide this message on long-running dedicated servers + if($Sim::Time < 86400) + { + %m = "\c6Your version of the \c3New Duplicator\c6 is newer than the server's! Ask the host to update it! "; + %m = %m @ "(Server Version: \c0" @ $ND::Version @ "\c6 | Your Version: \c3" @ %version @ "\c6)"; + //messageClient(%this, '', %m); + } + } +} + +//Compares two version numbers (major.minor.patch) +function ndCompareVersion(%ver1, %ver2) +{ + %ver1 = strReplace(%ver1, ".", " "); + %ver2 = strReplace(%ver2, ".", " "); + + %count = getMax(getWordCount(%ver1), getWordCount(%ver2)); + + for(%i = 0; %i < %count; %i ++) + { + %v1 = getWord(%ver1, %i); + %v2 = getWord(%ver2, %i); + + if(%v1 > %v2) + return 1; + else if(%v1 < %v2) + return 2; + } + + return 0; +} + +//Send handshakes to all clients +function ndResendHandshakes() +{ + for(%i = 0; %i < ClientGroup.getCount(); %i++) + commandToClient(ClientGroup.getObject(%i), 'ndHandshake', $ND::Version); +} diff --git a/scripts/server/highlight.cs b/scripts/server/highlight.cs new file mode 100644 index 0000000..1ca99da --- /dev/null +++ b/scripts/server/highlight.cs @@ -0,0 +1,106 @@ +// Handles highlighting and de-highlighting large groups of bricks. +// ------------------------------------------------------------------- + +//Highlight group data $NDH::* +// $NDH::LastId : The id of the last created highlight group +// $NDH::Count : Total number of active highlight groups +// +// $NDHN[brick] : Number of groups a brick is in +// $NDHF[brick] : Original color fx of the brick +// +// $NDH[group] : Count of bricks in a group +// $NDH[group, i] : Brick in group at position i + +//Reserve a highlight group id +function ndNewHighlightGroup() +{ + //Increase group number + $NDH::LastId++; + $NDH::Count++; + + //Set initial count + $NDH[$NDH::LastId] = 0; + + //Assign free id + return $NDH::LastId; +} + +//Remove highlight group and clean up garbage variables +function ndRemoveHighlightGroup(%group) +{ + //Don't delete groups that don't exist + if($NDH[%group] $= "") + return; + + //Lower group number + $NDH::Count--; + + //Clear count to allow reuse of index + $NDH[%group] = ""; + + //Cancel schedules + cancel($NDHS[%group]); + + //If this is the most recently created group, pretend it never existed + if($NDH::LastId == %group) + $NDH::LastId--; + + //If this is the last highlight group, just delete ALL highlight variables + if($NDH::Count < 1) + deleteVariables("$NDH*"); +} + +//Add a brick to a highlight group +function ndHighlightBrick(%group, %brick) +{ + //If brick is not highlighted, do that + if(!$NDHN[%brick]) + { + $NDHF[%brick] = %brick.colorFxID; + %brick.setColorFx(3); + } + + //Increase group number of this brick + $NDHN[%brick]++; + + //Add brick to highlight group + $NDH[%group, ($NDH[%group]++) - 1] = %brick; +} + +//Start de-highlighting bricks +function ndStartDeHighlight(%group) +{ + //Don't do this if already de-highlighting + %t = getTimeRemaining($NDHS[%group]); + + if(%t > 66 || %t == 0) + { + cancel($NDHS[%group]); + ndTickDeHighlight(%group, 0); + } +} + +//Tick de-highlighting bricks +function ndTickDeHighlight(%group, %start) +{ + %end = $NDH[%group]; + + if(%end - %start > $Pref::Server::ND::ProcessPerTick) + %end = %start + $Pref::Server::ND::ProcessPerTick; + else + %lastTick = true; + + for(%i = %start; %i < %end; %i++) + { + %brick = $NDH[%group, %i]; + + //If the brick is in no more groups, de-highlight it + if(isObject(%brick) && !($NDHN[%brick]--)) + %brick.setColorFx($NDHF[%brick]); + } + + if(!%lastTick) + $NDHS[%group] = schedule(30, 0, ndTickDeHighlight, %group, %end); + else + ndRemoveHighlightGroup(%group); +} diff --git a/scripts/server/images.cs b/scripts/server/images.cs new file mode 100644 index 0000000..085b42a --- /dev/null +++ b/scripts/server/images.cs @@ -0,0 +1,297 @@ +// Handles interactions with the handheld duplicator item. +// ------------------------------------------------------------------- + +//Set which image a client should use +function GameConnection::ndSetImage(%this, %image) +{ + %image = %image.getId(); + + if(%image != %this.ndImage) + { + %this.ndImage = %image; + + if(%this.ndEquipped) + { + %this.ndIgnoreNextMount = true; + %this.player.schedule(0, updateArm, %image); + %this.player.schedule(0, mountImage, %image, 0); + } + } +} + +//Mount the correct image when the item is equipped +function ND_Item::onUse(%this, %player, %slot) +{ + %image = %player.client.ndImage; + + if(!isObject(%image)) + %image = ND_Image; + + %player.updateArm(%image); + %player.mountImage(%image, 0); + %player.client.ndEquippedFromItem = true; +} + +package NewDuplicator_Server +{ + //Start select mode when duplicator is equipped + function ND_Image::onMount(%this, %player, %slot) + { + parent::onMount(%this, %player, %slot); + %player.ndEquipped(); + } + + function ND_Image_Blue::onMount(%this, %player, %slot) + { + parent::onMount(%this, %player, %slot); + %player.ndEquipped(); + } + + function ND_Image_Box::onMount(%this, %player, %slot) + { + parent::onMount(%this, %player, %slot); + %player.ndEquipped(); + } + + //Cancel mode when duplicator is unequipped + function ND_Image::onUnMount(%this, %player, %slot) + { + parent::onUnMount(%this, %player, %slot); + %player.ndUnEquipped(); + } + + function ND_Image_Blue::onUnMount(%this, %player, %slot) + { + parent::onUnMount(%this, %player, %slot); + %player.ndUnEquipped(); + } + + function ND_Image_Box::onUnMount(%this, %player, %slot) + { + parent::onUnMount(%this, %player, %slot); + %player.ndUnEquipped(); + } +}; + +//Start the swinging animation +function ND_Image::onPreFire(%this, %player, %slot) +{ + %player.playThread(2, shiftTo); +} + +function ND_Image_Blue::onPreFire(%this, %player, %slot) +{ + %player.playThread(2, shiftTo); +} + +function ND_Image_Box::onPreFire(%this, %player, %slot) +{ + %player.playThread(2, shiftTo); +} + +//Handle selecting things +function ND_Image::onFire(%this, %player, %slot) +{ + %player.ndFired(); +} + +function ND_Image_Blue::onFire(%this, %player, %slot) +{ + %player.ndFired(); +} + +function ND_Image_Box::onFire(%this, %player, %slot) +{ + %player.ndFired(); +} + +//Duplicator was equipped +function Player::ndEquipped(%this) +{ + %client = %this.client; + + if(%this.isHoleBot || %this.isSlayerBot || !isObject(%client)) + return; + + if(%client.ndIgnoreNextMount) + { + %client.ndIgnoreNextMount = false; + return; + } + + if($Pref::Server::ND::AdminOnly && !%client.isAdmin) + { + commandToClient(%client, 'centerPrint', "\c6Oops! The duplicator is admin only.", 5); + return; + } + + %client.ndEquipped = true; + + //Remove temp brick so it doesn't overlap the selection box + if(isObject(%this.tempBrick)) + %this.tempBrick.delete(); + + //Should resume last used select mode + if(!%client.ndModeIndex) + %client.ndSetMode(%client.ndLastSelectMode); +} + +//Duplicator was unequipped +function Player::ndUnEquipped(%this) +{ + %client = %this.client; + + if(%this.isHoleBot || %this.isSlayerBot || !isObject(%client)) + return; + + if(%client.ndIgnoreNextMount) + return; + + if(%client.ndModeIndex && !%client.ndMode.allowUnMount) + %client.ndKillMode(); + + %client.ndEquipped = false; +} + +//Duplicator was fired +function Player::ndFired(%this) +{ + %client = %this.client; + + if(!isObject(%client) || !%client.ndModeIndex || !%client.ndMode.allowSelecting) + return; + + if(isObject(%client.minigame) && !%client.minigame.enablebuilding) + { + commandToClient(%client, 'centerPrint', "\c6Oops! Building is disabled.", 5); + return; + } + + //Support for Script_RaycastOffTools by Conan + if(%this.isRaycastTool) + %mask = $TypeMasks::FxBrickObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType; + else + %mask = $TypeMasks::FxBrickAlwaysObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType; + + %start = %this.getEyePoint(); + %dir = %this.getEyeVector(); + + //Octree::Raycast fails to detect close bricks (~1TU) with a long raycast, so we'll do 3. + //First a very short one of 1 TU to detect very close bricks, then a slightly longer one + //of 10 TU, and finally the long range one of 1000. + %len[0] = 1; + %len[1] = 10; + %len[2] = 1000; + + for(%i = 0; %i < 3; %i++) + { + %end = vectorAdd(%start, vectorScale(%dir, %len[%i])); + %ray = containerRaycast(%start, %end, %mask, %this); + + if(isObject(%obj = firstWord(%ray))) + break; + } + + if(!isObject(%obj)) + return; + + %position = posFromRaycast(%ray); + %normal = normalFromRaycast(%ray); + + //Can't directly spawn an explosion, must use a projectile + %data = %client.ndImage.projectile; + + if(!isObject(%data)) + %data = ND_HitProjectile; + + %proj = new Projectile() + { + datablock = %data; + initialPosition = %position; + initialVelocity = %normal; + }; + + //Pass on the selected object to the dupli mode + %client.ndMode.onSelectObject(%client, %obj, %position, %normal); +} + +package NewDuplicator_Server +{ + //Automatically start the "ambient" animation on duplicator items + function ND_Item::onAdd(%this, %obj) + { + parent::onAdd(%this, %obj); + %obj.playThread(0, ambient); + + //Fix colorshift bullshit + %obj.schedule(100, setNodeColor, "ALL", %this.colorShiftColor); + } + + //Prevent accidently unequipping the duplicator + function serverCmdUnUseTool(%client) + { + if(%client.ndLastEquipTime + 1.5 > $Sim::Time) + return; + + if(%client.ndModeIndex == $NDM::StackSelect || %client.ndModeIndex == $NDM::BoxSelect) + { + %client.ndToolSchedule = %client.schedule(100, ndUnUseTool); + return; + } + + parent::serverCmdUnUseTool(%client); + } + + //Prevent creating ghost bricks in modes that allow un-mount + function BrickDeployProjectile::onCollision(%this, %obj, %col, %fade, %pos, %normal) + { + %client = %obj.client; + + if(isObject(%client) && %client.ndModeIndex) + %client.ndMode.onSelectObject(%client, %col, %pos, %normal); + else + parent::onCollision(%this, %obj, %col, %fade, %pos, %normal); + } + + //Handle ghost brick movements from New Brick Tool + function placeNewGhostBrick(%client, %pos, %normal, %noOrient) + { + if(!isObject(%client) || !%client.ndModeIndex) + return parent::placeNewGhostBrick(%client, %pos, %normal, %noOrient); + + if(%client.ndModeIndex == $NDM::PlantCopy) + { + if(!%noOrient) + { + %angleID = getAngleIDFromPlayer(%client.getControlObject()) - %client.ndSelection.angleIDReference; + %rotation = ((4 - %angleID) - %client.ndSelection.ghostAngleID) % 4; + + if(%rotation != 0) + %client.ndSelection.rotateGhostBricks(%rotation, %client.ndPivot); + } + + return NDM_PlantCopy.moveBricksTo(%client, %pos, %normal); + } + + %client.ndMode.onSelectObject(%client, 0, %pos, %normal); + } +}; + +//Fix for equipping paint can calling unUseTool +function GameConnection::ndUnUseTool(%this) +{ + %player = %this.player; + + if(%this.isTalking) + serverCmdStopTalking(%this); + + if(!isObject(%player)) + return; + + %player.currTool = -1; + %this.currInv = -1; + %this.currInvSlot = -1; + + %player.unmountImage(0); + %player.playThread(1, "root"); +} diff --git a/scripts/server/modes.cs b/scripts/server/modes.cs new file mode 100644 index 0000000..a306116 --- /dev/null +++ b/scripts/server/modes.cs @@ -0,0 +1,497 @@ +// Handles the duplicator state machine. Does not validate transitions! +// ------------------------------------------------------------------- + +//Base class for all duplicator modes +/////////////////////////////////////////////////////////////////////////// + +function NewDuplicatorMode::onStartMode(%this, %client, %lastMode){} +function NewDuplicatorMode::onChangeMode(%this, %client, %nextMode){} +function NewDuplicatorMode::onKillMode(%this, %client){} + +function NewDuplicatorMode::onSelectObject(%this, %client, %obj, %pos, %normal){} + +function NewDuplicatorMode::onLight(%this, %client){} +function NewDuplicatorMode::onNextSeat(%this, %client){} +function NewDuplicatorMode::onPrevSeat(%this, %client){} +function NewDuplicatorMode::onShiftBrick(%this, %client, %x, %y, %z){} +function NewDuplicatorMode::onSuperShiftBrick(%this, %client, %x, %y, %z){} +function NewDuplicatorMode::onRotateBrick(%this, %client, %direction){} +function NewDuplicatorMode::onPlantBrick(%this, %client){} +function NewDuplicatorMode::onCancelBrick(%this, %client){} + +function NewDuplicatorMode::onCopy(%this, %client) +{ + messageClient(%client, '', "\c6Copy can not be used in your current duplicator mode."); +} + +function NewDuplicatorMode::onPaste(%this, %client) +{ + messageClient(%client, '', "\c6Paste can not be used in your current duplicator mode."); +} + +function NewDuplicatorMode::onCut(%this, %client) +{ + messageClient(%client, '', "\c6Cut can not be used in your current duplicator mode."); +} + +function NewDuplicatorMode::getBottomPrint(%this, %client){} + + + +//Registering duplicator modes +/////////////////////////////////////////////////////////////////////////// + +//Possible mode indices +$NDM::Disabled = 0; +$NDM::BoxSelect = 1; +$NDM::BoxSelectProgress = 2; +$NDM::CutProgress = 3; +$NDM::FillColor = 4; +$NDM::FillColorProgress = 5; +$NDM::StackSelect = 6; +$NDM::StackSelectProgress = 7; +$NDM::PlantCopy = 8; +$NDM::PlantCopyProgress = 9; +$NDM::WrenchProgress = 10; +$NDM::SaveProgress = 11; +$NDM::LoadProgress = 12; +$NDM::SuperCutProgress = 13; + +//Create all the pseudo-classes to handle callbacks +function ndRegisterDuplicatorModes() +{ + echo("ND: Registering duplicator modes"); + + //Disabled duplicator mode (does nothing) + ND_ServerGroup.add( + new ScriptObject(NDM_Disabled) + { + class = "NewDuplicatorMode"; + index = $NDM::Disabled; + image = "ND_Image"; + spin = false; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Box Select duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_BoxSelect) + { + class = "NewDuplicatorMode"; + index = $NDM::BoxSelect; + image = "ND_Image_Box"; + spin = false; + + allowSelecting = true; + allowUnMount = false; + } + ); + + //Box Select Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_BoxSelectProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::BoxSelectProgress; + image = "ND_Image_Box"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Cut Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_CutProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::CutProgress; + image = "any"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Fill Color duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_FillColor) + { + class = "NewDuplicatorMode"; + index = $NDM::FillColor; + image = "any"; + spin = false; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Fill Color Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_FillColorProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::FillColorProgress; + image = "any"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Plant Copy duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_PlantCopy) + { + class = "NewDuplicatorMode"; + index = $NDM::PlantCopy; + image = "ND_Image_Blue"; + spin = false; + + allowSelecting = true; + allowUnMount = true; + } + ); + + //Plant Copy Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_PlantCopyProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::PlantCopyProgress; + image = "ND_Image_Blue"; + spin = true; + + allowSelecting = false; + allowUnMount = true; + } + ); + + //Stack Select duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_StackSelect) + { + class = "NewDuplicatorMode"; + index = $NDM::StackSelect; + image = "ND_Image"; + spin = false; + + allowSelecting = true; + allowUnMount = false; + } + ); + + //Stack Select Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_StackSelectProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::StackSelectProgress; + image = "ND_Image"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Wrench Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_WrenchProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::WrenchProgress; + image = "any"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Save Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_SaveProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::SaveProgress; + image = "any"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Load Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_LoadProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::LoadProgress; + image = "any"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //Supercut Progress duplicator mode + ND_ServerGroup.add( + new ScriptObject(NDM_SuperCutProgress) + { + class = "NewDuplicatorMode"; + index = $NDM::SuperCutProgress; + image = "ND_Image_Box"; + spin = true; + + allowSelecting = false; + allowUnMount = false; + } + ); + + //If clients already exist, reset their modes + for(%i = 0; %i < ClientGroup.getCount(); %i++) + { + %cl = ClientGroup.getObject(%i); + + %cl.ndPivot = true; + %cl.ndLimited = true; + %cl.ndDirection = true; + %cl.ndForcePlant = false; + + %cl.ndImage = ND_Image.getId(); + %cl.ndMode = NDM_Disabled; + %cl.ndModeIndex = $NDM::Disabled; + %cl.ndLastSelectMode = NDM_StackSelect; + } +} + + + +//Switching modes +/////////////////////////////////////////////////////////////////////////// + +//Change duplication mode +function GameConnection::ndSetMode(%this, %newMode) +{ + %oldMode = %this.ndMode; + + if(%oldMode.index == %newMode.index) + return; + + %this.ndMode = %newMode; + %this.ndModeIndex = %newMode.index; + + %oldMode.onChangeMode(%this, %newMode.index); + %newMode.onStartMode(%this, %oldMode.index); + + //Enable keybinds + if(!%oldMode.index) + { + commandToClient(%this, 'ndEnableKeybinds', true); + %client.ndMultiselect = false; + } + + //Change image + if(%newMode.image !$= "any") + %this.ndSetImage(nameToId(%newMode.image)); + + //Start or stop spinning + %this.player.setImageLoaded(0, !%newMode.spin); +} + +//Kill duplication mode +function GameConnection::ndKillMode(%this) +{ + if(!%this.ndModeIndex) + return; + + %this.ndMode.onKillMode(%this); + + %this.ndMode = NDM_Disabled; + %this.ndModeIndex = $NDM::Disabled; + + %this.ndUpdateBottomPrint(); + + //Disable keybinds + commandToClient(%this, 'ndEnableKeybinds', false); +} + + + +//Bottomprints +/////////////////////////////////////////////////////////////////////////// + +//Update the bottomprint +function GameConnection::ndUpdateBottomPrint(%this) +{ + if(%this.ndModeIndex) + commandToClient(%this, 'bottomPrint', %this.ndMode.getBottomPrint(%this), 0, true); + else + commandToClient(%this, 'clearBottomPrint'); +} + +//Format bottomprint message with left and right justified text +function ndFormatMessage(%title, %l0, %r0, %l1, %r1, %l2, %r2) +{ + %message = ""; + + //Last used alignment, false = left | true = right + %align = false; + + if(strStr("\c0\c1\c2\c3\c4\c5\c6\c7\c8\c9", getSubStr(%title, 0, 1)) < 0) + %message = %message @ "\c6"; + + %message = %message @ %title @ "\n"; + + for(%i = 0; strLen(%l[%i]) || strLen(%r[%i]); %i++) + { + if(strLen(%l[%i])) + { + if(%align) + %message = %message @ ""; + + if(strStr("\c0\c1\c2\c3\c4\c5\c6\c7\c8\c9", getSubStr(%l[%i], 0, 1)) < 0) + %message = %message @ "\c6"; + + %message = %message @ %l[%i]; + %align = false; + } + + if(strLen(%r[%i])) + { + if(!%align) + %message = %message @ ""; + + if(strStr("\c0\c1\c2\c3\c4\c5\c6\c7\c8\c9", getSubStr(%r[%i], 0, 1)) < 0) + %message = %message @ "\c6"; + + %message = %message @ %r[%i] @ " "; + %align = true; + } + + %message = %message @ "\n"; + } + + return %message @ " "; +} + + + +//Connecting, disconnecting, death +/////////////////////////////////////////////////////////////////////////// + +package NewDuplicator_Server +{ + //Set initial variables on join + function GameConnection::onClientEnterGame(%this) + { + %this.ndPivot = true; + %this.ndLimited = true; + %this.ndDirection = true; + %this.ndForcePlant = false; + + %this.ndImage = ND_Image.getId(); + %this.ndMode = NDM_Disabled; + %this.ndModeIndex = $NDM::Disabled; + %this.ndLastSelectMode = NDM_StackSelect; + + parent::onClientEnterGame(%this); + } + + //Kill duplicator mode when a client leaves + function GameConnection::onClientLeaveGame(%this) + { + if(%this.ndModeIndex) + %this.ndKillMode(%this); + + %this.ndEquipped = false; + + //Remove from client lists of selections + for(%i = 0; %i < ND_ServerGroup.getCount(); %i++) + { + %obj = ND_ServerGroup.getObject(%i); + + if(%obj.getName() $= "ND_Selection") + { + for(%j = 0; %j < %obj.numClients; %j++) + { + if($NS[%obj, "CL", %j] == %this.getId()) + { + for(%k = %j; %k < (%obj.numClients - 1); %k++) + $NS[%obj, "CL", %k] = $NS[%obj, "CL", %k + 1]; + + %obj.numClients--; + break; + } + } + } + } + + //Delete undo groups + deleteVariables("$NU" @ %this @ "_*"); + + %stack = %this.undoStack; + %max = %stack.head; + + if(%max < %stack.tail) + %max += %stack.size; + + for(%i = %stack.tail; %i < %max; %i++) + { + %val = %stack.val[%i % %stack.size]; + + if(getFieldCount(%val) == 2) + { + %str = getField(%val, 1); + + if( + %str $= "ND_PLANT" + || %str $= "ND_PAINT" + || %str $= "ND_WRENCH" + ){ + %group = getField(%val, 0); + + if(isObject(%group)) + { + %group.brickCount = 0; + %group.delete(); + } + } + } + } + + parent::onClientLeaveGame(%this); + } + + //Kill duplicator mode when a player dies + function GameConnection::onDeath(%this, %a, %b, %c, %d) + { + if(%this.ndModeIndex) + %this.ndKillMode(%this); + + %this.ndEquipped = false; + + parent::onDeath(%this, %a, %b, %c, %d); + } + + //Kill duplicator mode when a player is force respawned + function GameConnection::spawnPlayer(%this) + { + if(%this.ndModeIndex) + %this.ndKillMode(%this); + + %this.ndEquipped = false; + + parent::spawnPlayer(%this); + } +}; diff --git a/scripts/server/namedtargets.cs b/scripts/server/namedtargets.cs new file mode 100644 index 0000000..79f0cfc --- /dev/null +++ b/scripts/server/namedtargets.cs @@ -0,0 +1,90 @@ +// Improved versions of setNTObjectName and clearNTObjectName with much +// higher performance. Required to fix lag when clearing named bricks. +// ------------------------------------------------------------------- + +function SimObject::setNTObjectName(%this, %name) +{ + %this = %this.getId(); + %name = getSafeVariableName(trim(%name)); + + if(%name $= "") + { + %this.clearNTObjectName(); + %this.setName(""); + return; + } + + //Names must start with a _ to prevent overwriting real objects + if(getSubStr(%name, 0, 1) !$= "_") + %name = "_" @ %name; + + if(%this.getName() $= %name) + return; + + if(isObject(%name) && !(%name.getType() & $TypeMasks::FxBrickAlwaysObjectType)) + { + error("ERROR: SimObject::setNTObjectName() - Non-Brick object named \"" @ %name @ "\" already exists!"); + return; + } + + %this.clearNTObjectName(); + + %group = %this.getGroup(); + %count = %group.NTObjectCount[%name] | 0; + + if(!%count) + %group.addNTName(%name); + + //Add a reverse lookup to remove the name much faster + %group.NTObject[%name, %count] = %this; + %group.NTObjectIndex[%name, %this] = %count; + %group.NTObjectCount[%name] = %count + 1; + + %this.setName(%name); +} + +function SimObject::clearNTObjectName(%this) +{ + %this = %this.getId(); + %group = %this.getGroup(); + + if(!isObject(%group)) + return; + + %oldName = %this.getName(); + + if(%oldName $= "") + return; + + %index = %group.NTObjectIndex[%oldName, %this]; + %count = %group.NTObjectCount[%oldName]; + + if(%group.NTObject[%oldName, %index] == %this) + { + //Reverse lookup works, use fast version + %lastObj = %group.NTObject[%oldName, %count - 1]; + %group.NTObject[%oldName, %index] = %lastObj; + %group.NTObject[%oldName, %count - 1] = ""; + %group.NTObjectIndex[%oldName, %lastObj] = %index; + %group.NTObjectIndex[%oldName, %this] = ""; + %group.NTObjectCount[%oldName]--; + } + else + { + //Reverse lookup failed, use old and slow version + for(%i = 0; %i < %count; %i++) + { + if(%group.NTObject[%oldName, %i] == %this) + { + %group.NTObject[%oldName, %i] = %group.NTObject[%oldName, %count - 1]; + %group.NTObject[%oldName, %count - 1] = ""; + %group.NTObjectCount[%oldName]--; + break; + } + } + } + + if(!%group.NTObjectCount[%oldName]) + %group.removeNTName(%oldName); +} + diff --git a/scripts/server/prefs.cs b/scripts/server/prefs.cs new file mode 100644 index 0000000..eadb0e2 --- /dev/null +++ b/scripts/server/prefs.cs @@ -0,0 +1,212 @@ +// Detects common services like RTB and registers perferences to them. +// ------------------------------------------------------------------- + +function ndRegisterPrefs() +{ + //Glass prefs also set this variable so we don't need to add them seperately + if($RTB::Hooks::ServerControl) + ndRegisterPrefsToRtb(); + else + ndExtendDefaultPrefValues(); + + ndDeleteOutdatedPrefs(); +} + +function ndRegisterPrefsToRtb() +{ + echo("ND: Registering RTB prefs"); + %trustDropDown = "list None 0 Build 1 Full 2 Self 3"; + + //Limits + RTB_registerPref("Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::AdminOnly", "bool", "Tool_NewDuplicator", false, false, false, ""); + RTB_registerPref("Fill Paint Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::PaintAdminOnly", "bool", "Tool_NewDuplicator", false, false, false, ""); + RTB_registerPref("Fill Paint Fx Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::PaintFxAdminOnly", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_registerPref("Fill Wrench Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::WrenchAdminOnly", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_registerPref("Floating Bricks Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::FloatAdminOnly", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_registerPref("Save Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::SaveAdminOnly", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_registerPref("Load Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::LoadAdminOnly", "bool", "Tool_NewDuplicator", false, false, false, ""); + RTB_registerPref("Fill Bricks Admin Only", "New Duplicator | Limits", "$Pref::Server::ND::FillBricksAdminOnly", "bool", "Tool_NewDuplicator", true, false, false, ""); + + //Settings + RTB_RegisterPref("Trust Limit", "New Duplicator | Settings", "$Pref::Server::ND::TrustLimit", %trustDropDown, "Tool_NewDuplicator", 2, false, false, ""); + RTB_RegisterPref("Admin Trust Bypass (Select)", "New Duplicator | Settings", "$Pref::Server::ND::AdminTrustBypass1", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_RegisterPref("Admin Trust Bypass (Edit)", "New Duplicator | Settings", "$Pref::Server::ND::AdminTrustBypass2", "bool", "Tool_NewDuplicator", false, false, false, ""); + RTB_RegisterPref("Select Public Bricks", "New Duplicator | Settings", "$Pref::Server::ND::SelectPublicBricks", "bool", "Tool_NewDuplicator", true, false, false, ""); + + RTB_registerPref("Max Bricks (Admin)", "New Duplicator | Settings", "$Pref::Server::ND::MaxBricksAdmin", "int 1000 1000000", "Tool_NewDuplicator", 1000000, false, false, ""); + RTB_registerPref("Max Bricks (Player)", "New Duplicator | Settings", "$Pref::Server::ND::MaxBricksPlayer", "int 1000 1000000", "Tool_NewDuplicator", 50000, false, false, ""); + RTB_registerPref("Max Box Size (Admin)", "New Duplicator | Settings", "$Pref::Server::ND::MaxBoxSizeAdmin", "int 1 50000", "Tool_NewDuplicator", 1024, false, false, ""); + RTB_registerPref("Max Box Size (Player)", "New Duplicator | Settings", "$Pref::Server::ND::MaxBoxSizePlayer", "int 1 50000", "Tool_NewDuplicator", 64, false, false, ""); + + RTB_registerPref("Selecting Timeout (Player)", "New Duplicator | Settings", "$Pref::Server::ND::SelectTimeoutMS", "int 0 5000", "Tool_NewDuplicator", 400, false, false, ""); + RTB_registerPref("Planting Timeout (Player)", "New Duplicator | Settings", "$Pref::Server::ND::PlantTimeoutMS", "int 0 5000", "Tool_NewDuplicator", 400, false, false, ""); + + //Advanced + RTB_registerPref("Enable Menu Sounds", "New Duplicator | Advanced", "$Pref::Server::ND::PlayMenuSounds", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_registerPref("Max Ghost Bricks", "New Duplicator | Advanced", "$Pref::Server::ND::MaxGhostBricks", "int 1 50000", "Tool_NewDuplicator", 1500, false, false, ""); + RTB_registerPref("Instant Ghost Bricks", "New Duplicator | Advanced", "$Pref::Server::ND::InstantGhostBricks", "int 1 50000", "Tool_NewDuplicator", 150, false, false, ""); + RTB_registerPref("Scatter Ghost Bricks", "New Duplicator | Advanced", "$Pref::Server::ND::ScatterGhostBricks", "bool", "Tool_NewDuplicator", true, false, false, ""); + RTB_registerPref("Process Bricks per Tick", "New Duplicator | Advanced", "$Pref::Server::ND::ProcessPerTick", "int 1 50000", "Tool_NewDuplicator", 300, false, false, ""); + RTB_registerPref("Box Selection Chunk Size", "New Duplicator | Advanced", "$Pref::Server::ND::BoxSelectChunkDim", "int 1 50000", "Tool_NewDuplicator", 6, false, false, ""); + RTB_registerPref("Create Sym Table on Start", "New Duplicator | Advanced", "$Pref::Server::ND::SymTableOnStart", "bool", "Tool_NewDuplicator", false, false, false, ""); + + //Restore default prefs + RTB_registerPref("Check to restore defaults", "New Duplicator | Reset Prefs", "$ND::RestoreDefaultPrefs", "bool", "Tool_NewDuplicator", false, false, false, "ndRestoreDefaultPrefs"); +} + +//Callback function for "Reset Prefs" +function ndRestoreDefaultPrefs() +{ + if($ND::RestoreDefaultPrefs) + ndApplyDefaultPrefValues(); +} + +function ndExtendDefaultPrefValues() +{ + echo("ND: Extending default pref values"); + + //Limits + if($Pref::Server::ND::AdminOnly $= "") $Pref::Server::ND::AdminOnly = false; + if($Pref::Server::ND::PaintAdminOnly $= "") $Pref::Server::ND::PaintAdminOnly = false; + if($Pref::Server::ND::PaintFxAdminOnly $= "") $Pref::Server::ND::PaintFxAdminOnly = true; + if($Pref::Server::ND::WrenchAdminOnly $= "") $Pref::Server::ND::WrenchAdminOnly = true; + if($Pref::Server::ND::FloatAdminOnly $= "") $Pref::Server::ND::FloatAdminOnly = true; + if($Pref::Server::ND::SaveAdminOnly $= "") $Pref::Server::ND::SaveAdminOnly = true; + if($Pref::Server::ND::LoadAdminOnly $= "") $Pref::Server::ND::LoadAdminOnly = false; + if($Pref::Server::ND::FillBricksAdminOnly $= "") $Pref::Server::ND::FillBricksAdminOnly = true; + + //Settings + if($Pref::Server::ND::TrustLimit $= "") $Pref::Server::ND::TrustLimit = 2; + if($Pref::Server::ND::AdminTrustBypass1 $= "") $Pref::Server::ND::AdminTrustBypass1 = true; + if($Pref::Server::ND::AdminTrustBypass2 $= "") $Pref::Server::ND::AdminTrustBypass2 = false; + if($Pref::Server::ND::SelectPublicBricks $= "") $Pref::Server::ND::SelectPublicBricks = true; + + if($Pref::Server::ND::MaxBricksAdmin $= "") $Pref::Server::ND::MaxBricksAdmin = 1000000; + if($Pref::Server::ND::MaxBricksPlayer $= "") $Pref::Server::ND::MaxBricksPlayer = 10000; + if($Pref::Server::ND::MaxBoxSizeAdmin $= "") $Pref::Server::ND::MaxBoxSizeAdmin = 1024; + if($Pref::Server::ND::MaxBoxSizePlayer $= "") $Pref::Server::ND::MaxBoxSizePlayer = 64; + + if($Pref::Server::ND::SelectTimeoutMS $= "") $Pref::Server::ND::SelectTimeoutMS = 400; + if($Pref::Server::ND::PlantTimeoutMS $= "") $Pref::Server::ND::PlantTimeoutMS = 400; + + //Advanced + if($Pref::Server::ND::PlayMenuSounds $= "") $Pref::Server::ND::PlayMenuSounds = true; + if($Pref::Server::ND::MaxGhostBricks $= "") $Pref::Server::ND::MaxGhostBricks = 1500; + if($Pref::Server::ND::InstantGhostBricks $= "") $Pref::Server::ND::InstantGhostBricks = 150; + if($Pref::Server::ND::ScatterGhostBricks $= "") $Pref::Server::ND::ScatterGhostBricks = true; + if($Pref::Server::ND::ProcessPerTick $= "") $Pref::Server::ND::ProcessPerTick = 300; + if($Pref::Server::ND::BoxSelectChunkDim $= "") $Pref::Server::ND::BoxSelectChunkDim = 6; + if($Pref::Server::ND::SymTableOnStart $= "") $Pref::Server::ND::SymTableOnStart = false; + + //Always set this to false so we don't accidently reset the prefs + $ND::RestoreDefaultPrefs = false; +} + +function ndApplyDefaultPrefValues() +{ + echo("ND: Applying default pref values"); + messageAll('', "\c6(\c3New Duplicator\c6) \c6Prefs reset to default values."); + + //Limits + $Pref::Server::ND::AdminOnly = false; + $Pref::Server::ND::PaintAdminOnly = false; + $Pref::Server::ND::PaintFxAdminOnly = true; + $Pref::Server::ND::WrenchAdminOnly = true; + $Pref::Server::ND::FloatAdminOnly = true; + $Pref::Server::ND::SaveAdminOnly = true; + $Pref::Server::ND::LoadAdminOnly = false; + $Pref::Server::ND::FillBricksAdminOnly = true; + + //Settings + $Pref::Server::ND::TrustLimit = 2; + $Pref::Server::ND::AdminTrustBypass1 = true; + $Pref::Server::ND::AdminTrustBypass2 = false; + $Pref::Server::ND::SelectPublicBricks = true; + + $Pref::Server::ND::MaxBricksAdmin = 1000000; + $Pref::Server::ND::MaxBricksPlayer = 10000; + $Pref::Server::ND::MaxBoxSizeAdmin = 1024; + $Pref::Server::ND::MaxBoxSizePlayer = 64; + + $Pref::Server::ND::SelectTimeoutMS = 400; + $Pref::Server::ND::PlantTimeoutMS = 400; + + //Advanced + $Pref::Server::ND::PlayMenuSounds = true; + $Pref::Server::ND::MaxGhostBricks = 1500; + $Pref::Server::ND::InstantGhostBricks = 150; + $Pref::Server::ND::ScatterGhostBricks = true; + $Pref::Server::ND::ProcessPerTick = 300; + $Pref::Server::ND::BoxSelectChunkDim = 6; + $Pref::Server::ND::SymTableOnStart = false; + + //Always set this to false so we don't accidently reset the prefs + $ND::RestoreDefaultPrefs = false; +} + +//Erases outdated prefs from the config file +function ndDeleteOutdatedPrefs() +{ + //Step 1: Copy all current prefs + //Limits + %adminOnly = $Pref::Server::ND::AdminOnly; + %paintAdminOnly = $Pref::Server::ND::PaintAdminOnly; + %paintFxAdminOnly = $Pref::Server::ND::PaintFxAdminOnly; + %wrenchAdminOnly = $Pref::Server::ND::WrenchAdminOnly; + %floatAdminOnly = $Pref::Server::ND::FloatAdminOnly; + %saveAdminOnly = $Pref::Server::ND::SaveAdminOnly; + %loadAdminOnly = $Pref::Server::ND::LoadAdminOnly; + %fillBricksAdminOnly = $Pref::Server::ND::FillBricksAdminOnly; + //Settings + %trustLimit = $Pref::Server::ND::TrustLimit; + %adminTrustBypass1 = $Pref::Server::ND::AdminTrustBypass1; + %adminTrustBypass2 = $Pref::Server::ND::AdminTrustBypass2; + %selectPublicBricks = $Pref::Server::ND::SelectPublicBricks; + %maxBricksAdmin = $Pref::Server::ND::MaxBricksAdmin; + %maxBricksPlayer = $Pref::Server::ND::MaxBricksPlayer; + %maxBoxSizeAdmin = $Pref::Server::ND::MaxBoxSizeAdmin; + %maxBoxSizePlayer = $Pref::Server::ND::MaxBoxSizePlayer; + %selectTimeoutMS = $Pref::Server::ND::SelectTimeoutMS; + %plantTimeoutMS = $Pref::Server::ND::PlantTimeoutMS; + //Advanced + %playMenuSounds = $Pref::Server::ND::PlayMenuSounds; + %maxGhostBricks = $Pref::Server::ND::MaxGhostBricks; + %instantGhostBricks = $Pref::Server::ND::InstantGhostBricks; + %scatterGhostBricks = $Pref::Server::ND::ScatterGhostBricks; + %processPerTick = $Pref::Server::ND::ProcessPerTick; + %boxSelectChunkDim = $Pref::Server::ND::BoxSelectChunkDim; + %symTableOnStart = $Pref::Server::ND::SymTableOnStart; + + //Step 2: Delete everything + deleteVariables("$Pref::Server::ND::*"); + + //Step 3: Set current prefs again + //Limits + $Pref::Server::ND::AdminOnly = %adminOnly; + $Pref::Server::ND::PaintAdminOnly = %paintAdminOnly; + $Pref::Server::ND::PaintFxAdminOnly = %paintFxAdminOnly; + $Pref::Server::ND::WrenchAdminOnly = %wrenchAdminOnly; + $Pref::Server::ND::FloatAdminOnly = %floatAdminOnly; + $Pref::Server::ND::SaveAdminOnly = %saveAdminOnly; + $Pref::Server::ND::LoadAdminOnly = %loadAdminOnly; + $Pref::Server::ND::FillBricksAdminOnly = %fillBricksAdminOnl; + //Settings + $Pref::Server::ND::TrustLimit = %trustLimit; + $Pref::Server::ND::AdminTrustBypass1 = %adminTrustBypass1; + $Pref::Server::ND::AdminTrustBypass2 = %adminTrustBypass2; + $Pref::Server::ND::SelectPublicBricks = %selectPublicBricks; + $Pref::Server::ND::MaxBricksAdmin = %maxBricksAdmin; + $Pref::Server::ND::MaxBricksPlayer = %maxBricksPlayer; + $Pref::Server::ND::MaxBoxSizeAdmin = %maxBoxSizeAdmin; + $Pref::Server::ND::MaxBoxSizePlayer = %maxBoxSizePlayer; + $Pref::Server::ND::SelectTimeoutMS = %selectTimeoutMS; + $Pref::Server::ND::PlantTimeoutMS = %plantTimeoutMS; + //Advanced + $Pref::Server::ND::PlayMenuSounds = %playMenuSounds; + $Pref::Server::ND::MaxGhostBricks = %maxGhostBricks; + $Pref::Server::ND::InstantGhostBricks = %instantGhostBricks; + $Pref::Server::ND::ScatterGhostBricks = %scatterGhostBricks; + $Pref::Server::ND::ProcessPerTick = %processPerTick; + $Pref::Server::ND::BoxSelectChunkDim = %boxSelectChunkDim; + $Pref::Server::ND::SymTableOnStart = %symTableOnStart; +} diff --git a/scripts/server/symmetrydefinitions.cs b/scripts/server/symmetrydefinitions.cs new file mode 100644 index 0000000..9b19e8d --- /dev/null +++ b/scripts/server/symmetrydefinitions.cs @@ -0,0 +1,180 @@ +// Manually sets up symmetry planes for certain bricks with bad geometry. +// ------------------------------------------------------------------- + +//Manual symmetry can be set using the following variables: +// $ND::ManualSymmetry[UIName] = {0 - 5} +// $ND::ManualSymmetryDB[UIName] = Other UIName +// $ND::ManualSymmetryOffset[UIName] = {0 - 3} + +// $ND::ManualSymmetryZ[UIName] = {true, false} +// $ND::ManualSymmetryZDB[UIName] = Other UIName +// $ND::ManualSymmetryZOffset[UIName] = {0 - 3} + +//Built-in Bricks +$ND::ManualSymmetryZ["1x1 Round"] = true; +$ND::ManualSymmetryZ["1x1F Round"] = true; +$ND::ManualSymmetryZ["Castle Wall"] = true; +$ND::ManualSymmetryZ["1x4x5 Window"] = true; + +//Brick_V15 +$ND::ManualSymmetry["1x4x2 Bars"] = 1; +$ND::ManualSymmetryZ["1x4x2 Bars"] = true; + +//Brick_Treasure_Chest +$ND::ManualSymmetry["Treasure Chest"] = 2; + +//Brick_Teledoor +$ND::ManualSymmetryZ["Teledoor"] = 1; + +//Brick_Halloween +$ND::ManualSymmetry["Skull Cool Open"] = 2; +$ND::ManualSymmetry["Skull Cool"] = 2; + +$ND::ManualSymmetry["Pumpkin"] = 3; +$ND::ManualSymmetry["Pumpkin_Face"] = 3; +$ND::ManualSymmetry["Pumpkin_Scared"] = 3; +$ND::ManualSymmetry["Pumpkin_Ascii"] = 3; + +//Brick_PoleAdapters +$ND::ManualSymmetry["1x1x3 Pole"] = 1; +$ND::ManualSymmetry["1x1 Pole"] = 1; +$ND::ManualSymmetry["1x1F Pole"] = 1; + +$ND::ManualSymmetry["1x1F Pole Plus"] = 2; +$ND::ManualSymmetry["1x1F Pole Corner"] = 5; +$ND::ManualSymmetry["1x1F Pole Corner up"] = 2; +$ND::ManualSymmetry["1x1F Pole Corner down"] = 2; +$ND::ManualSymmetry["1x1F Pole T"] = 5; +$ND::ManualSymmetry["1x1F Pole T up"] = 2; +$ND::ManualSymmetry["1x1F Pole T down"] = 2; +$ND::ManualSymmetry["1x1F Pole X Vert"] = 2; +$ND::ManualSymmetry["1x1F Pole X"] = 1; + +$ND::ManualSymmetryZ["1x1F Pole Plus"] = true; +$ND::ManualSymmetryZ["1x1F Pole Corner"] = true; +$ND::ManualSymmetryZ["1x1F Pole Corner up"] = false; +$ND::ManualSymmetryZ["1x1F Pole Corner down"] = false; +$ND::ManualSymmetryZ["1x1F Pole T"] = true; +$ND::ManualSymmetryZ["1x1F Pole T up"] = false; +$ND::ManualSymmetryZ["1x1F Pole T down"] = false; +$ND::ManualSymmetryZ["1x1F Pole X Vert"] = true; +$ND::ManualSymmetryZ["1x1F Pole X"] = true; + +$ND::ManualSymmetryZDB["1x1F Pole Corner up"] = "1x1F Pole Corner down"; +$ND::ManualSymmetryZDB["1x1F Pole Corner down"] = "1x1F Pole Corner up"; +$ND::ManualSymmetryZDB["1x1F Pole T up"] = "1x1F Pole T down"; +$ND::ManualSymmetryZDB["1x1F Pole T down"] = "1x1F Pole T up"; + +$ND::ManualSymmetryZOffset["1x1F Pole Corner up"] = 0; +$ND::ManualSymmetryZOffset["1x1F Pole Corner down"] = 0; +$ND::ManualSymmetryZOffset["1x1F Pole T up"] = 0; +$ND::ManualSymmetryZOffset["1x1F Pole T down"] = 0; + +//Brick_PoleDiagonals +$ND::ManualSymmetryZ["1x1f Horiz. Diag."] = true; +$ND::ManualSymmetryZ["2x2f Horiz. Diag."] = true; +$ND::ManualSymmetryZ["3x3f Horiz. Diag."] = true; +$ND::ManualSymmetryZ["4x4f Horiz. Diag."] = true; +$ND::ManualSymmetryZ["5x5f Horiz. Diag."] = true; +$ND::ManualSymmetryZ["6x6f Horiz. Diag."] = true; + +$ND::ManualSymmetry["1x1 Vert. Diag. A"] = 2; +$ND::ManualSymmetry["2x2 Vert. Diag. A"] = 2; +$ND::ManualSymmetry["3x3 Vert. Diag. A"] = 2; +$ND::ManualSymmetry["4x4 Vert. Diag. A"] = 2; +$ND::ManualSymmetry["5x5 Vert. Diag. A"] = 2; +$ND::ManualSymmetry["6x6 Vert. Diag. A"] = 2; +$ND::ManualSymmetry["1x1 Vert. Diag. B"] = 2; +$ND::ManualSymmetry["2x2 Vert. Diag. B"] = 2; +$ND::ManualSymmetry["3x3 Vert. Diag. B"] = 2; +$ND::ManualSymmetry["4x4 Vert. Diag. B"] = 2; +$ND::ManualSymmetry["5x5 Vert. Diag. B"] = 2; +$ND::ManualSymmetry["6x6 Vert. Diag. B"] = 2; + +$ND::ManualSymmetryZ["1x1 Vert. Diag. A"] = false; +$ND::ManualSymmetryZ["2x2 Vert. Diag. A"] = false; +$ND::ManualSymmetryZ["3x3 Vert. Diag. A"] = false; +$ND::ManualSymmetryZ["4x4 Vert. Diag. A"] = false; +$ND::ManualSymmetryZ["5x5 Vert. Diag. A"] = false; +$ND::ManualSymmetryZ["6x6 Vert. Diag. A"] = false; +$ND::ManualSymmetryZ["1x1 Vert. Diag. B"] = false; +$ND::ManualSymmetryZ["2x2 Vert. Diag. B"] = false; +$ND::ManualSymmetryZ["3x3 Vert. Diag. B"] = false; +$ND::ManualSymmetryZ["4x4 Vert. Diag. B"] = false; +$ND::ManualSymmetryZ["5x5 Vert. Diag. B"] = false; +$ND::ManualSymmetryZ["6x6 Vert. Diag. B"] = false; + +$ND::ManualSymmetryZDB["1x1 Vert. Diag. A"] = "1x1 Vert. Diag. B"; +$ND::ManualSymmetryZDB["2x2 Vert. Diag. A"] = "2x2 Vert. Diag. B"; +$ND::ManualSymmetryZDB["3x3 Vert. Diag. A"] = "3x3 Vert. Diag. B"; +$ND::ManualSymmetryZDB["4x4 Vert. Diag. A"] = "4x4 Vert. Diag. B"; +$ND::ManualSymmetryZDB["5x5 Vert. Diag. A"] = "5x5 Vert. Diag. B"; +$ND::ManualSymmetryZDB["6x6 Vert. Diag. A"] = "6x6 Vert. Diag. B"; +$ND::ManualSymmetryZDB["1x1 Vert. Diag. B"] = "1x1 Vert. Diag. A"; +$ND::ManualSymmetryZDB["2x2 Vert. Diag. B"] = "2x2 Vert. Diag. A"; +$ND::ManualSymmetryZDB["3x3 Vert. Diag. B"] = "3x3 Vert. Diag. A"; +$ND::ManualSymmetryZDB["4x4 Vert. Diag. B"] = "4x4 Vert. Diag. A"; +$ND::ManualSymmetryZDB["5x5 Vert. Diag. B"] = "5x5 Vert. Diag. A"; +$ND::ManualSymmetryZDB["6x6 Vert. Diag. B"] = "6x6 Vert. Diag. A"; + +$ND::ManualSymmetryZOffset["1x1 Vert. Diag. A"] = 2; +$ND::ManualSymmetryZOffset["2x2 Vert. Diag. A"] = 2; +$ND::ManualSymmetryZOffset["3x3 Vert. Diag. A"] = 2; +$ND::ManualSymmetryZOffset["4x4 Vert. Diag. A"] = 2; +$ND::ManualSymmetryZOffset["5x5 Vert. Diag. A"] = 2; +$ND::ManualSymmetryZOffset["6x6 Vert. Diag. A"] = 2; +$ND::ManualSymmetryZOffset["1x1 Vert. Diag. B"] = 2; +$ND::ManualSymmetryZOffset["2x2 Vert. Diag. B"] = 2; +$ND::ManualSymmetryZOffset["3x3 Vert. Diag. B"] = 2; +$ND::ManualSymmetryZOffset["4x4 Vert. Diag. B"] = 2; +$ND::ManualSymmetryZOffset["5x5 Vert. Diag. B"] = 2; +$ND::ManualSymmetryZOffset["6x6 Vert. Diag. B"] = 2; + +//Brick_GrillPlate +$ND::ManualSymmetry["Grill Corner"] = 4; + +//Brick_Bevel +$ND::ManualSymmetry["1x1F Beveled"] = 1; +$ND::ManualSymmetry["1x1FF Beveled"] = 1; +$ND::ManualSymmetry["1x2F Beveled"] = 1; +$ND::ManualSymmetry["1x2FF Beveled"] = 1; +$ND::ManualSymmetry["1x1 Beveled"] = 1; +$ND::ManualSymmetry["1x1x2 Beveled"] = 1; +$ND::ManualSymmetry["1x2 Beveled"] = 1; +$ND::ManualSymmetry["2x2F Beveled"] = 1; +$ND::ManualSymmetry["2x4F Beveled"] = 1; +$ND::ManualSymmetry["2x4FF Beveled"] = 1; + +$ND::ManualSymmetryZ["1x1F Beveled"] = true; +$ND::ManualSymmetryZ["1x1FF Beveled"] = true; +$ND::ManualSymmetryZ["1x2F Beveled"] = true; +$ND::ManualSymmetryZ["1x2FF Beveled"] = true; +$ND::ManualSymmetryZ["1x1 Beveled"] = true; +$ND::ManualSymmetryZ["1x1x2 Beveled"] = true; +$ND::ManualSymmetryZ["1x2 Beveled"] = true; +$ND::ManualSymmetryZ["2x2F Beveled"] = true; +$ND::ManualSymmetryZ["2x4F Beveled"] = true; +$ND::ManualSymmetryZ["2x4FF Beveled"] = true; + +//Brick_1RandomPack +$ND::ManualSymmetry["2x2x2 Octo Elbow Horz"] = 4; + +$ND::ManualSymmetryZ["1x1 Octo"] = true; +$ND::ManualSymmetryZ["1x1x2 Octo"] = true; +$ND::ManualSymmetryZ["2x2x2 Octo T Horz"] = true; +$ND::ManualSymmetryZ["2x2x2 Octo Elbow Horz"] = true; + +$ND::ManualSymmetryZ["2x3x2 Octo Offset"] = false; +$ND::ManualSymmetryZDB["2x3x2 Octo Offset"] = "2x3x2 Octo Offset"; +$ND::ManualSymmetryZOffset["2x3x2 Octo Offset"] = 2; + +//Brick_Fence +$ND::ManualSymmetry["1x4 Fence"] = 3; + +//Brick_SmallBricks +$ND::ManualSymmetry["0.25x0.25 Corner"] = 4; +$ND::ManualSymmetry["0.25x0.25F Corner"] = 4; +$ND::ManualSymmetry["0.5x0.5 Corner"] = 4; +$ND::ManualSymmetry["0.5x0.5F Corner"] = 4; +$ND::ManualSymmetry["0.75x0.75 Corner"] = 4; +$ND::ManualSymmetry["0.75x0.75F Corner"] = 4; diff --git a/scripts/server/symmetrytable.cs b/scripts/server/symmetrytable.cs new file mode 100644 index 0000000..1a60dcc --- /dev/null +++ b/scripts/server/symmetrytable.cs @@ -0,0 +1,692 @@ +// Analyzes brick geometry to detect common symmetry planes. +// ------------------------------------------------------------------- + +//Begin generating the symmetry table +function ndCreateSymmetryTable() +{ + if($ND::SymmetryTableCreating) + return; + + //Tell everyone what is happening + messageAll('', "\c6(\c3New Duplicator\c6) \c6Creating brick symmetry table..."); + + //Make sure we have the uiname table for manual symmetry + if(!$UINameTableCreated) + createUINameTable(); + + //Delete previous data + deleteVariables("$ND::Symmetry*"); + + $ND::SymmetryTableCreated = false; + $ND::SymmetryTableCreating = true; + $ND::SymmetryTableStarted = getRealTime(); + + $NDT::SimpleCount = 0; + $NDT::MeshCount = 0; + + $NDT::AsymXCountTotal = 0; + $NDT::AsymZCountTotal = 0; + + echo("ND: Start building brick symmetry table..."); + echo("=========================================================================="); + + ndTickCreateSymmetryTable(0, getDatablockGroupSize()); +} + +//Process symmetry for 6 datablocks each tick +function ndTickCreateSymmetryTable(%lastIndex, %max) +{ + %processed = 0; + %limit = $Server::Dedicated ? 400 : 200; + + for(%i = %lastIndex; %i < %max; %i++) + { + %db = getDatablock(%i); + + if(%db.getClassName() $= "FxDtsBrickData") + { + %processed += ndTestBrickSymmetry(%db); + + if(%processed > %limit) + { + schedule(30, 0, ndTickCreateSymmetryTable, %i + 1, %max); + return; + } + } + } + + %simple = $NDT::SimpleCount; + %mesh = $NDT::MeshCount; + + %asymx = $NDT::AsymXCountTotal; + %asymz = $NDT::AsymZCountTotal; + + echo("=========================================================================="); + echo("ND: Finished basic symmetry tests: " @ %simple @ " simple, " @ %mesh @ " with mesh, " @ %asymx @ " asymmetric, " @ %asymz @ " z-asymmetric"); + + ndFindSymmetricPairs(); +} + +//Attempt to find symmetric pairs between asymmetric bricks +function ndFindSymmetricPairs() +{ + echo("ND: Starting X symmetric pair search..."); + echo("=========================================================================="); + + for(%i = 0; %i < $NDT::AsymXCountTotal; %i++) + { + %index = $NDT::AsymXBrick[%i]; + + if(!$NDT::SkipAsymX[%index]) + ndFindSymmetricPairX(%index); + } + + echo("=========================================================================="); + echo("ND: Finished finding symmetric pairs"); + echo("ND: Starting Z symmetric pair search..."); + echo("=========================================================================="); + + for(%i = 0; %i < $NDT::AsymZCountTotal; %i++) + { + %index = $NDT::AsymZBrick[%i]; + + if(!$NDT::SkipAsymZ[%index]) + ndFindSymmetricPairZ(%index); + } + + //Delete temporary arrays + deleteVariables("$NDT::*"); + + echo("=========================================================================="); + echo("ND: Finished finding Z symmetric pairs"); + echo("ND: Symmetry table complete in " @ (getRealTime() - $ND::SymmetryTableStarted) / 1000 @ " seconds"); + + $ND::SymmetryTableCreated = true; + $ND::SymmetryTableCreating = false; + + //We're done! + %seconds = mFloatLength((getRealTime() - $ND::SymmetryTableStarted) / 1000, 0); + messageAll('', "\c6(\c3New Duplicator\c6) \c6Created brick symmetry table in " @ %seconds @ " seconds."); +} + +//Test symmetry of a single blb file +function ndTestBrickSymmetry(%datablock) +{ + //Open blb file + %file = new FileObject(); + %file.openForRead(%datablock.brickFile); + + //Skip brick size - irrelevant + %file.readLine(); + + //Simple bricks are always fully symmetric + if(%file.readLine() $= "BRICK") + { + $NDT::SimpleCount++; + + $ND::Symmetry[%datablock] = 1; + $ND::SymmetryZ[%datablock] = true; + + %file.close(); + %file.delete(); + return 2; + } + + //Not simple, get mesh data index in temp arrays + %dbi = $NDT::MeshCount; + $NDT::MeshCount++; + + //Load mesh from blb file + %faces = 0; + %points = 0; + + while(!%file.isEOF()) + { + //Find start of face + %line = %file.readLine(); + + if(getSubStr(%line, 0, 4) $= "TEX:") + { + %tex = trim(getSubStr(%line, 4, strLen(%line))); + + //Top and bottom faces have different topology, skip + if(%tex $= "TOP" || %tex $= "BOTTOMLOOP" || %tex $= "BOTTOMEDGE") + continue; + + //Add face + $NDT::FaceTexId[%dbi, %faces] = (%tex $= "SIDE" ? 0 : (%tex $= "RAMP" ? 1 : 2)); + + //Skip useless lines + while(trim(%file.readLine()) !$= "POSITION:") {} + + //Add the 4 points + for(%i = 0; %i < 4; %i++) + { + //Read next line + %line = %file.readLine(); + + //Skip useless blank lines + while(!strLen(%line)) + %line = %file.readLine(); + + //Remove formatting from point + %pos = vectorAdd(%line, "0 0 0"); + + //Round down two digits to fix float errors + %pos = mFloatLength(getWord(%pos, 0), 3) * 1.0 + SPC mFloatLength(getWord(%pos, 1), 3) * 1.0 + SPC mFloatLength(getWord(%pos, 2), 3) * 1.0; + + //Get index of this point + if(!%ptIndex = $NDT::PtAtPosition[%dbi, %pos]) + { + //Points array is 1-indexed so we can quickly test !PtAtPosition[...] + %points++; + %ptIndex = %points; + + //Add new point to array + $NDT::PtPosition[%dbi, %points] = %pos; + $NDT::PtAtPosition[%dbi, %pos] = %points; + } + + //Add face to point + if(!$NDT::PtInFace[%dbi, %faces, %ptIndex]) + { + //Increase first then subtract 1 to get 0 the first time + %fIndex = $NDT::FacesAtPt[%dbi, %ptIndex]++ - 1; + $NDT::FaceAtPt[%dbi, %ptIndex, %fIndex] = %faces; + } + + //Add point to face + $NDT::FacePt[%dbi, %faces, %i] = %ptIndex; + $NDT::PtInFace[%dbi, %faces, %ptIndex] = true; + } + + //Added face + %faces++; + } + } + + $NDT::FaceCount[%dbi] = %faces; + $NDT::Datablock[%dbi] = %datablock; + + %file.close(); + %file.delete(); + + //Possible symmetries: + // 0: asymmetric + // 1: x & y + // 2: x + // 3: y + // 4: x+y + // 5: x-y + + //We will test in the following order: + // X + // Y + // X+Y + // X-Y + // Z + + //Check manual symmetry first + %sym = $ND::ManualSymmetry[%datablock.uiname]; + + if(%sym !$= "") + { + if(!%sym) + { + //Try to find the other brick + %otherdb = $UINameTable[$ND::ManualSymmetryDB[%datablock.uiname]]; + %offset = $ND::ManualSymmetryOffset[%datablock.uiname]; + + //... + if(!isObject(%otherdb)) + { + %otherdb = ""; + %offset = 0; + echo("ND: " @ %datablock.uiname @ " has manual symmetry but the paired brick does not exist"); + } + + $ND::SymmetryXDatablock[%datablock] = %otherdb; + $ND::SymmetryXOffset[%datablock] = %offset; + } + + %manualSym = true; + } + else + { + %failX = ndTestSelfSymmetry(%dbi, 0); + %failY = ndTestSelfSymmetry(%dbi, 1); + + //Diagonals are only needed if the brick isn't symmetric to the axis + if(%failX && %failY) + %failXY = ndTestSelfSymmetry(%dbi, 3); + + //One diagonal is enough, only test second if first one fails + if(%failXY) + %failYX = ndTestSelfSymmetry(%dbi, 4); + + //X, Y symmetry + if(!%failX && !%failY) + %sym = 1; + else if(!%failX) + %sym = 2; + else if(!%failY) + %sym = 3; + else if(!%failXY) + %sym = 4; + else if(!%failYX) + %sym = 5; + else + %sym = 0; + } + + //Check manual symmetry first + %symZ = $ND::ManualSymmetryZ[%datablock.uiname]; + + //Z symmetry + if(%symZ !$= "") + { + if(!%symZ) + { + //Try to find the other brick + %otherdb = $UINameTable[$ND::ManualSymmetryZDB[%datablock.uiname]]; + %offset = $ND::ManualSymmetryZOffset[%datablock.uiname]; + + //... + if(!isObject(%otherdb)) + { + %otherdb = ""; + %offset = 0; + echo("ND: " @ %datablock.uiname @ " has manual Z symmetry but the paired brick does not exist"); + } + + $ND::SymmetryZDatablock[%datablock] = %otherdb; + $ND::SymmetryZOffset[%datablock] = %offset; + } + + %manualZSym = true; + } + else + %symZ = !ndTestSelfSymmetry(%dbi, 2); + + if(!%manualSym && !%sym) + { + //Add to lookup table of X-asymmetric bricks of this type + %bIndex = $NDT::AsymXCount[%faces, %symZ]++; + $NDT::AsymXBrick[%faces, %symZ, %bIndex] = %dbi; + + //Add to list of asymmetric bricks + $NDT::AsymXBrick[$NDT::AsymXCountTotal] = %dbi; + $NDT::AsymXCountTotal++; + } + + if(!%manualZSym && !%symZ) + { + //Add to lookup table of Z-asymmetric bricks of this type + %bIndex = $NDT::AsymZCount[%faces, %sym]++; + $NDT::AsymZBrick[%faces, %sym, %bIndex] = %dbi; + + //Add to list of Z-asymmetric bricks + $NDT::AsymZBrick[$NDT::AsymZCountTotal] = %dbi; + $NDT::AsymZCountTotal++; + } + + //Save symmetries + $ND::Symmetry[%datablock] = %sym; + $ND::SymmetryZ[%datablock] = %symZ; + + //Return processed faces + return %faces; +} + +//Find symmetric pair between two bricks on X axis +function ndFindSymmetricPairX(%dbi) +{ + if($NDT::SkipAsymX[%dbi]) + return; + + %datablock = $NDT::Datablock[%dbi]; + + %zsym = $ND::SymmetryZ[%datablock]; + %faces = $NDT::FaceCount[%dbi]; + %count = $NDT::AsymXCount[%faces, %zsym]; + + //Only potential match is the brick itself - fail + if(%count == 1) + { + echo("ND: No X match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")"); + return; + } + + %off = -1; + $NDT::SkipAsymX[%dbi] = true; + + for(%i = 1; %i <= %count; %i++) + { + %other = $NDT::AsymXBrick[%faces, %zsym, %i]; + + //Don't compare with bricks that already have a pair + if($NDT::SkipAsymX[%other]) + continue; + + //Test all 4 possible rotations + //Not using loop due to lack of goto command + if(!ndTestPairSymmetry(%dbi, %other, true, 0)) + { + %off = 0; + break; + } + + if(!ndTestPairSymmetry(%dbi, %other, true, 1)) + { + %off = 1; + break; + } + + if(!ndTestPairSymmetry(%dbi, %other, true, 2)) + { + %off = 2; + break; + } + + if(!ndTestPairSymmetry(%dbi, %other, true, 3)) + { + %off = 3; + break; + } + } + + if(%off != -1) + { + %otherdb = $NDT::Datablock[%other]; + + //Save symmetry + $ND::SymmetryXDatablock[%datablock] = %otherdb; + $ND::SymmetryXOffset[%datablock] = %off; + + $ND::SymmetryXDatablock[%otherdb] = %datablock; + $ND::SymmetryXOffset[%otherdb] = -%off; + + //No need to process the other brick again + $NDT::SkipAsymX[%other] = true; + } + else + echo("ND: No X match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")"); +} + +//Find symmetric pair between two bricks on Z axis +function ndFindSymmetricPairZ(%dbi) +{ + if($NDT::SkipAsymZ[%dbi]) + return; + + %datablock = $NDT::Datablock[%dbi]; + + %sym = $ND::Symmetry[%datablock]; + %faces = $NDT::FaceCount[%dbi]; + %count = $NDT::AsymZCount[%faces, %sym]; + + //Only potential match is the brick itself - fail + if(%count == 1) + { + echo("ND: No Z match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")"); + return; + } + + %off = -1; + + for(%i = 1; %i <= %count; %i++) + { + %other = $NDT::AsymZBrick[%faces, %sym, %i]; + + //Don't compare with bricks that already have a pair + if($NDT::SkipAsymZ[%other]) + continue; + + //Test all 4 possible rotations + //Not using loop due to lack of goto command + if(!ndTestPairSymmetry(%dbi, %other, false, 0)) + { + %off = 0; + break; + } + + if(!ndTestPairSymmetry(%dbi, %other, false, 1)) + { + %off = 1; + break; + } + + if(!ndTestPairSymmetry(%dbi, %other, false, 2)) + { + %off = 2; + break; + } + + if(!ndTestPairSymmetry(%dbi, %other, false, 3)) + { + %off = 3; + break; + } + } + + //It's possible for a brick to match itself rotated + //here, so only mark it after the search + $NDT::SkipAsymZ[%dbi] = true; + + if(%off != -1) + { + %otherdb = $NDT::Datablock[%other]; + + //Save symmetry + $ND::SymmetryZDatablock[%datablock] = %otherdb; + $ND::SymmetryZOffset[%datablock] = %off; + + $ND::SymmetryZDatablock[%otherdb] = %datablock; + $ND::SymmetryZOffset[%otherdb] = -%off; + + //No need to process the other brick again + $NDT::SkipAsymZ[%other] = true; + } + else + echo("ND: No Z match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")"); +} + +//Test a mesh for a single symmetry plane in itself +function ndTestSelfSymmetry(%dbi, %plane) +{ + %fail = false; + %faces = $NDT::FaceCount[%dbi]; + + for(%i = 0; %i < %faces; %i++) + { + //If this face was already used by another mirror, skip + if(%skipFace[%i]) + continue; + + //Attempt to find the mirrored points + for(%j = 0; %j < 4; %j++) + { + %pt = $NDT::FacePt[%dbi, %i, %j]; + + //Do we already know the mirrored one? + if(%mirrPt[%pt]) + { + %mirr[%j] = %mirrPt[%pt]; + continue; + } + + //Get position of point + %v = $NDT::PtPosition[%dbi, %pt]; + + //Get point at mirrored position based on plane + switch$(%plane) + { + //Flip X + case 0: %mirr = $NDT::PtAtPosition[%dbi, -firstWord(%v) SPC restWords(%v)]; + //Flip Y + case 1: %mirr = $NDT::PtAtPosition[%dbi, getWord(%v, 0) SPC -getWord(%v, 1) SPC getWord(%v, 2)]; + //Flip Z + case 2: %mirr = $NDT::PtAtPosition[%dbi, getWords(%v, 0, 1) SPC -getWord(%v, 2)]; + //Mirror along X+Y + case 3: %mirr = $NDT::PtAtPosition[%dbi, -getWord(%v, 1) SPC -getWord(%v, 0) SPC getWord(%v, 2)]; + //Mirror along X-Y + default: %mirr = $NDT::PtAtPosition[%dbi, getWord(%v, 1) SPC getWord(%v, 0) SPC getWord(%v, 2)]; + } + + if(%mirr) + { + %mirrPt[%pt] = %mirr; + %mirrPt[%mirr] = %pt; + + %mirr[%j] = %mirr; + } + else + { + %fail = true; + break; + } + } + + if(%fail) + break; + + //Test whether the points have a common face + %fail = true; + %count = $NDT::FacesAtPt[%dbi, %mirr0]; + + for(%j = 0; %j < %count; %j++) + { + %potentialFace = $NDT::FaceAtPt[%dbi, %mirr0, %j]; + + //Mirrored face must have the same texture id + if($NDT::FaceTexId[%dbi, %i] != $NDT::FaceTexId[%dbi, %potentialFace]) + continue; + + //Check whether remaining points are in the face + if(!$NDT::PtInFace[%dbi, %potentialFace, %mirr1]) + continue; + + if(!$NDT::PtInFace[%dbi, %potentialFace, %mirr2]) + continue; + + if(!$NDT::PtInFace[%dbi, %potentialFace, %mirr3]) + continue; + + //We found a matching face! + %skipFace[%potentialFace] = true; + %fail = false; + break; + } + + if(%fail) + break; + } + + return %fail; +} + +//Test X or Z symmetry between two meshes with rotation offset +function ndTestPairSymmetry(%dbi, %other, %plane, %rotation) +{ + %fail = false; + %faces = $NDT::FaceCount[%dbi]; + + for(%i = 0; %i < %faces; %i++) + { + //Attempt to find the mirrored points + for(%j = 0; %j < 4; %j++) + { + %pt = $NDT::FacePt[%dbi, %i, %j]; + + //Do we already know the mirrored one? + if(%mirrPt[%pt]) + { + %mirr[%j] = %mirrPt[%pt]; + continue; + } + + //Get position of point + %v = $NDT::PtPosition[%dbi, %pt]; + + //true = X, false = Z + if(%plane) + { + //Get point at mirrored position based on rotation + switch(%rotation) + { + //Flip X + case 0: %mirr = $NDT::PtAtPosition[%other, -firstWord(%v) SPC restWords(%v)]; + //Flip X, rotate 90 + case 1: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 1) SPC getWord(%v, 0) SPC getWord(%v, 2)]; + //Flip X, rotate 180 + case 2: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 0) SPC -getWord(%v, 1) SPC getWord(%v, 2)]; + //Flip X, rotate 270 + default: %mirr = $NDT::PtAtPosition[%other, -getWord(%v, 1) SPC -getWord(%v, 0) SPC getWord(%v, 2)]; + } + } + else + { + //Get point at mirrored position based on rotation + switch(%rotation) + { + //Flip Z + case 0: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 0) SPC getWord(%v, 1) SPC -getWord(%v, 2)]; + //Flip Z, rotate 90 + case 1: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 1) SPC -getWord(%v, 0) SPC -getWord(%v, 2)]; + //Flip Z, rotate 180 + case 2: %mirr = $NDT::PtAtPosition[%other, -getWord(%v, 0) SPC -getWord(%v, 1) SPC -getWord(%v, 2)]; + //Flip Z, rotate 270 + default: %mirr = $NDT::PtAtPosition[%other, -getWord(%v, 1) SPC getWord(%v, 0) SPC -getWord(%v, 2)]; + } + } + + if(%mirr) + { + %mirrPt[%pt] = %mirr; + %mirr[%j] = %mirr; + } + else + { + %fail = true; + break; + } + } + + if(%fail) + break; + + //Test whether the points have a common face + %fail = true; + %count = $NDT::FacesAtPt[%other, %mirr0]; + + for(%j = 0; %j < %count; %j++) + { + %potentialFace = $NDT::FaceAtPt[%other, %mirr0, %j]; + + //Mirrored face must have the same texture id + if($NDT::FaceTexId[%dbi, %i] != $NDT::FaceTexId[%other, %potentialFace]) + continue; + + //Check whether remaining points are in the face + if(!$NDT::PtInFace[%other, %potentialFace, %mirr1]) + continue; + + if(!$NDT::PtInFace[%other, %potentialFace, %mirr2]) + continue; + + if(!$NDT::PtInFace[%other, %potentialFace, %mirr3]) + continue; + + //We found a matching face! + %fail = false; + break; + } + + if(%fail) + break; + } + + return %fail; +} diff --git a/scripts/server/undo.cs b/scripts/server/undo.cs new file mode 100644 index 0000000..6491c1c --- /dev/null +++ b/scripts/server/undo.cs @@ -0,0 +1,31 @@ +// Handles the undo stack for duplicator actions. +// ------------------------------------------------------------------- + +package NewDuplicator_Server +{ + //Catch things falling off the end of the undo stack + function QueueSO::push(%obj, %val) + { + %lastVal = %obj.val[(%obj.head + 1) % %obj.size]; + + if(getFieldCount(%lastVal) == 2) + { + %str = getField(%lastVal, 1); + + if( + %str $= "ND_PLANT" + || %str $= "ND_PAINT" + || %str $= "ND_WRENCH" + ){ + %qobj = getField(%lastVal, 0); + if(isObject(%qobj)){ + %qobj.delete(); + }else{ + // talk("QueueSO::push(" @ %obj @ ", " @ %val @ ") - Nonexistent object " @ %qobj); + } + } + } + + parent::push(%obj, %val); + } +}; diff --git a/server.cs b/server.cs new file mode 100644 index 0000000..47e0f76 --- /dev/null +++ b/server.cs @@ -0,0 +1,65 @@ +// Executes all required scripts and initializes the server side. +// ------------------------------------------------------------------- + +$ND::Version = "1.6.2"; + +$ND::FilePath = filePath($Con::File) @ "/"; +$ND::ConfigPath = "config/NewDuplicator/"; + +$ND::ClassPath = $ND::FilePath @ "classes/"; +$ND::ScriptPath = $ND::FilePath @ "scripts/"; +$ND::ResourcePath = $ND::FilePath @ "resources/"; + +if(isObject(ND_ServerGroup)) + ND_ServerGroup.delete(); + +new ScriptGroup(ND_ServerGroup); + +exec($ND::ClassPath @ "server/ghostgroup.cs"); +exec($ND::ClassPath @ "server/highlightbox.cs"); +exec($ND::ClassPath @ "server/selection.cs"); +exec($ND::ClassPath @ "server/selectionbox.cs"); +exec($ND::ClassPath @ "server/undogrouppaint.cs"); +exec($ND::ClassPath @ "server/undogroupplant.cs"); +exec($ND::ClassPath @ "server/undogroupwrench.cs"); + +exec($ND::ClassPath @ "server/duplimode/boxselect.cs"); +exec($ND::ClassPath @ "server/duplimode/boxselectprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/cutprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/fillcolor.cs"); +exec($ND::ClassPath @ "server/duplimode/fillcolorprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/loadprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/plantcopy.cs"); +exec($ND::ClassPath @ "server/duplimode/plantcopyprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/saveprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/stackselect.cs"); +exec($ND::ClassPath @ "server/duplimode/stackselectprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/supercutprogress.cs"); +exec($ND::ClassPath @ "server/duplimode/wrenchprogress.cs"); + +exec($ND::ScriptPath @ "common/bytetable.cs"); +exec($ND::ScriptPath @ "server/commands.cs"); +exec($ND::ScriptPath @ "server/datablocks.cs"); +exec($ND::ScriptPath @ "server/functions.cs"); +exec($ND::ScriptPath @ "server/handshake.cs"); +exec($ND::ScriptPath @ "server/highlight.cs"); +exec($ND::ScriptPath @ "server/images.cs"); +exec($ND::ScriptPath @ "server/modes.cs"); +exec($ND::ScriptPath @ "server/namedtargets.cs"); +exec($ND::ScriptPath @ "server/prefs.cs"); +exec($ND::ScriptPath @ "server/symmetrydefinitions.cs"); +exec($ND::ScriptPath @ "server/symmetrytable.cs"); +exec($ND::ScriptPath @ "server/undo.cs"); + +activatePackage(NewDuplicator_Server); +schedule(10, 0, activatePackage, NewDuplicator_Server_Final); + +ndRegisterDuplicatorModes(); +ndRegisterPrefs(); +ndResendHandshakes(); + +if($Pref::Server::ND::SymTableOnStart && !$ND::SymmetryTableCreated){ + schedule(10, 0, ndCreateSymmetryTable); +} + +exec("./v20fix.cs"); diff --git a/v20fix.cs b/v20fix.cs new file mode 100644 index 0000000..b6b648b --- /dev/null +++ b/v20fix.cs @@ -0,0 +1,15 @@ + +function colorFToI(%f){ + %i = %f*255; + %i = mFloor(%i + 0.5); + return %i; +} + +function getColorI(%color){ + %color2 = + colorFToI(getWord(%color, 0)) SPC + colorFToI(getWord(%color, 1)) SPC + colorFToI(getWord(%color, 2)) SPC + colorFToI(getWord(%color, 3)) + ; +}