initial commit

This commit is contained in:
Redo
2022-10-05 16:02:11 -06:00
commit 93d385e859
51 changed files with 11722 additions and 0 deletions

225
scripts/common/bytetable.cs Normal file
View File

@ -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);
}

1151
scripts/server/commands.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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";
};

734
scripts/server/functions.cs Normal file
View File

@ -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 <color:xxxxxx> 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 "<color:" @ %r1 @ %r2 @ %g1 @ %g2 @ %b1 @ %b2 @ ">";
}
//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', "<font:Verdana:20>\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;
}

View File

@ -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);
}

106
scripts/server/highlight.cs Normal file
View File

@ -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);
}

297
scripts/server/images.cs Normal file
View File

@ -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', "<font:Verdana:20>\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', "<font:Verdana:20>\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");
}

497
scripts/server/modes.cs Normal file
View File

@ -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 = "<font:Arial:22>";
//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<font:Verdana:16>";
for(%i = 0; strLen(%l[%i]) || strLen(%r[%i]); %i++)
{
if(strLen(%l[%i]))
{
if(%align)
%message = %message @ "<just:left>";
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 @ "<just:right>";
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);
}
};

View File

@ -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);
}

212
scripts/server/prefs.cs Normal file
View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

31
scripts/server/undo.cs Normal file
View File

@ -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);
}
};