Skip to content
Open
292 changes: 230 additions & 62 deletions data/js/commands/targeting/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ function CommandRegistration()
RegisterCommand( "addxspawner", 8, true );
}

// Collapse spaces/underscores/dashes and lowercase, e.g. "Brown Horse" -> "brownhorse"
function normalizeSectionID( str )
{
return ( str || "" ).replace( /[\s_\-]+/g, "" ).toLowerCase();
}

/** @type { ( socket: Socket, cmdString: string ) => void } */
function command_ADD( socket, cmdString )
{
Expand All @@ -28,25 +34,42 @@ function command_ADD( socket, cmdString )
case "NPC":
if( splitString[1] )
{
socket.xText = splitString[1];
socket.CustomTarget( 0, GetDictionaryEntry( 8068, socket.language ) + " " + splitString[1] ); // Select location for NPC:
// Join tokens after "npc"
var tokens = splitString.slice( 1 );
// Trailing number => amount
var maybeAmount = parseInt( tokens[tokens.length - 1] );
if( !isNaN( maybeAmount ) && maybeAmount > 0 )
{
socket.addAmount = maybeAmount;
tokens.pop();
}
else
socket.addAmount = 1;

// Keep underscores/dashes/spaces; just lowercase
var rawNpc = tokens.join(" ").toLowerCase();

socket.xText = rawNpc;
socket.CustomTarget( 0, GetDictionaryEntry( 8068, socket.language ) + " " + rawNpc ); // Select location for NPC:
}
break;

case "ITEM":
if( splitString[1] )
{
// .add item itemID
if( splitString[2] ) // Check for optional amount
var tokens = splitString.slice(1);
var maybeAmount = parseInt( tokens[tokens.length - 1] );
if( !isNaN( maybeAmount ) && maybeAmount > 0 )
{
// .add item itemID amount
let amount = parseInt( splitString[2] );
if( !isNaN( amount ) && amount > 0 )
{
socket.addAmount = amount;
}
socket.addAmount = maybeAmount;
tokens.pop();
}
socket.xText = splitString[1];
socket.CustomTarget( 2, GetDictionaryEntry( 8069, socket.language ) + " " + splitString[1] ); // Select location for scripted item:

// Keep underscores/dashes/spaces; just lowercase
var rawItem = tokens.join(" ").toLowerCase();

socket.xText = rawItem;
socket.CustomTarget( 2, GetDictionaryEntry( 8069, socket.language ) + " " + rawItem ); // Select location for scripted item:
}
break;
case "SPAWNER":
Expand Down Expand Up @@ -119,37 +142,72 @@ function onCallback0( socket, ourObj )
return;

var mChar = socket.currentChar;
if( mChar )
if( !mChar ) return;

var x = socket.GetWord( 11 );
var y = socket.GetWord( 13 );
var z = socket.GetSByte( 16 );
var StrangeByte = socket.GetWord( 1 );

// Pre-7.0.9 client: add tile height if needed
if(( StrangeByte == 0 && ourObj.isItem ) || ( socket.clientMajorVer <= 7 && socket.clientSubVer < 9 ))
{
var x = socket.GetWord( 11 );
var y = socket.GetWord( 13 );
var z = socket.GetSByte( 16 );
var StrangeByte = socket.GetWord(1);
z += GetTileHeight( socket.GetWord( 17 ));
}

// If connected with a client lower than v7.0.9, manually add height of targeted tile
if(( StrangeByte == 0 && ourObj.isItem ) || ( socket.clientMajorVer <= 7 && socket.clientSubVer < 9 ))
{
z += GetTileHeight( socket.GetWord( 17 ));
}
var npcSection = socket.xText;
socket.xText = null;

var npcSection = socket.xText;
socket.xText = null;
var useNpcList = false;
if( socket.tempInt2 )
{
useNpcList = true;
socket.tempInt2 = null;
}

// Amount defaults to 1. If NPCLIST is used, we ignore amount to avoid repeated pickers
var amount = socket.addAmount;
if( amount == null || amount < 1 ) amount = 1;
if( useNpcList ) amount = 1;

var useNpcList = false;
if( socket.tempInt2 )
// Try to spawn one; if not found, retry with normalized fallback
function trySpawnOnce(section)
{
var c = SpawnNPC( section, x, y, z, mChar.worldnumber, mChar.instanceID, useNpcList );
if( ValidateObject( c ) && c.isChar )
{
useNpcList = true;
socket.tempInt2 = null;
c.InitWanderArea();
return c;
}
return null;
}

var newChar = SpawnNPC( npcSection, x, y, z, mChar.worldnumber, mChar.instanceID, useNpcList );
if( ValidateObject( newChar ) && newChar.isChar )
// First spawn (with fallback to normalized form of what we already normalized�harmless)
var first = trySpawnOnce( npcSection );
if( !first )
{
var altSection = normalizeSectionID( npcSection );
if( altSection && altSection !== npcSection )
{
newChar.InitWanderArea();
first = trySpawnOnce( altSection );
if( first ) npcSection = altSection;
}
else
}

if( !first )
{
mChar.SysMessage( GetDictionaryEntry( 8072, socket.language ) + " " + npcSection ); // NPC-section not found in DFNs:
return;
}

// Spawn remaining (amount-1) at the same ground location
for( var i = 1; i < amount; i++ )
{
var extra = trySpawnOnce( npcSection );
if( !extra )
{
mChar.SysMessage( GetDictionaryEntry( 8072, socket.language ) + " " + npcSection ); // NPC-section not found in DFNs:
// If one of the later spawns fails, just stop quietly
break;
}
}
}
Expand Down Expand Up @@ -244,59 +302,169 @@ function onCallback2( socket, ourObj )
var iSection = socket.xText;
socket.xText = null;

