uox3/data/js/server/misc/facetRuleset.js
2026-03-02 20:28:09 -06:00

555 lines
18 KiB
JavaScript

/// <reference path="../../definitions.d.ts" />
// @ts-check
const coreShardEra = GetServerSetting( "CoreShardEra" );
const timeBetweenMurdererVisit = 2592000; // 30 days between each time a murderer can visit Ilshenar
// Define which rules apply to which facets
// Example: const facetCorpseCarveRestrict = [ 1, 2, 3, 4 ]; // Corpse carving restricted in Trammel, Ilshenar, Malas, Tokuno
const facetCorpseCarveRestrict = [1, 2, 3, 4, 5]; // Prevents players from carving corpses of other players
const facetMurdererRestrict = [1, 2, 3, 4, 5]; // Restricts access to facet for Murderers
const facetCombatRestrict = [1, 2, 3, 4, 5]; // Prevents players from attacking other players
const facetSpellRestrict = [1, 2, 3, 4, 5]; // Prevents players from casting hostile and/or beneficial spells on other players
const facetPotionRestrict = [1, 2, 3, 4, 5]; // Prevents players from damaging other players with explosion potions
const facetStealRestrict = [1, 2, 3, 4, 5]; // Prevents players from stealing from other players
const facetSnoopRestrict = [1, 2, 3, 4, 5]; // Prevents players from snooping in packs of other players
const facetLootingRestrict = [1, 2, 3, 4, 5]; // Prevents players from looting corpses if that would make them criminal
const facetBlockMovementRestrict = [1, 2, 3, 4, 5]; // Prevents characters from being blocked by other players and/or movable objects
const facetBladeSpiritTargetRestrict = [1, 2, 3, 4, 5]; // Restricts BS targets to monsters, wild animals and caster and guild members/enemies
const facetEnergyVortexTargetRestrict = [1, 2, 3, 4, 5]; // Restricts EV targets to monsters, wild animals and caster and guild members/enemies
const facetBardProvokeRestrict = [1, 2, 3, 4, 5]; // Restricts targets for bard provocation skill to monsters, wild animals, caster and guild members/enemies
// Override above facet-specific rules for specific regions
// Example: const regionCorpseCarveOverride = [ 17, 23, 56 ]; // Override corpse carving restrictions in these regions
const regionCorpseCarveOverride = [];
const regionMurdererOverride = [];
const regionCombatOverride = [];
const regionSpellOverride = [];
const regionPotionOverride = [];
const regionStealOverride = [];
const regionSnoopOverride = [];
const regionLootingOverride = [];
const regionBlockMovementOverride = [];
const regionBladeSpiritTargetOverride = [];
const regionEnergyVortexTargetOverride = [];
const regionBardProvokeOverride = [];
/** @type { ( attacker: Character, defender: Character ) => boolean } */
function onCombatStart( pAttacker, pDefender )
{
var socket = pAttacker.socket;
if( socket == null )
return true;
var worldNum = pAttacker.worldnumber;
if( regionCombatOverride.indexOf( pAttacker.region.id ) == -1 && facetCombatRestrict.indexOf( pAttacker.worldnumber ) != -1 )
{
if( pDefender.npc )
{
if( ValidateObject( pDefender.owner ))
{
// Treat Defender as if it IS its owner
pDefender = pDefender.owner;
}
else
{
if(( pDefender.npcFlag == 1 && pDefender.innocent ) || pDefender.innocent )
{
// Don't allow starting combat against "good" NPCs that are innocent, or against innocent NPCs
socket.SysMessage( GetDictionaryEntry( 9230, socket.language )); // You cannot perform negative acts on your target.
return false;
}
else
{
// Allow combat against non-good and/or non-innocent NPCs
return true;
}
}
}
// Player cannot attack other players in Trammel, Ilshenar, or Malas - unless they are in the same guild or in a guild they're at war with
var guildRelations = CheckGuildRelationShip( pAttacker, pDefender );
if( guildRelations != 1 && guildRelations != 4 )
{
// Neither in same guild nor at war, disallow combat
socket.SysMessage( GetDictionaryEntry( 9230, socket.language )); // You cannot perform negative acts on your target.
return false;
}
}
// By default, allow hard-coded combat start process to continue
return true;
}
// Players cannot cast aggressive spells at other players in Trammel/Ilshenar/Malas
/** @type { ( target: BaseObject, caster: Character, spellNum: number ) => number } */
function onSpellTarget( myTarget, pCaster, spellID )
{
// We don't care if caster and target is the same
if( myTarget == pCaster )
return 0;
var socket = pCaster.socket;
if( socket != null )
{
var worldNum = pCaster.worldnumber;
if( regionSpellOverride.indexOf( pCaster.region.id ) == -1 && facetSpellRestrict.indexOf( pCaster.worldnumber ) != -1 )
{
var spellCast = Spells[spellID];
if( spellCast.aggressiveSpell )
{
if( myTarget.npc )
{
if( ValidateObject( myTarget.owner ))
{
// Treat target as if it IS its owner
myTarget = myTarget.owner;
}
else
{
if(( myTarget.npcFlag == 1 && myTarget.innocent ) || myTarget.innocent )
{
// Don't allow aggressive spells against blue/innocent/good NPCs
socket.SysMessage( GetDictionaryEntry( 9230, socket.language )); // You cannot perform negative acts on your target.
return 2;
}
else
{
// Allow aggressive spell against grey/criminal or red/murderer NPCs
return false;
}
}
}
// Aggressive spells only allowed between guild members or between guilds at war
var guildRelations = CheckGuildRelationShip( pCaster, myTarget );
if( guildRelations != 1 && guildRelations != 4 )
{
// Neither in same guild nor at war
socket.SysMessage( GetDictionaryEntry( 9230, socket.language )); // You cannot perform negative acts on your target.
return 2;
}
}
else // Non-aggressive spells
{
if( myTarget.npc )
{
if( ValidateObject( myTarget.owner ))
{
// Treat target as if it IS its owner
myTarget = myTarget.owner;
}
else if( !myTarget.isHuman || ( !myTarget.innocent && myTarget.npcFlag != 1 ))
{
socket.SysMessage( GetDictionaryEntry( 9231, socket.language )); // You cannot perform beneficial acts on your target.
return 2;
}
}
// Beneficial spellcasts only allowed between...
if( guildRelations == 4 || ( pCaster.guild == null && myTarget.guild == null ))
{
// ...players in same guild / non-guild players and non-guild players
return false;
}
else if(( pCaster.guild == null && myTarget.guild != null && myTarget.guild.IsAtPeace() ) ||
( myTarget.guild == null && pCaster.guild != null && pCaster.guild.IsAtPeace() ))
{
// ...non-guild players and players in guilds not at war with anyone
return false;
}
else if( pCaster.guild != null && pCaster.guild.IsAtPeace() && myTarget.guild != null && myTarget.guild.IsAtPeace() )
{
// ...players in guilds not at war with anyone and another player in another guild not at war with anyone
return false;
}
socket.SysMessage( GetDictionaryEntry( 9231, socket.language )); // You cannot perform beneficial acts on your target.
return 2;
}
}
}
return false;
}
// Players cannot steal from other players in Trammel/Ilshenar/Malas
/** @type { ( thief: Character, theft: Item, victim: Character ) => void } */
function onSteal( pThief, iStolen, pVictim )
{
if( !ValidateObject( pThief ) || !ValidateObject( pVictim ))
return 1;
var socket = pThief.socket;
var worldNum = pThief.worldnumber;
if( regionStealOverride.indexOf( pThief.region.id ) == -1 && facetStealRestrict.indexOf( pThief.worldnumber ) != -1 && !pVictim.npc )
{
if( socket != null )
{
socket.SysMessage( GetDictionaryEntry( 9230, socket.language )); // You cannot perform negative acts on your target.
}
return 1;
}
return 0;
}
// Players cannot snoop the backpacks of other players in Trammel/Ilshenar/Malas
/** @type { ( snooped: Character, pack: Item, snooper: Character ) => boolean } */
function onSnoopAttempt( pSnooped, targPack, pSnooping )
{
if( !ValidateObject( pSnooped ) || !ValidateObject( pSnooping ) || !ValidateObject( targPack ))
return true;
if( pSnooping.isGM ) // Override for GMs
return true;
var socket = pSnooping.socket;
var worldNum = pSnooping.worldnumber;
if(( regionSnoopOverride.indexOf( pSnooping.region.id ) == -1 && facetSnoopRestrict.indexOf( pSnooping.worldnumber ) != -1 ) && !pSnooped.npc )
{
if( socket != null )
{
socket.SysMessage( GetDictionaryEntry( 9230, socket.language )); // You cannot perform negative acts on your target.
}
return false;
}
// Default to allow snooping
return true;
}
/** @type { ( pChanging: Character, newStatus: number, oldStatus: number ) => boolean } */
function onFlagChange( pChanging, newStatus, oldStatus )
{
var worldNum = pChanging.worldnumber;
if( regionMurdererOverride.indexOf( pChanging.region.id ) == -1 && facetMurdererRestrict.indexOf( pChanging.worldnumber ) != -1 )
{
if( newStatus == 0x01 ) // Murderer
{
if( worldNum == 2 ) // Ilshenar
{
// Murderers can go to Ilshenar once per month. This limitation does not reset if they "go blue" and then turn red again
var lastIlshenarVisit = parseInt( pChanging.GetTag( "ilshMurderVisit" ));
if( lastIlshenarVisit == 0 || (( Date.now() - lastIlshenarVisit ) / 1000 > timeBetweenMurdererVisit ))
{
// Allow them to stay in Ilshenar, since it's more than 30 days since their last visit as a murderer (but update timestamp)
pChanging.SetTag( "ilshMurderVisit", Date.now().toString() );
return false;
}
}
// Otherwise, teleport murderers instantly to Chaos shrine in Felucca
// Murderers are not allowed in Trammel/Malas, and only limited visitation rights to Ilshenar
pChanging.Teleport( 1456, 852, 0 );
}
else if( newStatus == 0x02 ) // Criminal
{
if( EraStringToNum( coreShardEra ) >= EraStringToNum( "lbr" ))
{
// Criminal flag is not enabled in Trammel ruleset, as per Publish 15 (LBR), so make sure it can't be set
pChanging.innocent = true;
}
}
}
return false;
}
// Handle corpse-looting rules
/** @type { ( item: Item, pickerUpper: Character, objCont: BaseObject ) => boolean } */
function onPickup( iPickedUp, pGrabber )
{
var pSock = pGrabber.socket;
if( pSock == null)
return false;
var worldNum = pGrabber.worldnumber;
if( regionLootingOverride.indexOf( pGrabber.region.id ) == -1 && facetLootingRestrict.indexOf( pGrabber.worldnumber ) != -1 )
{
// Check the value of pSock.pickupSpot to determine where the item was picked up from
switch( pSock.pickupSpot )
{
case 3: // otherpack
if( iPickedUp.container && ( iPickedUp.container ).corpse )
{
var corpseOwner = ( iPickedUp.container ).owner;
if( ValidateObject( corpseOwner ) && !corpseOwner.npc )
{
// If corpse belongs to another player, disallow picking up the item, unless
// the players belong to the same guild, their guilds are at war, or they
// are in the same party and have set looting rules to allow party members to
// loot them
var guildRelations = CheckGuildRelationShip( pGrabber, corpseOwner );
if( guildRelations != 1 && guildRelations != 4 )
{
// OR if corpse belongs to a criminal or murderer
if( !corpseOwner.criminal && !corpseOwner.murderer )
{
// Neither in same guild nor at war, disallow looting corpse
pSock.SysMessage( GetDictionaryEntry( 9064, pSock.language )); // You cannot pick that up.
return false;
}
}
}
}
break;
default:
break;
}
}
// Default to hard-coded rules for pickup
return true;
}
// Players cannot collide with non-locked down items or shove other players in Trammel/Ilshenar
// TODO: How is collision with non-locked down items overridden? Client prevents movement through blocking items...
/** @type { ( targSock: Socket, objColliding: Character, objCollideWith: BaseObject ) => boolean } */
function onCollide( trgSock, srcChar, trgObj )
{
if( !ValidateObject( srcChar ) || !ValidateObject( trgObj ))
return true;
var worldNum = srcChar.worldnumber;
if( regionBlockMovementOverride.indexOf( trgObj.region.id ) == -1 && facetBlockMovementRestrict.indexOf( trgObj.worldnumber ) != -1 )
{
// Don't let characters block other characters...
if( trgObj.isChar )
{
// ...unless they're in the same guild, or at war!
if( ValidateObject( trgObj.owner ))
{
// Treat target as if it IS its owner
trgObj = trgObj.owner;
}
var guildRelations = CheckGuildRelationShip( srcChar, trgObj );
if( guildRelations != 1 && guildRelations != 4 )
{
return false;
}
}
else if( trgObj.isItem && trgObj.movable == 1 )
{
// Don't let items dropped on ground block movement
return false;
}
else if( trgObj.isItem && trgObj.dispellable ) // Wall of Stone
{
// Don't let Wall of Stone (or Energy Field) block movement unless players are in same guild or at war
// TODO: Client seems to automatically block movement through these items though...
var trgChar = CalcCharFromSer( trgObj.morey );
if( ValidateObject( trgChar ))
{
var guildRelations = CheckGuildRelationShip( srcChar, trgChar );
if( guildRelations != 1 && guildRelations != 4 )
{
return false;
}
}
}
}
// Default to hard-coded collision handling
return true;
}
function FacetRuleExplosionDamage( sourceChar, targetChar )
{
if( regionPotionOverride.indexOf( targetChar.region.id ) == -1 && facetPotionRestrict.indexOf( targetChar.worldnumber ) != -1 )
{
if( ValidateObject( targetChar.owner ))
{
// Treat target as if it IS its owner
targetChar = targetChar.owner;
}
if( targetChar.npc && targetChar.innocent )
{
// Not a valid target for Explosion Damage
return false;
}
if( !targetChar.npc )
{
var guildRelations = CheckGuildRelationShip( sourceChar, targetChar );
if( guildRelations != 1 )
{
// Not at war with target player's guild
if( sourceChar.socket )
{
sourceChar.socket.SysMessage( GetDictionaryEntry( 9230, sourceChar.socket.language )); // You cannot perform negative acts on your target.
}
return;
}
}
}
return true;
}
function FacetRuleBladeSpiritTarget( sourceChar, targetChar )
{
if( regionBladeSpiritTargetOverride.indexOf( targetChar.region.id ) == -1
&& facetBladeSpiritTargetRestrict.indexOf( targetChar.worldnumber ) != -1 )
{
if( ValidateObject( targetChar.owner ))
{
// Treat target as if it IS its owner
targetChar = targetChar.owner;
}
if( targetChar.npc && targetChar.innocent )
{
// Not a valid target for Blade Spirit
return false;
}
if( !targetChar.npc )
{
var guildRelations = CheckGuildRelationShip( sourceChar, targetChar );
if( guildRelations != 1 && guildRelations != 4 ) // not at war, nor same guild
{
// Not a valid target for Blade Spirit
return false;
}
}
}
return true;
}
function FacetRuleEnergyVortexTarget( sourceChar, targetChar )
{
if( regionEnergyVortexTargetOverride.indexOf( targetChar.region.id ) == -1
&& facetEnergyVortexTargetRestrict.indexOf( targetChar.worldnumber ) != -1 )
{
if( ValidateObject( targetChar.owner ))
{
// Treat target as if it IS its owner
targetChar = targetChar.owner;
}
if( targetChar.npc && targetChar.innocent )
{
// Not a valid target for Energy Vortex
return false;
}
if( !targetChar.npc )
{
var guildRelations = CheckGuildRelationShip( sourceChar, targetChar );
if( guildRelations != 1 && guildRelations != 4 ) // not at war, nor same guild
{
// Not a valid target for Energy Vortex
return false;
}
}
}
return true;
}
function FacetRuleBardProvoke( sourceChar, targetChar )
{
if( regionBardProvokeOverride.indexOf( targetChar.region.id ) == -1
&& facetBardProvokeRestrict.indexOf( targetChar.worldnumber ) != -1 )
{
if( ValidateObject( targetChar.owner ))
{
// Treat target as if it IS its owner
targetChar = targetChar.owner;
}
if( targetChar.npc && targetChar.innocent )
{
// Not a valid target for provocation
if( sourceChar.socket )
{
sourceChar.socket.SysMessage( GetDictionaryEntry( 9230, sourceChar.socket.language )); // You cannot perform negative acts on your target.
}
return false;
}
if( !targetChar.npc )
{
var guildRelations = CheckGuildRelationShip( sourceChar, targetChar );
if( guildRelations != 1 && guildRelations != 4 ) // not at war, nor same guild
{
// Not a valid target for provocation
if( sourceChar.socket )
{
sourceChar.socket.SysMessage( GetDictionaryEntry( 9230, sourceChar.socket.language )); // You cannot perform negative acts on your target.
}
return false;
}
}
}
return true;
}
/** @type { ( damaged: Character, attacker: Character, damageValue: number, damageType: WeatherType ) => boolean } */
function onDamage( targetChar, sourceChar, damageValue, damageType )
{
// Allow all damage without a source character, or with a non-tame/non-hirling NPC as the source
if( !ValidateObject( sourceChar ) || ( ValidateObject( sourceChar ) && sourceChar.npc && !ValidateObject( targetChar.owner )))
return true;
if( regionSpellOverride.indexOf( targetChar.region.id ) == -1 && facetSpellRestrict.indexOf( targetChar.worldnumber ) != -1 )
{
if( ValidateObject( targetChar.owner ))
{
// Treat target as if it IS its owner
targetChar = targetChar.owner;
}
var guildRelations = CheckGuildRelationShip( sourceChar, targetChar );
if( guildRelations != 1 )
{
// Not at war with target player's guild
return false;
}
}
return true;
}
function CheckGuildRelationShip( pAttacker, pDefender )
{
var attackerGuild = pAttacker;
var defenderGuild = pDefender;
if( attackerGuild && defenderGuild )
{
// Potential relationships:
// 0 - Neutral
// 1 - At War
// 2 - Allied
// 3 - Unknown
// 4 - Same
return CompareGuildByGuild( attackerGuild, defenderGuild );
}
return -1; // Either player does not belong to a guild
}
function CheckCorpseCarving( pChar, corpseOwner, iCorpse )
{
var worldNum = pChar.worldnumber;
if( regionCorpseCarveOverride.indexOf( pChar.region.id ) == -1 && facetCorpseCarveRestrict.indexOf( pChar.worldnumber ) != -1 )
{
if( !corpseOwner.npc )
{
// Disallow carving up corpses of players in Trammel/Ilshenar/Malas
return false;
}
}
else // Felucca, Tokuno?
{
if( !corpseOwner.npc && !corpseOwner.murderer && !pChar.murderer )
{
// Disallow carving up corpses of non-murderers in Felucca, unless pChar is a murderer
return false;
}
}
return true;
}