2025-02-26 17:36:12 -07:00

764 lines
18 KiB
C#

//bls 3
// -------------------------------------------------------------------
// Utility
function ndmessageClient(%client, %ignore, %msg) {
commandToClient(%client, 'centerPrint', "<font:Verdana:20>" @ %msg, 4);
}
//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;
}
function ndColorFToI(%f){
%i = %f*255;
%i = mFloor(%i + 0.5);
return %i;
}
function ndGetColorI(%color){
%color2 =
ndColorFToI(getWord(%color, 0)) SPC
ndColorFToI(getWord(%color, 1)) SPC
ndColorFToI(getWord(%color, 2)) SPC
ndColorFToI(getWord(%color, 3))
;
}
//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 = ndGetColorI(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 = ndGetColorI(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
{
ndmessageClient(%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
{
ndmessageClient(%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
{
ndmessageClient(%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);
if(%db.ndSubset $= "") continue;
%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();
if(%subset == $ND::SubsetLogicBus) {
$ND::FillBrickSubset = $ND::SubsetLogicWire;
ndFillBus(getWords(%box, 0, 2), getWords(%box, 3, 5), getAngleIDFromPlayer(%this.getControlObject()), %this.currentColor);
} else {
ndFillAreaWithBricks(getWords(%box, 0, 2), getWords(%box, 3, 5));
}
%s = ($ND::FillBrickCount == 1 ? "" : "s");
ndmessageClient(%this, '', "\c6Filled in \c3" @ $ND::FillBrickCount @ "\c6 brick" @ %s);
}
$ND::SubsetDefault = 0;
$ND::SubsetLogicWire = 1;
$ND::SubsetLogicBus = 2;
//$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 "";
}
} else {
return $ND::SubsetDefault;
}
}
function ndLookupSubsetName(%name) {
%subset = $ND::Subset[%name];
return (%subset !$= "") ? %subset : $ND::SubsetDefault;
}