// Get amount to add
// Get amount to add (default 1)
var itemAmount = socket.addAmount;
if( itemAmount == null || itemAmount < 1 )
{
itemAmount = 1;
}

var StrangeByte = socket.GetWord( 1 );
if( StrangeByte == 0 && ourObj.isChar )

// Helper to spawn N items into a character's pack
function spawnToPack(targetChar, section, count)
{
//If target is a character, add item to backpack
var backpack = ourObj.FindItemLayer( 21 );
if( backpack != null )
var made = 0;
// Try once with 'count' to allow stacking DFNs to do it in one go
var first = CreateDFNItem( socket, targetChar, section, count, "ITEM", true );
if( ValidateObject(first) )
{
var newItem = CreateDFNItem( socket, ourObj, iSection, itemAmount, "ITEM", true );
made += (first.amount && first.amount > 0) ? first.amount : 1;

// If it didn't stack up to requested amount, add the rest one by one
while( made < count )
{
var extra = CreateDFNItem( socket, targetChar, section, 1, "ITEM", true );
if( ValidateObject(extra) )
{
made += (extra.amount && extra.amount > 0) ? extra.amount : 1;
}
else
{
break;
}
}
return true;
}
else
return false;
}

// Helper to spawn N items into a container item
function spawnToContainer(containerItem, section, count)
{
var made = 0;

// First try a single spawn with 'count' (in case DFN stacks)
var first = CreateDFNItem( socket, mChar, section, count, "ITEM", false );
if( ValidateObject(first) )
{
mChar.SysMessage( GetDictionaryEntry( 8073, socket.language )); // That character has no backpack, no item added
first.container = containerItem;
first.PlaceInPack();

made += (first.amount && first.amount > 0) ? first.amount : 1;

while( made < count )
{
var extra = CreateDFNItem( socket, mChar, section, 1, "ITEM", false );
if( ValidateObject(extra) )
{
extra.container = containerItem;
extra.PlaceInPack();
made += (extra.amount && extra.amount > 0) ? extra.amount : 1;
}
else
{
break;
}
}
return true;
}
return false;
}
else if( StrangeByte == 0 && ourObj.isItem && ourObj.type == 1 )

