1419 lines
60 KiB
TypeScript
1419 lines
60 KiB
TypeScript
import { Circuit } from './Circuit';
|
|
import { Agent } from './Agent';
|
|
import { Caps } from './Caps';
|
|
import { Comms } from './Comms';
|
|
import { ClientEvents } from './ClientEvents';
|
|
import { IObjectStore } from './interfaces/IObjectStore';
|
|
import { ObjectStoreFull } from './ObjectStoreFull';
|
|
import { ObjectStoreLite } from './ObjectStoreLite';
|
|
import { RequestRegionInfoMessage } from './messages/RequestRegionInfo';
|
|
import { RegionInfoMessage } from './messages/RegionInfo';
|
|
import { Message } from '../enums/Message';
|
|
import { Utils } from './Utils';
|
|
import { RegionHandshakeMessage } from './messages/RegionHandshake';
|
|
import { MapNameRequestMessage } from './messages/MapNameRequest';
|
|
import { GridLayerType } from '../enums/GridLayerType';
|
|
import { MapBlockReplyMessage } from './messages/MapBlockReply';
|
|
import { FilterResponse } from '../enums/FilterResponse';
|
|
import * as Long from 'long';
|
|
import { Packet } from './Packet';
|
|
import { LayerDataMessage } from './messages/LayerData';
|
|
import { LayerType } from '../enums/LayerType';
|
|
import { Subscription } from 'rxjs/internal/Subscription';
|
|
import { BitPack } from './BitPack';
|
|
import * as builder from 'xmlbuilder';
|
|
import { SimAccessFlags } from '../enums/SimAccessFlags';
|
|
import { Subject } from 'rxjs/internal/Subject';
|
|
import { ParcelDwellRequestMessage } from './messages/ParcelDwellRequest';
|
|
import { ParcelDwellReplyMessage } from './messages/ParcelDwellReply';
|
|
import { Parcel } from './public/Parcel';
|
|
import { RegionEnvironment } from './public/RegionEnvironment';
|
|
import { Color4 } from './Color4';
|
|
import { SkyPreset } from './public/interfaces/SkyPreset';
|
|
import { Vector4 } from './Vector4';
|
|
import { WaterPreset } from './public/interfaces/WaterPreset';
|
|
import { ClientCommands } from './ClientCommands';
|
|
import { SimulatorViewerTimeMessageMessage } from './messages/SimulatorViewerTimeMessage';
|
|
import { ParcelOverlayMessage } from './messages/ParcelOverlay';
|
|
import { ILandBlock } from './interfaces/ILandBlock';
|
|
import { LandFlags } from '../enums/LandFlags';
|
|
import { ParcelPropertiesRequestMessage } from './messages/ParcelPropertiesRequest';
|
|
import Timer = NodeJS.Timer;
|
|
import { UUID } from './UUID';
|
|
import { RegionFlags } from '../enums/RegionFlags';
|
|
import { BotOptionFlags } from '../enums/BotOptionFlags';
|
|
import { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent';
|
|
import { PacketFlags } from '../enums/PacketFlags';
|
|
import { Vector3 } from './Vector3';
|
|
import { Vector2 } from './Vector2';
|
|
import { ObjectResolver } from './ObjectResolver';
|
|
import { SimStatsMessage } from './messages/SimStats';
|
|
import { SimStatsEvent } from '../events/SimStatsEvent';
|
|
import { StatID } from '../enums/StatID';
|
|
import { CoarseLocationUpdateMessage } from './messages/CoarseLocationUpdate';
|
|
import { Avatar } from './public/Avatar';
|
|
import { MoneyBalanceReplyMessage } from './messages/MoneyBalanceReply';
|
|
import { BalanceUpdatedEvent } from '../events/BalanceUpdatedEvent';
|
|
|
|
export class Region
|
|
{
|
|
static CopyMatrix16: number[] = [];
|
|
static CosineTable16: number[] = [];
|
|
static DequantizeTable16: number[] = [];
|
|
static setup = false;
|
|
static OO_SQRT_2 = 0.7071067811865475244008443621049;
|
|
|
|
regionName: string;
|
|
regionOwner: UUID;
|
|
regionID: UUID;
|
|
regionSizeX = 256;
|
|
regionSizeY = 256;
|
|
regionHandle: Long;
|
|
xCoordinate: number;
|
|
yCoordinate: number;
|
|
estateID: number;
|
|
parentEstateID: number;
|
|
regionFlags: RegionFlags;
|
|
mapImage: UUID;
|
|
|
|
simAccess: number;
|
|
maxAgents: number;
|
|
billableFactor: number;
|
|
objectBonusFactor: number;
|
|
waterHeight: number;
|
|
terrainRaiseLimit: number;
|
|
terrainLowerLimit: number;
|
|
pricePerMeter: number;
|
|
redirectGridX: number;
|
|
redirectGridY: number;
|
|
useEstateSun: boolean;
|
|
sunHour: number;
|
|
productSKU: string;
|
|
productName: string;
|
|
maxAgents32: number;
|
|
hardMaxAgents: number;
|
|
hardMaxObjects: number;
|
|
cacheID: UUID;
|
|
cpuClassID: number;
|
|
cpuRatio: number;
|
|
coloName: string;
|
|
|
|
terrainBase0: UUID;
|
|
terrainBase1: UUID;
|
|
terrainBase2: UUID;
|
|
terrainBase3: UUID;
|
|
terrainDetail0: UUID;
|
|
terrainDetail1: UUID;
|
|
terrainDetail2: UUID;
|
|
terrainDetail3: UUID;
|
|
terrainStartHeight00: number;
|
|
terrainStartHeight01: number;
|
|
terrainStartHeight10: number;
|
|
terrainStartHeight11: number;
|
|
terrainHeightRange00: number;
|
|
terrainHeightRange01: number;
|
|
terrainHeightRange10: number;
|
|
terrainHeightRange11: number;
|
|
|
|
handshakeComplete = false;
|
|
handshakeCompleteEvent: Subject<void> = new Subject<void>();
|
|
|
|
circuit: Circuit;
|
|
objects: IObjectStore;
|
|
caps: Caps;
|
|
comms: Comms;
|
|
clientEvents: ClientEvents;
|
|
clientCommands: ClientCommands;
|
|
options: BotOptionFlags;
|
|
agent: Agent;
|
|
messageSubscription: Subscription;
|
|
parcelPropertiesSubscription: Subscription;
|
|
|
|
terrain: number[][] = [];
|
|
tilesReceived = 0;
|
|
terrainComplete = false;
|
|
terrainCompleteEvent: Subject<void> = new Subject<void>();
|
|
|
|
parcelsComplete = false;
|
|
parcelsCompleteEvent: Subject<void> = new Subject<void>();
|
|
|
|
parcelOverlayComplete = false;
|
|
parcelOverlayCompleteEvent: Subject<void> = new Subject<void>();
|
|
|
|
parcelOverlay: ILandBlock[] = [];
|
|
parcels: {[key: number]: Parcel} = {};
|
|
parcelsByUUID: {[key: string]: Parcel} = {};
|
|
parcelMap: number[][] = [];
|
|
|
|
|
|
parcelCoordinates: {x: number, y: number}[] = [];
|
|
|
|
environment: RegionEnvironment;
|
|
|
|
timeOffset = 0;
|
|
|
|
resolver: ObjectResolver = new ObjectResolver(this);
|
|
|
|
agents: {[key: string]: Avatar} = {};
|
|
|
|
private parcelOverlayReceived: {[key: number]: Buffer} = {};
|
|
|
|
static IDCTColumn16(linein: number[], lineout: number[], column: number): void
|
|
{
|
|
let total: number;
|
|
let usize: number;
|
|
|
|
for (let n = 0; n < 16; n++)
|
|
{
|
|
total = this.OO_SQRT_2 * linein[column];
|
|
|
|
for (let u = 1; u < 16; u++)
|
|
{
|
|
usize = u * 16;
|
|
total += linein[usize + column] * this.CosineTable16[usize + n];
|
|
}
|
|
|
|
lineout[16 * n + column] = total;
|
|
}
|
|
}
|
|
|
|
static IDCTLine16(linein: number[], lineout: number[], line: number): void
|
|
{
|
|
const oosob: number = 2.0 / 16.0;
|
|
const lineSize: number = line * 16;
|
|
let total = 0;
|
|
|
|
for (let n = 0; n < 16; n++)
|
|
{
|
|
total = this.OO_SQRT_2 * linein[lineSize];
|
|
|
|
for (let u = 1; u < 16; u++)
|
|
{
|
|
total += linein[lineSize + u] * this.CosineTable16[u * 16 + n];
|
|
}
|
|
|
|
lineout[lineSize + n] = total * oosob;
|
|
}
|
|
}
|
|
|
|
static InitialSetup(): void
|
|
{
|
|
// Build copy matrix 16
|
|
{
|
|
let diag = false;
|
|
let right = true;
|
|
let i = 0;
|
|
let j = 0;
|
|
let count = 0;
|
|
|
|
for (let x = 0; x < 16 * 16; x++)
|
|
{
|
|
this.CopyMatrix16.push(0);
|
|
this.DequantizeTable16.push(0);
|
|
this.CosineTable16.push(0);
|
|
}
|
|
while (i < 16 && j < 16)
|
|
{
|
|
this.CopyMatrix16[j * 16 + i] = count++;
|
|
|
|
if (!diag)
|
|
{
|
|
if (right)
|
|
{
|
|
if (i < 16 - 1)
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
|
|
right = false;
|
|
diag = true;
|
|
}
|
|
else
|
|
{
|
|
if (j < 16 - 1)
|
|
{
|
|
j++;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
|
|
right = true;
|
|
diag = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (right)
|
|
{
|
|
i++;
|
|
j--;
|
|
if (i === 16 - 1 || j === 0)
|
|
{
|
|
diag = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i--;
|
|
j++;
|
|
if (j === 16 - 1 || i === 0)
|
|
{
|
|
diag = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
for (let j = 0; j < 16; j++)
|
|
{
|
|
for (let i = 0; i < 16; i++)
|
|
{
|
|
this.DequantizeTable16[j * 16 + i] = 1.0 + 2.0 * (i + j);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
const hposz: number = Math.PI * 0.5 / 16.0;
|
|
|
|
for (let u = 0; u < 16; u++)
|
|
{
|
|
for (let n = 0; n < 16; n++)
|
|
{
|
|
this.CosineTable16[u * 16 + n] = Math.cos((2.0 * n + 1.0) * u * hposz);
|
|
}
|
|
}
|
|
}
|
|
this.setup = true;
|
|
}
|
|
|
|
private static doesBitmapContainCoordinate(bitmap: Buffer, x: number, y: number): boolean
|
|
{
|
|
const mapBlockX = Math.floor(x / 4);
|
|
const mapBlockY = Math.floor(y / 4);
|
|
|
|
let index = (mapBlockY * 64) + mapBlockX;
|
|
const bit = index % 8;
|
|
index >>= 3;
|
|
return ((bitmap[index] & (1 << bit)) !== 0);
|
|
}
|
|
|
|
constructor(agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags)
|
|
{
|
|
if (!Region.setup)
|
|
{
|
|
Region.InitialSetup();
|
|
}
|
|
for (let x = 0; x < 256; x++)
|
|
{
|
|
this.terrain.push([]);
|
|
for (let y = 0; y < 256; y++)
|
|
{
|
|
this.terrain[x].push(-1);
|
|
}
|
|
}
|
|
for (let x = 0; x < 64; x++)
|
|
{
|
|
this.parcelMap.push([]);
|
|
for (let y = 0; y < 64; y++)
|
|
{
|
|
this.parcelMap[x].push(0);
|
|
}
|
|
}
|
|
this.agent = agent;
|
|
this.options = options;
|
|
this.clientEvents = clientEvents;
|
|
this.circuit = new Circuit();
|
|
if (options & BotOptionFlags.LiteObjectStore)
|
|
{
|
|
this.objects = new ObjectStoreLite(this.circuit, agent, clientEvents, options);
|
|
}
|
|
else
|
|
{
|
|
this.objects = new ObjectStoreFull(this.circuit, agent, clientEvents, options);
|
|
}
|
|
this.comms = new Comms(this.circuit, clientEvents);
|
|
|
|
this.parcelPropertiesSubscription = this.clientEvents.onParcelPropertiesEvent.subscribe(async (parcelProperties: ParcelPropertiesEvent) =>
|
|
{
|
|
await this.resolveParcel(parcelProperties);
|
|
});
|
|
|
|
this.messageSubscription = this.circuit.subscribeToMessages([
|
|
Message.ParcelOverlay,
|
|
Message.LayerData,
|
|
Message.SimulatorViewerTimeMessage,
|
|
Message.SimStats,
|
|
Message.CoarseLocationUpdate,
|
|
Message.MoneyBalanceReply
|
|
], async (packet: Packet) =>
|
|
{
|
|
switch (packet.message.id)
|
|
{
|
|
case Message.MoneyBalanceReply:
|
|
{
|
|
const msg = packet.message as MoneyBalanceReplyMessage;
|
|
const evt = new BalanceUpdatedEvent();
|
|
if (msg.TransactionInfo.Amount === -1)
|
|
{
|
|
// This is a requested balance update, so don't sent an event
|
|
return;
|
|
}
|
|
evt.balance = msg.MoneyData.MoneyBalance;
|
|
evt.transaction = {
|
|
type: msg.TransactionInfo.TransactionType,
|
|
amount: msg.TransactionInfo.Amount,
|
|
from: msg.TransactionInfo.SourceID,
|
|
to: msg.TransactionInfo.DestID,
|
|
success: msg.MoneyData.TransactionSuccess,
|
|
fromGroup: msg.TransactionInfo.IsSourceGroup,
|
|
toGroup: msg.TransactionInfo.IsDestGroup,
|
|
description: Utils.BufferToStringSimple(msg.TransactionInfo.ItemDescription)
|
|
}
|
|
this.clientEvents.onBalanceUpdated.next(evt);
|
|
break;
|
|
}
|
|
case Message.CoarseLocationUpdate:
|
|
{
|
|
const locations: CoarseLocationUpdateMessage = packet.message as CoarseLocationUpdateMessage;
|
|
const foundAgents: {[key: string]: Vector3} = {};
|
|
for (let x = 0; x < locations.AgentData.length; x++)
|
|
{
|
|
const agentData = locations.AgentData[x];
|
|
const location = locations.Location[x];
|
|
const newPosition = new Vector3([location.X, location.Y, location.Z * 4]);
|
|
foundAgents[agentData.AgentID.toString()] = newPosition;
|
|
|
|
if (this.agents[agentData.AgentID.toString()] === undefined)
|
|
{
|
|
let resolved = await this.clientCommands.grid.avatarKey2Name(agentData.AgentID);
|
|
if (Array.isArray(resolved))
|
|
{
|
|
resolved = resolved[0];
|
|
}
|
|
if (this.agents[agentData.AgentID.toString()] === undefined)
|
|
{
|
|
this.agents[agentData.AgentID.toString()] = new Avatar(agentData.AgentID, resolved.getFirstName(), resolved.getLastName());
|
|
this.clientEvents.onAvatarEnteredRegion.next(this.agents[agentData.AgentID.toString()]);
|
|
}
|
|
else
|
|
{
|
|
this.agents[agentData.AgentID.toString()].coarsePosition = newPosition;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.agents[agentData.AgentID.toString()].coarsePosition = newPosition;
|
|
}
|
|
}
|
|
const keys = Object.keys(this.agents)
|
|
for (const agentID of keys)
|
|
{
|
|
if (foundAgents[agentID] === undefined)
|
|
{
|
|
this.agents[agentID].coarseLeftRegion();
|
|
delete this.agents[agentID];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Message.SimStats:
|
|
{
|
|
const stats: SimStatsMessage = packet.message as SimStatsMessage;
|
|
if (stats.Stat.length > 0)
|
|
{
|
|
const evt = new SimStatsEvent();
|
|
for (const pair of stats.Stat)
|
|
{
|
|
const value = pair.StatValue;
|
|
switch (pair.StatID)
|
|
{
|
|
case StatID.TimeDilation:
|
|
evt.timeDilation = value;
|
|
break;
|
|
case StatID.FPS:
|
|
evt.fps = value;
|
|
break;
|
|
case StatID.PhysFPS:
|
|
evt.physFPS = value;
|
|
break;
|
|
case StatID.AgentUPS:
|
|
evt.agentUPS = value;
|
|
break;
|
|
case StatID.FrameMS:
|
|
evt.frameMS = value;
|
|
break;
|
|
case StatID.NetMS:
|
|
evt.netMS = value;
|
|
break;
|
|
case StatID.SimOtherMS:
|
|
evt.simOtherMS = value;
|
|
break;
|
|
case StatID.SimPhysicsMS:
|
|
evt.simPhysicsMS = value;
|
|
break;
|
|
case StatID.AgentMS:
|
|
evt.agentMS = value;
|
|
break;
|
|
case StatID.ImagesMS:
|
|
evt.imagesMS = value;
|
|
break;
|
|
case StatID.ScriptMS:
|
|
evt.scriptMS = value;
|
|
break;
|
|
case StatID.NumTasks:
|
|
evt.numTasks = value;
|
|
break;
|
|
case StatID.NumTasksActive:
|
|
evt.numTasksActive = value;
|
|
break;
|
|
case StatID.NumAgentMain:
|
|
evt.numAgentMain = value;
|
|
break;
|
|
case StatID.NumAgentChild:
|
|
evt.numAgentChild = value;
|
|
break;
|
|
case StatID.NumScriptsActive:
|
|
evt.numScriptsActive = value;
|
|
break;
|
|
case StatID.LSLIPS:
|
|
evt.lslIPS = value;
|
|
break;
|
|
case StatID.InPPS:
|
|
evt.inPPS = value;
|
|
break;
|
|
case StatID.OutPPS:
|
|
evt.outPPS = value;
|
|
break;
|
|
case StatID.PendingDownloads:
|
|
evt.pendingDownloads = value;
|
|
break;
|
|
case StatID.PendingUploads:
|
|
evt.pendingUploads = value;
|
|
break;
|
|
case StatID.VirtualSizeKB:
|
|
evt.virtualSizeKB = value;
|
|
break;
|
|
case StatID.ResidentSizeKB:
|
|
evt.residentSizeKB = value;
|
|
break;
|
|
case StatID.PendingLocalUploads:
|
|
evt.pendingLocalUploads = value;
|
|
break;
|
|
case StatID.TotalUnackedBytes:
|
|
evt.totalUnackedBytes = value;
|
|
break;
|
|
case StatID.PhysicsPinnedTasks:
|
|
evt.physicsPinnedTasks = value;
|
|
break;
|
|
case StatID.PhysicsLODTasks:
|
|
evt.physicsLODTasks = value;
|
|
break;
|
|
case StatID.SimPhysicsStepMS:
|
|
evt.simPhysicsStepMS = value;
|
|
break;
|
|
case StatID.SimPhysicsShapeMS:
|
|
evt.simPhysicsShapeMS = value;
|
|
break;
|
|
case StatID.SimPhysicsOtherMS:
|
|
evt.simPhysicsOtherMS = value;
|
|
break;
|
|
case StatID.SimPhysicsMemory:
|
|
evt.simPhysicsMemory = value;
|
|
break;
|
|
case StatID.ScriptEPS:
|
|
evt.scriptEPS = value;
|
|
break;
|
|
case StatID.SimSpareTime:
|
|
evt.simSpareTime = value;
|
|
break;
|
|
case StatID.SimSleepTime:
|
|
evt.simSleepTime = value;
|
|
break;
|
|
case StatID.IOPumpTime:
|
|
evt.ioPumpTime = value;
|
|
break;
|
|
case StatID.PCTScriptsRun:
|
|
evt.pctScriptsRun = value;
|
|
break;
|
|
case StatID.RegionIdle:
|
|
evt.regionIdle = value;
|
|
break;
|
|
case StatID.RegionIdlePossible:
|
|
evt.regionIdlePossible = value;
|
|
break;
|
|
case StatID.SimAIStepTimeMS:
|
|
evt.simAIStepTimeMS = value;
|
|
break;
|
|
case StatID.SkippedAISilStepsPS:
|
|
evt.skippedAISilStepsPS = value;
|
|
break;
|
|
case StatID.PCTSteppedCharacters:
|
|
evt.pctSteppedCharacters = value;
|
|
break;
|
|
}
|
|
this.clientEvents.onSimStats.next(evt);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Message.ParcelOverlay:
|
|
{
|
|
const parcelData: ParcelOverlayMessage = packet.message as ParcelOverlayMessage;
|
|
const sequence = parcelData.ParcelData.SequenceID;
|
|
|
|
if (this.parcelOverlayReceived[sequence] !== undefined)
|
|
{
|
|
this.parcelOverlayReceived = {};
|
|
}
|
|
|
|
this.parcelOverlayReceived[sequence] = parcelData.ParcelData.Data;
|
|
let totalLength = 0;
|
|
let highestSeq = 0;
|
|
for (const seq of Object.keys(this.parcelOverlayReceived))
|
|
{
|
|
const sequenceID: number = parseInt(seq, 10);
|
|
totalLength += this.parcelOverlayReceived[sequenceID].length;
|
|
if (sequenceID > highestSeq)
|
|
{
|
|
highestSeq = sequenceID;
|
|
}
|
|
}
|
|
if (totalLength !== (this.regionSizeX / 4) * (this.regionSizeY / 4))
|
|
{
|
|
// Overlay is incomplete
|
|
return;
|
|
}
|
|
for (let x = 0; x <= highestSeq; x++)
|
|
{
|
|
if (this.parcelOverlayReceived[x] === undefined)
|
|
{
|
|
// Overlay is incomplete
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.parcelOverlay = [];
|
|
for (let seq = 0; seq <= highestSeq; seq++)
|
|
{
|
|
const data = this.parcelOverlayReceived[seq];
|
|
for (let x = 0; x < data.length; x++)
|
|
{
|
|
const block = data.readUInt8(x);
|
|
this.parcelOverlay.push({
|
|
landType: block & 0xF,
|
|
landFlags: block & ~ 0xF,
|
|
parcelID: -1
|
|
});
|
|
}
|
|
}
|
|
|
|
this.parcelCoordinates = [];
|
|
let currentParcelID = 0;
|
|
for (let y = 63; y > -1; y--)
|
|
{
|
|
for (let x = 63; x > -1; x--)
|
|
{
|
|
if (this.parcelOverlay[(y * 64) + x].parcelID === -1)
|
|
{
|
|
this.parcelCoordinates.push({x, y});
|
|
currentParcelID++;
|
|
this.fillParcel(currentParcelID, x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!this.parcelOverlayComplete)
|
|
{
|
|
this.parcelOverlayComplete = true;
|
|
this.parcelOverlayCompleteEvent.next();
|
|
}
|
|
|
|
this.parcelOverlayReceived = {};
|
|
break;
|
|
}
|
|
case Message.LayerData:
|
|
{
|
|
const layerData: LayerDataMessage = packet.message as LayerDataMessage;
|
|
const type: LayerType = layerData.LayerID.Type;
|
|
|
|
const nibbler = new BitPack(layerData.LayerData.Data, 0);
|
|
|
|
// stride - unused for now
|
|
nibbler.UnpackBits(16);
|
|
const patchSize = nibbler.UnpackBits(8);
|
|
const headerLayerType = nibbler.UnpackBits(8);
|
|
|
|
switch (type)
|
|
{
|
|
case LayerType.Land:
|
|
if (headerLayerType === type) // Quick sanity check
|
|
{
|
|
let x = 0;
|
|
let y = 0;
|
|
const patches: number[] = [];
|
|
for (let xi = 0; xi < 32 * 32; xi++)
|
|
{
|
|
patches.push(0);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
// DecodePatchHeader
|
|
const quantWBits = nibbler.UnpackBits(8);
|
|
if (quantWBits === 97)
|
|
{
|
|
break;
|
|
}
|
|
const dcOffset = nibbler.UnpackFloat();
|
|
const range = nibbler.UnpackBits(16);
|
|
const patchIDs = nibbler.UnpackBits(10);
|
|
const wordBits = (quantWBits & 0x0f) + 2;
|
|
|
|
x = patchIDs >> 5;
|
|
y = patchIDs & 0x1F;
|
|
if (x >= 16 || y >= 16)
|
|
{
|
|
console.error('Invalid land packet. x: ' + x + ', y: ' + y + ', patchSize: ' + patchSize);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Decode patch
|
|
let temp = 0;
|
|
for (let n = 0; n < patchSize * patchSize; n++)
|
|
{
|
|
temp = nibbler.UnpackBits(1);
|
|
if (temp !== 0)
|
|
{
|
|
temp = nibbler.UnpackBits(1);
|
|
if (temp !== 0)
|
|
{
|
|
temp = nibbler.UnpackBits(1);
|
|
if (temp !== 0)
|
|
{
|
|
// negative
|
|
temp = nibbler.UnpackBits(wordBits);
|
|
patches[n] = temp * -1;
|
|
|
|
}
|
|
else
|
|
{
|
|
// positive
|
|
temp = nibbler.UnpackBits(wordBits);
|
|
patches[n] = temp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (let o = n; o < patchSize * patchSize; o++)
|
|
{
|
|
patches[o] = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
patches[n] = 0;
|
|
}
|
|
}
|
|
|
|
// Decompress this patch
|
|
const block: number[] = [];
|
|
const output: number[] = [];
|
|
|
|
const prequant = (quantWBits >> 4) + 2;
|
|
const quantize = 1 << prequant;
|
|
const ooq = 1.0 / quantize;
|
|
const mult = ooq * range;
|
|
const addVal = mult * (1 << (prequant - 1)) + dcOffset;
|
|
|
|
if (patchSize === 16)
|
|
{
|
|
for (let n = 0; n < 16 * 16; n++)
|
|
{
|
|
block.push(patches[Region.CopyMatrix16[n]] * Region.DequantizeTable16[n])
|
|
}
|
|
|
|
const ftemp: number[] = [];
|
|
for (let o = 0; o < 16 * 16; o++)
|
|
{
|
|
ftemp.push(o);
|
|
}
|
|
for (let o = 0; o < 16; o++)
|
|
{
|
|
Region.IDCTColumn16(block, ftemp, o);
|
|
}
|
|
for (let o = 0; o < 16; o++)
|
|
{
|
|
Region.IDCTLine16(ftemp, block, o);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Error('IDCTPatchLarge not implemented');
|
|
}
|
|
|
|
for (let j = 0; j < block.length; j++)
|
|
{
|
|
output.push(block[j] * mult + addVal);
|
|
}
|
|
|
|
let outputIndex = 0;
|
|
for (let yPoint = y * 16; yPoint < (y + 1) * 16; yPoint++)
|
|
{
|
|
for (let xPoint = x * 16; xPoint < (x + 1) * 16; xPoint++)
|
|
{
|
|
if (this.terrain[yPoint][xPoint] === -1)
|
|
{
|
|
this.tilesReceived++;
|
|
}
|
|
this.terrain[yPoint][xPoint] = output[outputIndex++];
|
|
}
|
|
}
|
|
|
|
if (this.tilesReceived === 65536)
|
|
{
|
|
this.terrainComplete = true;
|
|
this.terrainCompleteEvent.next();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case Message.SimulatorViewerTimeMessage:
|
|
{
|
|
const msg = packet.message as SimulatorViewerTimeMessageMessage;
|
|
const timeStamp = msg.TimeInfo.UsecSinceStart.toNumber() / 1000000;
|
|
this.timeOffset = (new Date().getTime() / 1000) - timeStamp;
|
|
break;
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
private async resolveParcel(parcelProperties: ParcelPropertiesEvent): Promise<Parcel>
|
|
{
|
|
// Get the parcel UUID
|
|
const msg = new ParcelDwellRequestMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
msg.Data = {
|
|
LocalID: parcelProperties.LocalID,
|
|
ParcelID: UUID.zero()
|
|
};
|
|
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
const dwellReply = await this.circuit.waitForMessage<ParcelDwellReplyMessage>(Message.ParcelDwellReply, 10000, (message: ParcelDwellReplyMessage): FilterResponse =>
|
|
{
|
|
if (message.Data.LocalID === parcelProperties.LocalID)
|
|
{
|
|
return FilterResponse.Finish;
|
|
}
|
|
else
|
|
{
|
|
return FilterResponse.NoMatch;
|
|
}
|
|
});
|
|
const parcelID: string = dwellReply.Data.ParcelID.toString();
|
|
let parcel = new Parcel(this);
|
|
if (this.parcelsByUUID[parcelID])
|
|
{
|
|
parcel = this.parcelsByUUID[parcelID];
|
|
}
|
|
parcel.LocalID = parcelProperties.LocalID;
|
|
parcel.ParcelID = new UUID(dwellReply.Data.ParcelID.toString());
|
|
parcel.RegionDenyAgeUnverified = parcelProperties.RegionDenyTransacted;
|
|
parcel.MediaDesc = parcelProperties.MediaDesc;
|
|
parcel.MediaHeight = parcelProperties.MediaHeight;
|
|
parcel.MediaLoop = parcelProperties.MediaLoop;
|
|
parcel.MediaType = parcelProperties.MediaType;
|
|
parcel.MediaWidth = parcelProperties.MediaWidth;
|
|
parcel.ObscureMedia = parcelProperties.ObscureMedia;
|
|
parcel.ObscureMusic = parcelProperties.ObscureMusic;
|
|
parcel.AABBMax = parcelProperties.AABBMax;
|
|
parcel.AABBMin = parcelProperties.AABBMin;
|
|
parcel.AnyAVSounds = parcelProperties.AnyAVSounds;
|
|
parcel.Area = parcelProperties.Area;
|
|
parcel.AuctionID = parcelProperties.AuctionID;
|
|
parcel.AuthBuyerID = new UUID(parcelProperties.AuthBuyerID.toString());
|
|
parcel.Bitmap = parcelProperties.Bitmap;
|
|
parcel.Category = parcelProperties.Category;
|
|
parcel.ClaimDate = parcelProperties.ClaimDate;
|
|
parcel.ClaimPrice = parcelProperties.ClaimPrice;
|
|
parcel.Desc = parcelProperties.Desc;
|
|
parcel.Dwell = dwellReply.Data.Dwell;
|
|
parcel.GroupAVSounds = parcelProperties.GroupAVSounds;
|
|
parcel.GroupID = new UUID(parcelProperties.GroupID.toString());
|
|
parcel.GroupPrims = parcelProperties.GroupPrims;
|
|
parcel.IsGroupOwned = parcelProperties.IsGroupOwned;
|
|
parcel.LandingType = parcelProperties.LandingType;
|
|
parcel.MaxPrims = parcelProperties.MaxPrims;
|
|
parcel.MediaAutoScale = parcelProperties.MediaAutoScale;
|
|
parcel.MediaID = new UUID(parcelProperties.MediaID.toString());
|
|
parcel.MediaURL = parcelProperties.MediaURL;
|
|
parcel.MusicURL = parcelProperties.MusicURL;
|
|
parcel.Name = parcelProperties.Name;
|
|
parcel.OtherCleanTime = parcelProperties.OtherCleanTime;
|
|
parcel.OtherCount = parcelProperties.OtherCount;
|
|
parcel.OtherPrims = parcelProperties.OtherPrims;
|
|
parcel.OwnerID = new UUID(parcelProperties.OwnerID.toString());
|
|
parcel.OwnerPrims = parcelProperties.OwnerPrims;
|
|
parcel.ParcelFlags = parcelProperties.ParcelFlags;
|
|
parcel.ParcelPrimBonus = parcelProperties.ParcelPrimBonus;
|
|
parcel.PassHours = parcelProperties.PassHours;
|
|
parcel.PassPrice = parcelProperties.PassPrice;
|
|
parcel.PublicCount = parcelProperties.PublicCount;
|
|
parcel.RegionDenyAnonymous = parcelProperties.RegionDenyAnonymous;
|
|
parcel.RegionDenyIdentified = parcelProperties.RegionDenyIdentified;
|
|
parcel.RegionPushOverride = parcelProperties.RegionPushOverride;
|
|
parcel.RegionDenyTransacted = parcelProperties.RegionDenyTransacted;
|
|
parcel.RentPrice = parcelProperties.RentPrice;
|
|
parcel.RequestResult = parcelProperties.RequestResult;
|
|
parcel.SalePrice = parcelProperties.SalePrice;
|
|
parcel.SeeAvs = parcelProperties.SeeAvs;
|
|
parcel.SelectedPrims = parcelProperties.SelectedPrims;
|
|
parcel.SelfCount = parcelProperties.SelfCount;
|
|
parcel.SequenceID = parcelProperties.SequenceID;
|
|
parcel.SimWideMaxPrims = parcelProperties.SimWideMaxPrims;
|
|
parcel.SimWideTotalPrims = parcelProperties.SimWideTotalPrims;
|
|
parcel.SnapSelection = parcelProperties.SnapSelection;
|
|
parcel.SnapshotID = new UUID(parcelProperties.SnapshotID.toString());
|
|
parcel.Status = parcelProperties.Status;
|
|
parcel.TotalPrims = parcelProperties.TotalPrims;
|
|
parcel.UserLocation = parcelProperties.UserLocation;
|
|
parcel.UserLookAt = parcelProperties.UserLookAt;
|
|
parcel.RegionAllowAccessOverride = parcelProperties.RegionAllowAccessOverride;
|
|
this.parcels[parcelProperties.LocalID] = parcel;
|
|
|
|
let foundEmpty = false;
|
|
for (let y = 0; y < 64; y++)
|
|
{
|
|
for (let x = 0; x < 64; x++)
|
|
{
|
|
if (Region.doesBitmapContainCoordinate(parcel.Bitmap, x * 4, y * 4))
|
|
{
|
|
this.parcelMap[y][x] = parcel.LocalID;
|
|
}
|
|
else
|
|
{
|
|
if (this.parcelMap[y][x] === 0)
|
|
{
|
|
foundEmpty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!foundEmpty)
|
|
{
|
|
if (!this.parcelsComplete)
|
|
{
|
|
this.parcelsComplete = true;
|
|
this.parcelsCompleteEvent.next();
|
|
}
|
|
}
|
|
else if (this.parcelsComplete)
|
|
{
|
|
this.parcelsComplete = false;
|
|
}
|
|
return parcel;
|
|
}
|
|
|
|
/* // This was useful for debugging, so leaving it here for the future
|
|
private drawParcelMap()
|
|
{
|
|
console.log('====================================================');
|
|
for (let y2 = 63; y2 > -1; y2--)
|
|
{
|
|
let row = '';
|
|
for (let x2 = 0; x2 < 64; x2++)
|
|
{
|
|
const parcelID = this.parcelOverlay[(y2 * 64) + x2].parcelID;
|
|
if (parcelID === -1)
|
|
{
|
|
row += 'X';
|
|
}
|
|
else if (parcelID < 10)
|
|
{
|
|
row += String(parcelID);
|
|
}
|
|
else
|
|
{
|
|
row += '#';
|
|
}
|
|
}
|
|
console.log(row);
|
|
}
|
|
}
|
|
*/
|
|
|
|
private fillParcel(parcelID: number, x: number, y: number): void
|
|
{
|
|
if ( x < 0 || y < 0 || x > 63 || y > 63)
|
|
{
|
|
return;
|
|
}
|
|
if (this.parcelOverlay[(y * 64) + x].parcelID !== -1)
|
|
{
|
|
return;
|
|
}
|
|
this.parcelOverlay[(y * 64) + x].parcelID = parcelID;
|
|
const flags = this.parcelOverlay[(y * 64) + x].landFlags;
|
|
if (!(flags & LandFlags.BorderSouth))
|
|
{
|
|
this.fillParcel(parcelID, x, y - 1);
|
|
}
|
|
if (!(flags & LandFlags.BorderWest))
|
|
{
|
|
this.fillParcel(parcelID, x - 1, y);
|
|
}
|
|
if (x < 63 && !(this.parcelOverlay[(y * 64) + (x + 1)].landFlags & LandFlags.BorderWest))
|
|
{
|
|
this.fillParcel(parcelID, x + 1, y);
|
|
}
|
|
if (y < 63 && !(this.parcelOverlay[((y + 1) * 64) + x].landFlags & LandFlags.BorderSouth))
|
|
{
|
|
this.fillParcel(parcelID, x, y + 1);
|
|
}
|
|
}
|
|
|
|
public getParcelProperties(x: number, y: number): Promise<Parcel>
|
|
{
|
|
return new Promise<Parcel>((resolve, reject) =>
|
|
{
|
|
const request = new ParcelPropertiesRequestMessage();
|
|
request.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
request.ParcelData = {
|
|
North: y + 1,
|
|
East: x + 1,
|
|
South: y,
|
|
West: x,
|
|
SequenceID: -10000,
|
|
SnapSelection: false
|
|
};
|
|
this.circuit.sendMessage(request, PacketFlags.Reliable);
|
|
let messageAwait: Subscription | undefined = undefined;
|
|
let messageWaitTimer: number | undefined = undefined;
|
|
|
|
messageAwait = this.clientEvents.onParcelPropertiesEvent.subscribe(async (parcelProperties: ParcelPropertiesEvent) =>
|
|
{
|
|
if (Region.doesBitmapContainCoordinate(parcelProperties.Bitmap, x, y))
|
|
{
|
|
if (messageAwait !== undefined)
|
|
{
|
|
messageAwait.unsubscribe();
|
|
messageAwait = undefined;
|
|
}
|
|
if (messageWaitTimer !== undefined)
|
|
{
|
|
clearTimeout(messageWaitTimer);
|
|
messageWaitTimer = undefined;
|
|
}
|
|
resolve(await this.resolveParcel(parcelProperties));
|
|
}
|
|
});
|
|
|
|
messageWaitTimer = setTimeout(() =>
|
|
{
|
|
if (messageAwait !== undefined)
|
|
{
|
|
messageAwait.unsubscribe();
|
|
messageAwait = undefined;
|
|
}
|
|
if (messageWaitTimer !== undefined)
|
|
{
|
|
clearTimeout(messageWaitTimer);
|
|
messageWaitTimer = undefined;
|
|
}
|
|
reject(new Error('Timed out'));
|
|
}, 10000) as any as number;
|
|
});
|
|
}
|
|
|
|
async getParcels(): Promise<Parcel[]>
|
|
{
|
|
await this.waitForParcelOverlay();
|
|
const parcels: Parcel[] = [];
|
|
for (const parcel of this.parcelCoordinates)
|
|
{
|
|
try
|
|
{
|
|
parcels.push(await this.getParcelProperties(parcel.x * 4.0, parcel.y * 4.0));
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error(error);
|
|
}
|
|
}
|
|
return parcels;
|
|
}
|
|
|
|
resetParcels(): void
|
|
{
|
|
this.parcelMap = [];
|
|
for (let x = 0; x < 64; x++)
|
|
{
|
|
this.parcelMap.push([]);
|
|
for (let y = 0; y < 64; y++)
|
|
{
|
|
this.parcelMap[x].push(0);
|
|
}
|
|
}
|
|
this.parcels = {};
|
|
this.parcelsByUUID = {};
|
|
this.parcelsComplete = false;
|
|
}
|
|
|
|
waitForParcelOverlay(): Promise<void>
|
|
{
|
|
return new Promise<void>((resolve, reject) =>
|
|
{
|
|
if (this.parcelOverlayComplete)
|
|
{
|
|
resolve();
|
|
}
|
|
else
|
|
{
|
|
let timeout: Timer | null = null;
|
|
const subscription = this.parcelOverlayCompleteEvent.subscribe(() =>
|
|
{
|
|
if (timeout !== null)
|
|
{
|
|
clearTimeout(timeout);
|
|
}
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
});
|
|
timeout = setTimeout(() =>
|
|
{
|
|
subscription.unsubscribe();
|
|
reject(new Error('Timeout waiting for parcel overlay'));
|
|
}, 10000);
|
|
}
|
|
});
|
|
}
|
|
|
|
waitForParcels(): Promise<void>
|
|
{
|
|
return new Promise<void>((resolve, reject) =>
|
|
{
|
|
if (this.parcelsComplete)
|
|
{
|
|
resolve();
|
|
}
|
|
else
|
|
{
|
|
let timeout: Timer | null = null;
|
|
const subscription = this.parcelsCompleteEvent.subscribe(() =>
|
|
{
|
|
if (timeout !== null)
|
|
{
|
|
clearTimeout(timeout);
|
|
}
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
});
|
|
timeout = setTimeout(() =>
|
|
{
|
|
subscription.unsubscribe();
|
|
reject(new Error('Timeout waiting for parcels'));
|
|
}, 10000);
|
|
}
|
|
});
|
|
}
|
|
|
|
waitForTerrain(): Promise<void>
|
|
{
|
|
return new Promise<void>((resolve, reject) =>
|
|
{
|
|
if (this.terrainComplete)
|
|
{
|
|
resolve();
|
|
}
|
|
else
|
|
{
|
|
let timeout: Timer | null = null;
|
|
const subscription = this.terrainCompleteEvent.subscribe(() =>
|
|
{
|
|
if (timeout !== null)
|
|
{
|
|
clearTimeout(timeout);
|
|
}
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
});
|
|
timeout = setTimeout(() =>
|
|
{
|
|
subscription.unsubscribe();
|
|
reject(new Error('Timeout waiting for terrain'));
|
|
}, 10000);
|
|
}
|
|
});
|
|
}
|
|
|
|
getTerrainHeightAtPoint(x: number, y: number): number
|
|
{
|
|
const patchX = Math.floor(x / 16);
|
|
const patchY = Math.floor(y / 16);
|
|
x = x % 16;
|
|
y = y % 16;
|
|
|
|
const p = this.terrain[patchY * 16 + patchX];
|
|
if (p === null)
|
|
{
|
|
return 0;
|
|
}
|
|
return p[y * 16 + x];
|
|
}
|
|
|
|
exportXML(): string
|
|
{
|
|
const document = builder.create('RegionSettings');
|
|
const general = document.ele('General');
|
|
general.ele('AllowDamage', (this.regionFlags & RegionFlags.AllowDamage) ? 'True' : 'False');
|
|
general.ele('AllowLandResell', !(this.regionFlags & RegionFlags.BlockLandResell) ? 'True' : 'False');
|
|
general.ele('AllowLandJoinDivide', (this.regionFlags & RegionFlags.AllowParcelChanges) ? 'True' : 'False');
|
|
general.ele('BlockFly', (this.regionFlags & RegionFlags.NoFly) ? 'True' : 'False');
|
|
general.ele('BlockLandShowInSearch', (this.regionFlags & RegionFlags.BlockParcelSearch) ? 'True' : 'False');
|
|
general.ele('BlockTerraform', (this.regionFlags & RegionFlags.BlockTerraform) ? 'True' : 'False');
|
|
general.ele('DisableCollisions', (this.regionFlags & RegionFlags.SkipCollisions) ? 'True' : 'False');
|
|
general.ele('DisablePhysics', (this.regionFlags & RegionFlags.SkipPhysics) ? 'True' : 'False');
|
|
general.ele('DisableScripts', (this.regionFlags & RegionFlags.EstateSkipScripts) ? 'True' : 'False');
|
|
general.ele('MaturityRating', (this.simAccess & SimAccessFlags.Mature & SimAccessFlags.Adult & SimAccessFlags.PG));
|
|
general.ele('RestrictPushing', (this.regionFlags & RegionFlags.RestrictPushObject) ? 'True' : 'False');
|
|
general.ele('AgentLimit', this.maxAgents);
|
|
general.ele('ObjectBonus', this.objectBonusFactor);
|
|
const groundTextures = document.ele('GroundTextures');
|
|
groundTextures.ele('Texture1', this.terrainDetail0.toString());
|
|
groundTextures.ele('Texture2', this.terrainDetail1.toString());
|
|
groundTextures.ele('Texture3', this.terrainDetail2.toString());
|
|
groundTextures.ele('Texture4', this.terrainDetail3.toString());
|
|
|
|
groundTextures.ele('ElevationLowSW', this.terrainStartHeight00);
|
|
groundTextures.ele('ElevationLowNW', this.terrainStartHeight01);
|
|
groundTextures.ele('ElevationLowSE', this.terrainStartHeight10);
|
|
groundTextures.ele('ElevationLowNE', this.terrainStartHeight11);
|
|
|
|
groundTextures.ele('ElevationHighSW', this.terrainHeightRange00);
|
|
groundTextures.ele('ElevationHighNW', this.terrainHeightRange01);
|
|
groundTextures.ele('ElevationHighSE', this.terrainHeightRange10);
|
|
groundTextures.ele('ElevationHighNE', this.terrainHeightRange11);
|
|
|
|
const terrain = document.ele('Terrain');
|
|
terrain.ele('WaterHeight', this.waterHeight);
|
|
terrain.ele('TerrainRaiseLimit', this.terrainRaiseLimit);
|
|
terrain.ele('TerrainLowerLimit', this.terrainLowerLimit);
|
|
terrain.ele('UseEstateSun', (this.useEstateSun) ? 'True' : 'False');
|
|
terrain.ele('FixedSun', (this.regionFlags & RegionFlags.SunFixed) ? 'True' : 'False');
|
|
terrain.ele('SunPosition', this.sunHour);
|
|
this.environment.getXML(document);
|
|
return document.end({pretty: true, allowEmpty: true});
|
|
}
|
|
|
|
activateCaps(seedURL: string): void
|
|
{
|
|
if (this.caps !== undefined)
|
|
{
|
|
this.caps.shutdown();
|
|
}
|
|
this.caps = new Caps(this.agent, seedURL, this.clientEvents);
|
|
}
|
|
async handshake(handshake: RegionHandshakeMessage): Promise<void>
|
|
{
|
|
this.regionName = Utils.BufferToStringSimple(handshake.RegionInfo.SimName);
|
|
this.simAccess = handshake.RegionInfo.SimAccess;
|
|
this.regionFlags = handshake.RegionInfo.RegionFlags;
|
|
this.regionOwner = handshake.RegionInfo.SimOwner;
|
|
this.agent.setIsEstateManager(handshake.RegionInfo.IsEstateManager);
|
|
this.waterHeight = handshake.RegionInfo.WaterHeight;
|
|
this.billableFactor = handshake.RegionInfo.BillableFactor;
|
|
this.cacheID = handshake.RegionInfo.CacheID;
|
|
this.terrainBase0 = handshake.RegionInfo.TerrainBase0;
|
|
this.terrainBase1 = handshake.RegionInfo.TerrainBase1;
|
|
this.terrainBase2 = handshake.RegionInfo.TerrainBase2;
|
|
this.terrainBase3 = handshake.RegionInfo.TerrainBase3;
|
|
this.terrainDetail0 = handshake.RegionInfo.TerrainDetail0;
|
|
this.terrainDetail1 = handshake.RegionInfo.TerrainDetail1;
|
|
this.terrainDetail2 = handshake.RegionInfo.TerrainDetail2;
|
|
this.terrainDetail3 = handshake.RegionInfo.TerrainDetail3;
|
|
this.terrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00;
|
|
this.terrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01;
|
|
this.terrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10;
|
|
this.terrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11;
|
|
this.terrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00;
|
|
this.terrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01;
|
|
this.terrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10;
|
|
this.terrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11;
|
|
this.regionID = handshake.RegionInfo2.RegionID;
|
|
this.cpuClassID = handshake.RegionInfo3.CPUClassID;
|
|
this.cpuRatio = handshake.RegionInfo3.CPURatio;
|
|
this.coloName = Utils.BufferToStringSimple(handshake.RegionInfo3.ColoName);
|
|
this.productSKU = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductSKU);
|
|
this.productName = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductName);
|
|
|
|
|
|
|
|
const request: RequestRegionInfoMessage = new RequestRegionInfoMessage();
|
|
request.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
this.circuit.sendMessage(request, PacketFlags.Reliable);
|
|
const regionInfo: RegionInfoMessage = await this.circuit.waitForMessage<RegionInfoMessage>(Message.RegionInfo, 10000);
|
|
|
|
this.estateID = regionInfo.RegionInfo.EstateID;
|
|
this.parentEstateID = regionInfo.RegionInfo.ParentEstateID;
|
|
this.maxAgents = regionInfo.RegionInfo.MaxAgents;
|
|
this.objectBonusFactor = regionInfo.RegionInfo.ObjectBonusFactor;
|
|
this.terrainRaiseLimit = regionInfo.RegionInfo.TerrainRaiseLimit;
|
|
this.terrainLowerLimit = regionInfo.RegionInfo.TerrainLowerLimit;
|
|
this.pricePerMeter = regionInfo.RegionInfo.PricePerMeter;
|
|
this.redirectGridX = regionInfo.RegionInfo.RedirectGridX;
|
|
this.redirectGridY = regionInfo.RegionInfo.RedirectGridY;
|
|
this.useEstateSun = regionInfo.RegionInfo.UseEstateSun;
|
|
this.sunHour = regionInfo.RegionInfo.SunHour;
|
|
this.maxAgents32 = regionInfo.RegionInfo2.MaxAgents32;
|
|
this.hardMaxAgents = regionInfo.RegionInfo2.HardMaxAgents;
|
|
this.hardMaxObjects = regionInfo.RegionInfo2.HardMaxObjects;
|
|
|
|
const msg: MapNameRequestMessage = new MapNameRequestMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID,
|
|
Flags: GridLayerType.Objects,
|
|
EstateID: 0,
|
|
Godlike: false
|
|
};
|
|
msg.NameData = {
|
|
Name: handshake.RegionInfo.SimName
|
|
};
|
|
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
await this.circuit.waitForMessage<MapBlockReplyMessage>(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse =>
|
|
{
|
|
for (const region of filterMsg.Data)
|
|
{
|
|
const name = Utils.BufferToStringSimple(region.Name);
|
|
if (name.trim().toLowerCase() === this.regionName.trim().toLowerCase())
|
|
{
|
|
this.xCoordinate = region.X;
|
|
this.yCoordinate = region.Y;
|
|
this.mapImage = region.MapImageID;
|
|
const globalPos = Utils.RegionCoordinatesToHandle(this.xCoordinate, this.yCoordinate);
|
|
this.regionHandle = globalPos.regionHandle;
|
|
return FilterResponse.Finish;
|
|
}
|
|
}
|
|
return FilterResponse.NoMatch;
|
|
});
|
|
|
|
this.environment = new RegionEnvironment();
|
|
this.environment.dayCycleKeyframes = [];
|
|
this.environment.skyPresets = {};
|
|
this.environment.water = {
|
|
blurMultiplier: 0,
|
|
fresnelOffset: 0,
|
|
fresnelScale: 0,
|
|
normalScale: Vector3.getZero(),
|
|
normalMap: UUID.zero(),
|
|
scaleAbove: 0,
|
|
scaleBelow: 0,
|
|
underWaterFogMod: 0,
|
|
waterFogColor: Color4.white,
|
|
waterFogDensity: 0,
|
|
wave1Dir: Vector2.getZero(),
|
|
wave2Dir: Vector2.getZero()
|
|
};
|
|
|
|
await this.caps.waitForSeedCapability();
|
|
const response = await this.caps.capsGetXML('EnvironmentSettings');
|
|
if (response.length >= 4)
|
|
{
|
|
if (Array.isArray(response[1]) && typeof response[2] === 'object' && typeof response[3] === 'object')
|
|
{
|
|
for (const kf of response[1])
|
|
{
|
|
this.environment.dayCycleKeyframes.push({
|
|
time: kf[0],
|
|
preset: kf[1]
|
|
});
|
|
}
|
|
for (const presetKey of Object.keys(response[2]))
|
|
{
|
|
const preset = response[2][presetKey];
|
|
this.environment.skyPresets[presetKey] = new class implements SkyPreset
|
|
{
|
|
ambient = new Vector4(preset['ambient']);
|
|
blueDensity = new Vector4(preset['blue_density']);
|
|
blueHorizon = new Vector4(preset['blue_horizon']);
|
|
cloudColor = new Color4(preset['cloud_color']);
|
|
cloudPosDensity1 = new Vector4(preset['cloud_pos_density1']);
|
|
cloudPosDensity2 = new Vector4(preset['cloud_pos_density2']);
|
|
cloudScale = new Vector4(preset['cloud_scale']);
|
|
cloudScrollRate = new Vector2(preset['cloud_scroll_rate']);
|
|
cloudShadow = new Vector4(preset['cloud_shadow']);
|
|
densityMultiplier = new Vector4(preset['density_multiplier']);
|
|
distanceMultiplier = new Vector4(preset['distance_multiplier']);
|
|
eastAngle = preset['east_angle'];
|
|
enableCloudScroll = {
|
|
x: preset['enable_cloud_scroll'][0],
|
|
y: preset['enable_cloud_scroll'][1]
|
|
};
|
|
gamma = new Vector4(preset['gamma']);
|
|
glow = new Vector4(preset['glow']);
|
|
hazeDensity = new Vector4(preset['haze_density']);
|
|
hazeHorizon = new Vector4(preset['haze_horizon']);
|
|
lightNormal = new Vector4(preset['lightnorm']);
|
|
maxY = new Vector4(preset['max_y']);
|
|
starBrightness = preset['start_brightness'];
|
|
sunAngle = preset['sun_angle'];
|
|
sunlightColor = new Color4(preset['sunlight_color']);
|
|
};
|
|
}
|
|
const wat = response[3];
|
|
this.environment.water = new class implements WaterPreset
|
|
{
|
|
blurMultiplier = wat['blurMultiplier'];
|
|
fresnelOffset = wat['fresnelOffset'];
|
|
fresnelScale = wat['fresnelScale'];
|
|
normalScale = new Vector3(wat['normScale']);
|
|
normalMap = new UUID(wat['normalMap'].toString());
|
|
scaleAbove = wat['scaleAbove'];
|
|
scaleBelow = wat['scaleBelow'];
|
|
underWaterFogMod = wat['underWaterFogMod'];
|
|
waterFogColor = new Color4(wat['waterFogColor']);
|
|
waterFogDensity = wat['waterFogDensity'];
|
|
wave1Dir = new Vector2(wat['wave1Dir']);
|
|
wave2Dir = new Vector2(wat['wave2Dir']);
|
|
};
|
|
}
|
|
}
|
|
this.handshakeComplete = true;
|
|
this.handshakeCompleteEvent.next();
|
|
}
|
|
|
|
shutdown(): void
|
|
{
|
|
this.parcelPropertiesSubscription.unsubscribe();
|
|
this.messageSubscription.unsubscribe();
|
|
this.comms.shutdown();
|
|
this.caps.shutdown();
|
|
this.objects.shutdown();
|
|
this.circuit.shutdown();
|
|
|
|
}
|
|
}
|