// Helper to spawn N items on the ground at x,y,z
function spawnToWorld(x, y, z, section, count)
{
// If target is an item, and a container, add item to the container
var newItem = CreateDFNItem( socket, ourObj, iSection, itemAmount, "ITEM", false );
if( ValidateObject( newItem ))
var made = 0;

var first = CreateDFNItem( socket, mChar, section, count, "ITEM", false );
if( ValidateObject(first) )
{
newItem.container = ourObj;
newItem.PlaceInPack();
first.SetLocation( x, y, z );
made += (first.amount && first.amount > 0) ? first.amount : 1;

while( made < count )
{
var extra = CreateDFNItem( socket, mChar, section, 1, "ITEM", false );
if( ValidateObject(extra) )
{
extra.SetLocation( x, y, z );
made += (extra.amount && extra.amount > 0) ? extra.amount : 1;
}
else
{
break;
}
}
return true;
}
return false;
}
else
{
var x = socket.GetWord( 11 );
var y = socket.GetWord( 13 );
var z = socket.GetSByte( 16 );

// If connected with a client lower than v7.0.9, manually add height of targeted tile
if(( StrangeByte == 0 && ourObj.isItem ) || ( socket.clientMajorVer <= 7 && socket.clientSubVer < 9 ))
// Target is a character? -> put in their backpack
if( StrangeByte == 0 && ourObj.isChar )
{
var backpack = ourObj.FindItemLayer( 21 );
if( backpack != null )
{
z += GetTileHeight( socket.GetWord( 17 ));
var ok = spawnToPack( ourObj, iSection, itemAmount );
if( !ok )
{
// Fallback: try normalized section ID (handles "long sword" -> "longsword")
var alt = normalizeSectionID( iSection );
if( alt && alt != iSection )
{
ok = spawnToPack( ourObj, alt, itemAmount );
if( ok ) iSection = alt;
}
}
if( !ok )
mChar.SysMessage( GetDictionaryEntry( 8074, socket.language ) + " " + iSection ); // Item-section not found in DFNs:
}
else
{
mChar.SysMessage( GetDictionaryEntry( 8073, socket.language )); // That character has no backpack, no item added
}
return;
}

var newItem = CreateDFNItem( socket, mChar, iSection, itemAmount, "ITEM", false );
if( newItem )
// Target is a container item? -> drop into that container
if( StrangeByte == 0 && ourObj.isItem && ourObj.type == 1 )
{
var ok2 = spawnToContainer( ourObj, iSection, itemAmount );
if( !ok2 )
{
newItem.SetLocation( x, y, z );
var alt2 = normalizeSectionID( iSection );
if( alt2 && alt2 != iSection )
{
ok2 = spawnToContainer( ourObj, alt2, itemAmount );
if( ok2 ) iSection = alt2;
}
}
if( !ok2 )
mChar.SysMessage( GetDictionaryEntry( 8074, socket.language ) + " " + iSection );
return;
}
if( !newItem )

// Otherwise, world location
var x = socket.GetWord( 11 );
var y = socket.GetWord( 13 );
var z = socket.GetSByte( 16 );

// Pre-7.0.9 clients need tile height added
if(( StrangeByte == 0 && ourObj.isItem ) || ( socket.clientMajorVer <= 7 && socket.clientSubVer < 9 ))
z += GetTileHeight( socket.GetWord( 17 ));

var ok3 = spawnToWorld( x, y, z, iSection, itemAmount );
if( !ok3 )
{
mChar.SysMessage( GetDictionaryEntry( 8074, socket.language ) + " " + iSection ); // Item-section not found in DFNs:
var alt3 = normalizeSectionID( iSection );
if( alt3 && alt3 != iSection )
{
ok3 = spawnToWorld( x, y, z, alt3, itemAmount );
if( ok3 ) iSection = alt3;
}
}
if( !ok3 )
mChar.SysMessage( GetDictionaryEntry( 8074, socket.language ) + " " + iSection );
}
}

Expand Down