SkunkWorks API reference

This section documents the individual methods and properties that make up the SkunkWorks API. If you're looking for a tutorial on how to write a macro, this isn't it. If you've never coded in JScript or VBScript before, or never written any sort of program at all, this won't teach you how. See the Links page for resources where you might find information of that sort. What I'm going to focus on here is the details of how each API call works, on the assumption that you already know the difference between a string and an integer, and what a for loop is for.

SkunkWorks actually has a dual-API architecture. The ACScript API, including the ac and Inventory objects and the full range of ACMsg_* events, is supported in its entirety.

SkunkWorks also has a native API, which is essentially a ground-up redesign of the ACScript API with a number of new features added. Like many ACScript users, I've had moments when I thought, "If this were my program, by God I'd do things differently!" The dual-API architecture is my way of doing things differently while still maintaining full ACScript compatibility. The idea is that you shouldn't have to modify your script to run it under SkunkWorks—but you might want to in order to take advantage of the new features.

Which API should you use? That's entirely up to you. The ACScript API is familiar and stable; if you're a longtime ACScript user, you already know how it works, and can count on the fact that it's not going to change. If you just want to make a few changes to an existing script, without having to go through a whole learning curve, the ACScript API is the logical choice. On the other hand, if you're starting a new script from scratch, you may find it more convenient to bypass the ACScript API and code directly to the native API right from the get-go.

But it's not an all-or-nothing choice; you can mix and match elements from both APIs to suit your whim. For instance, objects returned by ac.GetObject are full-fledged SkunkWorks ACOs and implement all of the SkunkWorks ACO properties in addition to the ACScript GetObject properties. This mix-and-match capability lets you incrementally upgrade existing scripts to use some of the new SkunkWorks features, without having to rewrite everything in terms of the new API.

The SkunkWorks API is embodied in the skapi object. If you're running your script under the SkunkWorks console, this object is created for you automatically and is visible globally in your script in the same way as the familiar ac and Inventory objects. If you're writing a standalone automation app using the SkunkWorks COM interface, you'll have to instantiate the skapi object yourself.

The methods and properties of the skapi object are listed below, grouped by function. Methods, properties, and options new since version 3.5 are highlighted.

Users of ACSUtil will notice that SkunkWorks includes essentially all of the ACSUtil functionality natively. Existing scripts built around ACSUtil should run without modification under SkunkWorks as long as ACSUtil is installed; for new scripts, you may find it simpler to use the equivalent SkunkWorks functions instead. Again, it's possible to mix and match: maplocs produced by ACSUtil can be consumed by SkunkWorks, and vice versa.

The API uses a number of defined constants and bitmasks. Where the documentation below refers to a particular set of constants or masks, you'll find a link to the appropriate definitions. You'll also find all of the constant definitions reproduced in SkapiDefs.js and SkapiDefs.vbs; by including one or the other of these files in your SkunkWorks project, you'll be able to refer to these constants by the symbolic names given here instead of hard-coding the numeric values directly into your script.

ACOs and OIDs

Everything you can interact with in the game world—including other players, NPCs, creatures, inventory items, weapons and armor, doors, portals, chests, corpses, and so on—is represented to your script as an object of a specific type, with a specific set of methods and properties. In ACScript these objects are called simply "objects". In this document, and in the SkunkWorks API, they're called ACOs (for "AC Objects"). Whenever you see the term ACO, you'll know I'm talking about an object of this specific sort, i.e. one you can interact with in the game world, with a specific set of properties and methods. Other kinds of programming objects used by the API (such as locations, collections, event handlers, and so forth) will be referred to by other abbreviations.

Every ACO has a unique 32-bit Object ID, called here an OID, which is a kind of serial number assigned to the object when it first spawns and used to identify that object throughout its life. Given an ACO, you can easily get its OID; given an OID, you can (usually) get the corresponding ACO. There's not much you can do with the OID alone; it's just a number. It's the ACO—the object itself—that has all the interesting properties. For this reason, the SkunkWorks API emphasizes ACOs and de-emphasizes OIDs. Where the ACScript API tends to deal primarily in OIDs, you'll find that (with one or two exceptions) the corresponding functions of the SkunkWorks API accept or return ACOs directly, saving you an OID-to-ACO translation step.

In some situations a method that normally returns an ACO may not have one available to return. (For instance, you might have asked for an item that doesn't exist.) In such cases the value returned is null (in JScript) or Nothing (in VBScript).

For detailed information on the properties and methods of ACOs, see ACO properties.

Information about your character

Method or property

Result type

Description

skapi.cchar

Long

The number of characters in your logged-in account. This will be a number between one and five.

skapi.rgszChar(i)

String

The full name of the i th character in your logged-in account. i must be an integer between 0 and skapi.cchar−1. Character names are sorted alphabetically to match the order of character slots on the AC login screen.

skapi.szName

String

The full name of your currently logged-in character.

skapi.szWorld

String

The name of the world (game server) you're logged into (e.g. "Darktide").

skapi.szGender

String

Your character's gender ("Male" or "Female").

skapi.szRace

String

The name of your character's "heritage group" (e.g. "Sho").

skapi.szProfession

String

Your character's profession (e.g. "Sharpshooter").

skapi.szAccount

String

The account name of the logged-in account.

skapi.lvl

Long

Your character's current level.

skapi.rank

Long

Your character's allegiance rank.

skapi.burdenCur

Long

The total weight you're carrying in burden units. You can calculate your current burden as a percentage of full burden as follows:

skapi.burdenCur/skapi.burdenFull * 100

skapi.burdenFull

Long

The amount of weight, in burden units, that you can carry without being overburdened (i.e. your 100% burden capacity). This is equal to your current buffed Strength times 150. You can actually carry up to three times this amount (300% burden).

skapi.cpyCash

Long

The amount of cash (pyreal coins) you're carrying.

skapi.healthCur

Long

Your current health level in health points.

skapi.staminaCur

Long

Your current stamina level in stamina points.

skapi.manaCur

Long

Your current mana level in mana points.

skapi.fCombatMode

Boolean

True if your character is in combat/spellcasting mode, false if in peace mode.

skapi.SetCombatMode(fCombat)

None

Changes your character from combat mode to peace mode or vice versa. fCombat is a Boolean indicating the desired new state: True for combat/spellcasting mode, false for peace mode.

Note that skapi.SetCombatMode just sets the animation in motion but does not wait for it to complete. OnCombatMode will fire when the transition is complete.

skapi.chop

Long

A bitmask of character option masks reflecting your Character Options panel settings.

skapi.chop2

Long

A bitmask of extended character options containing new Character Options settings that didn't fit into skapi.chop.

skapi.acoChar

ACO

Returns an ACO representing your character.

skapi.szFellowship

String

The name of the fellowship you belong to, or the empty string ("") if you don't belong to a fellowship.

skapi.cofellow

Collection

A collection of Fellow objects describing the members of your fellowship. The first element of this collection describes the fellowship leader. If you're not part of a fellowship, the collection is empty.

Each Fellow object has the following properties and methods:

fellow.oid

Long

The object ID of this member. This is an OID rather than an ACO because the member may be far away at the time.

fellow.szName

String

The member's name.

fellow.lvl

Long

The member's character level.

fellow.fShareLoot

Boolean

True if this member is sharing loot with the fellowship, false otherwise.

fellow.healthMax

Long

The member's maximum health.

fellow.staminaMax

Long

The member's maximum stamina.

fellow.manaMax

Long

The member's maximum mana.

fellow.healthCur

Long

The member's current health.

fellow.staminaCur

Long

The member's current stamina.

fellow.manaCur

Long

The member's current mana.

fellow.Assess( )

None

Assess this member without bringing up the Fellowship panel. Results of the assessment are reported in the usual way by OnAssessCreature.

skapi.tBirth

Variant

The calendar date on which your character was created, in the form of a Date Variant. This can be converted to a locale-specific string as follows:

In JScript:

var d = new Date(skapi.tBirth);
var s = d.toLocaleString();

In VBScript:

FormatDateTime(skapi.tBirth)

In PerlScript:

$skapi->tBirth->Date()

skapi.csecAge

Long

The total play time of your character in seconds.

skapi.cdeath

Long

The number of times your character has died.

Skills and attributes

Method or property

Result type

Description

skapi.strengthUnbuffed

Long

Your unbuffed Strength attribute. Your actual Strength at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.enduranceUnbuffed

Long

Your unbuffed Endurance attribute. Your actual Endurance at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.quicknessUnbuffed

Long

Your unbuffed Quickness attribute. Your actual Quickness at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.coordinationUnbuffed

Long

Your unbuffed Coordination attribute. Your actual Coordination at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.focusUnbuffed

Long

Your unbuffed Focus attribute. Your actual Focus at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.selfUnbuffed

Long

Your unbuffed Self attribute. Your actual Self at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.healthMaxUnbuffed

Long

Your unbuffed Maximum Health attribute. The actual maximum at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.staminaMaxUnbuffed

Long

Your unbuffed Maximum Stamina level. The actual maximum at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.manaMaxUnbuffed

Long

Your unbuffed Maximum Mana level. The actual maximum at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.SklvlUnbuffedFromSkid(skid)

Long

Your unbuffed skill level in the skill specified by the numerical skill ID code skid. Your actual skill level at any moment may be different due to buffs, debuffs, or vitae penalties.

skapi.SklvlDefaultFromSkid(skid)

Long

Your default skill level in the skill specified by skid. This is the raw skill level given by the standard skill formula for that skill (e.g. (Focus + Self)/4 or whatever), and is the skill level you would have if you had never practiced or improved that skill.

skapi.SkinfoFromAttr(attr)

Skinfo

Returns skill information for the primary attribute specified by attr. See Skinfo properties for details on the kinds of information available.

skapi.SkinfoFromVital(vital)

Skinfo

Returns skill information for the vital statistic (secondary attribute) specified by vital. See Skinfo properties for details on the kinds of information available.

Note that you can't use skapi.SkinfoFromVital to retrieve info about your instantaneous health, stamina, or mana levels. Use skapi.healthCur, skapi.staminaCur, or skapi.manaCur for that. skapi.SkinfoFromVital is defined for vitalHealthMax, vitalStaminaMax, and vitalManaMax only, and not for the corresponding vital*Cur stats.

skapi.SkinfoFromSkid(skid)

Skinfo

Returns skill information for the skill specified by skid. See Skinfo properties for details on the kinds of information available.

skapi.SkidFromSz(sz)

Long

Given the name of a skill (e.g. "War Magic"), returns the corresponding skill id (e.g. 34).

skapi.RaiseAttr(attr, dexp)

None

Spends XP on the primary attribute specified by attr. dexp is optional; if present, it specifies the amount of XP to spend; if omitted, if defaults to enough XP to raise the attribute one point. You can spend as much or as little XP as you like in one call.

skapi.RaiseVital(vital, dexp)

None

Spends XP on the vital statistic (secondary attribute) specified by vital. dexp is optional; if present, it specifies the amount of XP to spend; if omitted, if defaults to enough XP to raise the vital one point. You can spend as much or as little XP as you like in one call.

skapi.RaiseSkill(skid, dexp)

None

Spends XP on the skill specified by skid. dexp is optional; if present, it specifies the amount of XP to spend; if omitted, if defaults to enough XP to raise the skill one point. You can spend as much or as little XP as you like in one call.

skapi.expTotal

Double

The total number of experience points (XP) earned by your character.

skapi.expSpent

Double

The number of XP you've spent so far on improving your character.

skapi.expUnspent

Double

The number of unspent XP available to your character.

skapi.skpUnspent

Long

The number of unspent skill credits available to your character.

skapi.DexpToLvl(lvlTarget)

Double

Returns the amount of additional experience needed to reach the given character level from where you are now. lvlTarget is optional; if omitted, it defaults to your current level + 1. In that case, DexpToLvl tells you how much more XP you need to advance one level. If you've already reached or passed the specified target level, DexpToLvl returns zero. (Contrast this with skapi.ExpFromLvl below.)

skapi.ExpFromLvl(lvl)

Double

Returns the total amount of experience corresponding to the specified character level, i.e. the amount of XP a raw newbie would have to earn to reach that level. (Contrast this with skapi.DexpToLvl above.)

skapi.LvlFromExp(exp)

Long

Returns the character level corresponding to the given amount of total experience, i.e. the level a raw newbie would have reached after earning that much XP.

skapi.cospell

Collection

A collection of spell objects describing all the stat-altering spells currently in effect on your character. This includes buffs, debuffs, spells cast by enchanted jewelry and armor, and vitae penalties. If a given spell has been cast on you more than once, it will appear more than once in the collection, with different layer counts for the different instances. See Spell properties for details on the kinds of spell information available.

Inventory and equipment

Method or property

Result type

Description

skapi.cpack

Long

The number of packs you're carrying, including your main pack.

skapi.AcoFromIpackIitem(ipack, iitem)

ACO

Returns an ACO representing an item in your inventory. The item returned is the one at position iitem in the pack specified by ipack, where both ipack and iitem are numbered starting at zero (with pack zero being the main pack).

If iitem is iitemNil (−1), the ACO returned represents the pack itself. You can use this ACO to find out information about the pack, such as the number of items it contains, the maximum number of items it can hold, and the total weight of the pack and its contents. Note that because of the way objects are represented in the game itself, the ACO returned for pack zero (the main pack) is the same ACO returned by skapi.acoChar. You are your main pack.

skapi.AcoFindInInv(szItem, acoSkip)

ACO

Searches your inventory for an item (or stack of items) of a particular kind. szItem specifies the item to be searched for, and can be either the exact name of the item (e.g. "Pyreal", "Potion of Healing", "Hearty Mana Applesauce"), or a simple wildcard pattern such as "* Healing Kit" or "Salvaged Steel (*)". The first such item found is returned, or null/Nothing if no such item is found.

acoSkip is optional; if specified, then the ACO it designates is skipped over during the search and will not be returned even if its name matches. This is useful for finding, say, the second of two Pyreal Bars in your inventory: just pass the result of the first search in to the second search as acoSkip.

skapi.CitemOfKindInInv(szItem)

Long

Counts up the number of items of a particular kind in your inventory and returns the total. szItem specifies the item to be counted, and can be either the exact name of the item, or a simple wildcard pattern such as "* Healing Kit" or "Salvaged Steel (*)". In the case of stackable items, the count returned is the total number of items in all of the stacks, not the number of stacks.

skapi.AcoFromEqm(eqm)

ACO

Returns an ACO representing an item of equipment that you're wearing or wielding. eqm is an equipment type mask specifying the slot or slots to be checked; the first item found that covers any one of those slots is returned, or null/Nothing if all of the specified slots are empty. eqm values may be combined using the bitwise OR operator ("|" in JScript, "Or" in VBScript) to specify multiple equipment slots.

skapi.CoacoFromEqm(eqm)

COACO

Returns a collection of ACOs representing all the items of equipment that intersect the specified eqm. So for instance

skapi.CoacoFromEqm(eqmChestOuter | eqmChestUnder)

returns a collection of all the armor and clothing protecting your chest. To get a collection of everything you're wearing and wielding, pass in eqmAll.

skapi.eqmEquipped

Long

Returns an equipment mask indicating which of your weapon, armor, and clothing slots are currently occupied.

skapi.coacoTradeLeft

COACO

A collection of ACOs representing the items on the left side of the Secure Trade window (i.e. the items you're receiving in trade). This collection is empty if the trade window is not open.

skapi.coacoTradeRight

COACO

A collection of ACOs representing the items on the right side of the Secure Trade window (i.e. the items you're giving in trade). This collection is empty if the trade window is not open.

ACOs at large in the world around you

Method or property

Result type

Description

skapi.AcoFromOid(oid)

ACO

Given the Object ID (OID) of a game object (player, creature, item, etc), returns the ACO representing that object. In most cases you won't need to use this, since the API generally deals in ACOs rather than OIDs.

skapi.AcoFromSz(sz)

ACO

Searches the entire object table (not just your inventory) for an ACO of a particular kind. szItem specifies the name of the ACO to be searched for, and can be the name of a character or NPC (e.g. "Spertat the Ursuin Hunter"), a kind of monster (e.g. "Tumerok Priest"), a weapon or other equipment (e.g. "Sturdy Iron Key"), etc. Simple wildcard patterns such as "*Tumerok*" are permitted. The first such ACO found is returned, or null/Nothing if no such ACO exists in your vicinity in the game world.

skapi.CoacoFromOcm(ocm)

COACO

Returns a subset of ACOs from the object table based on the object category masks specified by ocm. ocm values may be combined using the bitwise OR operator ("|" in JScript, "Or" in VBScript) to specify multiple object categories; in this case the returned collection will include a mix of object types. The special value ocmAll causes the entire object table to be returned, including an ACO for every game object of any sort in your vicinity.

Note that while skapi.CoacoFromOcm is still supported, much more flexible filtering is now available using skapi.AcfNew.

skapi.AcfNew()

ACF

Returns an ACO collection filter that can be used to create various filtered subsets of the ACOs in the game world. The filtering options are quite flexible and allow much more precise filtering than the predefined object categories used by skapi.CoacoFromOcm. See ACF properties for details on the specific kinds of filtering supported.

skapi.acoSelected

ACO

Returns the ACO representing the object currently selected in game, i.e. the one with the targeting highlight around it. If nothing is selected, null/Nothing is returned.

Note that since this works by reading client memory, the value of skapi.acoSelected can change without notice at any time, whether or not you've called skapi.WaitEvent.

skapi.acoSelPrev

ACO

Returns the previously selected ACO, or null/Nothing if nothing was previously selected.

Note that since this works by reading client memory, the value of skapi.acoSelPrev can change without notice at any time, whether or not you've called skapi.WaitEvent.

skapi.CoacoNew( )

COACO

Creates and returns an empty ACO collection for your use. You may then use coaco.AddAco to add ACOs to the collection.

skapi.AssessAco(aco, fLowPriority)

None

Requests an assessment of the object specified by aco without bringing up the Assess panel. Results of the assessment are reported in the usual way by OnAssessCreature or OnAssessItem. skapi.AssessAco does not wait for these results but returns immediately after queuing the request. It's up to you to wait for the results by calling skapi.WaitEvent.

skapi.AssessAco queues the requests internally and sends them off to the server one by one as the previous results are received. It will also retry each request up to three times at two-second intervals if no response is received. So the delay between calling skapi.AssessAco and receiving a response via OnAssessItem or OnAssessCreature can be quite long, depending on how many requests are in the queue. You should keep this in mind and try not to use skapi.AssessAco to indiscriminately assess everything under the sun.

fLowPriority is optional. If false or omitted, the request is queued at normal priority. If true, the request is queued at a lower priority, so that normal-priority requests take precedence. This is useful for assessing items in the background when you don't need the results right away, without interfering with the processing of normal-priority requests.

Spellcasting

Method or property

Result type

Description

skapi.rgspellid(itab, ispell)

Long

Returns spell IDs from your spellbook or spell shortcut slots. If itab is −1, then ispell indexes your spellbook (the set of all spells you know) and returns the spellid of the spell at that ordinal position (starting at zero) in your spellbook. If ispell is −1, rgspellid returns the total number of spells in your spellbook.

If itab is in the range 0-6 (inclusive), then ispell indexes one of your seven spell shortcut tabs (numbered starting at zero) and returns the spellid of the spell at that ordinal position (starting at zero) in the specified tab. If ispell is −1, rgspellid returns the number of spells on the specified shortcut tab.

Note that spell tab info is updated only when you log on, so if you rearrange spell shortcuts, you'll have to relog before SkunkWorks will notice the changes.

skapi.AddSpellToSpellTab(itab, ispell, spellid)

None

Adds a spell to a spell tab. itab is the tab number, in the range 0-6 (inclusive). ispell is the ordinal position in the tab (starting at zero). spellid is the numerical spell ID of the spell to be added. Any existing spell at that position is replaced.

skapi.DeleteSpellFromSpellTab(itab, spellid)

None

Deletes a spell from a spell tab. itab is the tab number, in the range 0-6 (inclusive). spellid is the numerical spell ID of the spell to be deleted.

skapi.FSpellidInSpellbook(spellid)

Boolean

Returns True if you know the given spell (i.e. if it's in your spellbook), False otherwise. spellid is the numerical spell ID of the spell in question.

skapi.arc

Long

The action result code of your last spellcasting attempt. Also used for missile attacks and various other purposes. Reading out this value resets it to zero (arcSuccess).

skapi.CastSpell(spellid, acoTarget)

None

Casts the spell specified by the numerical spell ID spellid on the object specified by acoTarget. acoTarget is optional; if omitted, the spell is cast with yourself as the target.

skapi.spellidMax

Long

Returns the largest defined spellid. This is not quite the same as the number of spells defined, since there may be gaps in the spellid numbering.

skapi.SpellidFromSz(sz)

Long

Given the name of a spell (e.g. "Primary Portal Recall"), returns the corresponding numerical spellid (e.g. 48). If there is more than one spell with the given name, SpellidFromSpell returns the one with the smallest spellid. If no spell matches the given name, the result is zero.

Level VII spells with special names such as "Might of the Lugians" can be looked up using either the special name or the generic level VII name (e.g. "Strength Self VII").

skapi.SzFromSpellid(spellid)

String

Given a numerical spellid (e.g. 48), returns the name of the spell as a text string (e.g. "Primary Portal Recall").

skapi.SzDescFromSpellid(spellid)

String

Given a numerical spellid, returns a text string describing the spell's effects.

skapi.SpellInfoFromSpellid(spellid)

Spell

Given a numerical spellid, returns a spell object containing information about the spell such as its name, description, difficulty, mana cost, duration, etc.

skapi.CospellFromFamily(family)

Collection

Returns a collection of spell objects containing information about a family of related spells that affect the same stat in the same way, such as all the Strength spells, or all the Cooking Ineptitudes. family is the family number for which information is to be retrieved. A complete list of family codes can be found in FamilyDefs.js, available from the UserWare section. You can also call skapi.SpellInfoFromSpellid on some member of the desired family and extract spell.family from the returned spell object.

Spells in the returned collection are sorted in order of increasing difficulty. You can use this information to quickly locate spells appropriate to your skill level and mana resources, or to devise fallback strategies that intelligently scale back spell difficulty as debuff hits are taken or resources are used up.

Location and navigation

The following functions provide precise information about the geographical location of your character in the game world and assist in moving your character from place to place under script control. Unlike ACScript, SkunkWorks deals natively in the familiar latitude-and-longitude coordinates used by the in-game map display as well as by AC Explorer.

For higher-level navigation functions including routing with multiple waypoints, see the SkunkNav script library.

Method or property

Result type

Description

skapi.maplocCur

Maploc

Returns your current location in the game world as a maploc object. (See Maploc properties for detailed information about maplocs.) This may differ from skapi.acoChar.maploc by being more up-to-date. skapi.acoChar.maploc is updated by network packets from the game server and thus reflects the server's last report of where you were at the time. skapi.maplocCur, in contrast, is read directly from client memory and is the client's instantaneous notion of where you are right now as you move around. For precise navigation, skapi.maplocCur is the best location information to use.

If your character is not logged in, and therefore has no location, skapi.maplocCur will be null/Nothing. If the memlocs used for reading out location data are not accurate (for instance on patch day), skapi.maplocCur raises a "Could not read location info" exception; if this happens, you need to update Memlocs.xml. If AC is not running, or its memory cannot be read for some other reason, skapi.maplocCur raises an "AC is not running" exception.

Note that since this works by reading client memory, the value of skapi.maplocCur can change without notice at any time, whether or not you've called skapi.WaitEvent.

skapi.MaplocFromLatLng(lat, lng, z, head)

Maploc

Takes numerical latitude and longitude values (positive for north/east, negative for south/west), along with altitude and heading, and returns a maploc object representing that location. z (altitude) and head (heading) are optional and default to zero.

skapi.MaplocFromSz(sz)

Maploc

Takes a text string of the form "48.9S 62.3E" and returns a maploc object representing that location.

skapi.MaplocFromArray(rgloc)

Maploc

Accepts a five-element VBArray (such as those returned by ac.Location and obj.Location) and returns a maploc object representing that location. Provided for compatibility.

skapi.TurnToHead(head)

None

Instructs the SkunkWorks plugin to turn your character to face the given compass heading (measured in degrees clockwise from due north). It does this by logically holding down the TurnLeft and TurnRight keys as appropriate. TurnToHead doesn't wait for the turn to complete (since that can take a few seconds) but returns immediately after setting the turn in motion. When the target heading is reached, turning stops automatically and OnNavStop fires with nsc equal to nscArrived.

If you manually press any movement keys while the turn is in progress, TurnToHead yields control to the keyboard and fires OnNavStop with nsc equal to nscCanceled.

skapi.GoToMaploc(maploc, distArrive, fWalk, fStopOnArrival, cmsecTimeout)

None

Instructs the SkunkWorks plugin to move your character toward the map location specified by maploc. If fWalk is true, your character walks toward the target maploc; if false (the default), your character runs. GoToMaploc doesn't wait for you to arrive at the target but returns immediately after setting your character in motion. The SkunkWorks plugin does however continue to actively steer you toward the target even after GoToMaploc returns control to your script. It does this using a combination of logical keystrokes and Decal navigation hooks. Controlling the motion on the plugin side instead of the script side makes for less lag in steering and more precise stopping.

When you get within distArrive map units of the target, SkunkWorks fires an OnNavStop event with nsc equal to nscArrived to let your script know you've arrived. If fStopOnArrival is true (the default), your character stops moving on arrival. If false, your character continues moving in the same direction past the target point after OnNavStop fires. This lets you use the OnNavStop event to chain together consecutive legs of a complex route without having to stop at each intermediate waypoint. To stop this continuing motion after OnNavStop fires, use skapi.CancelNav.

If you fail to arrive at the target within cmsecTimeout milliseconds, SkunkWorks stops your character (regardless of the setting of fStopOnArrival) and fires OnNavStop with nsc equal to nscTimeout. cmsecTimeout is optional; if omitted, GoToMaploc calculates an appropriate timeout based on distance, Run skill level, and burden.

If you manually press any movement keys while navigation is in progress, GoToMaploc stops trying to steer and yields control of your character to the keyboard. When you let go of the movement keys, GoToMaploc will resume active steering. So if you need to temporarily override GoToMaploc's automated steering, you can do so without a struggle.

To completely break off automated navigation, call skapi.CancelNav.

Also note that while chatting is possible during automatic navigation, activating the chat buffer does prevent GoToMaploc from using keystrokes to steer or walk your toon. In such case your toon may overshoot a waypoint and have to backtrack a bit once the chat buffer goes inactive.

skapi.GoToMaplocEx(maploc, distArrive, cmsecTimeout, nopt)

None

Like skapi.GoToMaploc but with more options. nopt is a bitmask of navigation options and may include:

noptWalk

If specified, your character walks toward the target maploc; if omitted, your character runs.

noptStopOnArrival

If specified, your character stops moving on arrival at the target maploc. If omitted, your character continues moving in the same direction past the target point after OnNavStop fires. This lets you use the OnNavStop event to chain together consecutive legs of a complex route without having to stop at each intermediate waypoint. To stop this continuing motion after OnNavStop fires, use skapi.CancelNav.

noptZigzag

If specified, your character moves in a zigzag fashion toward the target maploc, making it harder for enemies to predict your line of motion for missile or war bolt targeting. (Note that this does not involve any kind of intelligent threat detection or dodging; it's just a simple programmed zigzag meant to confound targeting.) If omitted, your character moves in as direct a line as possible, changing course only as needed to remain on that direct line (and making your motion more predictable to enemies).

skapi.CancelNav( )

None

Cancels any navigation set in motion by skapi.GoToMaploc, stops your character in its tracks, and fires OnNavStop with nsc equal to nscCanceled.

skapi.vRun

Single

The approximate running speed of your character in map units/second. This takes into account your current buffed Run skill level as well as any slowdown due to overburdening. Again this is an approximate value and may not be accurate for all characters. For comparison, typical running speeds are on the order of 0.03 to 0.04 MU/s, and walking speed is about 0.01 MU/s.

skapi.KeylessFaceHeading(head)

None

Calls Decal's FaceHeading client hook function to turn your character to face the given compass heading (measured in degrees clockwise from due north) without using keyboard commands. KeylessFaceHeading doesn't wait for the turn to complete but returns immediately after setting the turn in motion. There is no notification when the target heading is reached.

skapi.KeylessAutorun(fOn)

None

Calls Decal's SetAutorun client hook function to enter or exit Autorun mode without using keyboard commands. fOn should be true to start running, false to stop. There is no way to walk using this function.

Making things happen in game

To some extent, your script makes things happen in game the same way you do: by pushing keys and clicking mouse buttons. The functions below do that. There are, however, a few shortcut functions for doing certain things more directly; see skapi.CastSpell, aco.Use, aco.UseOnAco, aco.MoveToPack, and aco.MoveToContainer.

Method or property

Result type

Description

skapi.Keys(cmid, kmo)

None

Sends one or more keyboard commands to the game, simulating the pressing and/or releasing of the desired key(s). The game must have keyboard focus for this to work. skapi.Keys cannot send keystrokes to an Alt+Tabbed window. cmid specifies the command ID of the key to press. Command IDs are abstract names such as "{Jump}" or "{TurnRight}" rather than specific key values like spacebar or right arrow. There are cmids defined for all of the commands on AC's Keyboard Configuration screen except for the "Permanent Keys" (which aren't remappable anyway). By using these abstract IDs instead of hard-coding specific keys, you enable your script to run on any keyboard configuration (or at least any configuration that has those commands mapped to keys).

kmo is an optional key motion code, and specifies the direction of key motion: press only (kmoDown), release only (kmoUp), or normal press-and-release (kmoClick). If omitted, kmo defaults to kmoClick.

To specify a sequence of several commands, use the + operator to concatenate the cmid values together (e.g. cmidClosestItem + cmidExamineSelected to assess loot on the ground).

You don't have to use cmids; if you'd rather, you can pass in specific key names such as "e" or "{tab}". (See this list of supported special key names.)

To modify the effect of a given key with Shift, Ctrl, or Alt, prefix it with "~", "^", or "@" respectively, e.g. "~{esc}" for Shift+Escape, "^v" for Crtl+V (Paste), "@{f4}" for Alt+F4.

Like cmids, these literal key values can be concatenated together in any sequence using the + operator, or simply combined in one string. I encourage you, though, to use the cmid values as much as possible to ensure portability of your script.

Note that the g_kb_* constants defined in ACScriptLib.js will work also, if you prefer to continue using them.

If the game is not running, skapi.Keys sends keystrokes to the Windows desktop. You can use this feature to automate game startup and Zone login. In this case the cmid values obviously aren't meaningful and you should use literal key values.

skapi.PasteSz(sz)

None

Loads a text string specified by sz into the clipboard and then pastes it in (by simulating a Ctrl+V) at the current typein position. This is usually more efficient then laboriously typing out text using skapi.Keys. Note that it's up to you to ensure that the game is ready to accept typein before calling skapi.PasteSz. (For instance, use skapi.Keys("{return}") to activate the chat buffer before pasting a message into it.)

skapi.VkFromCmid(cmid)

Long

Returns the virtual key code mapped to the command ID specified by cmid, or zero if no key is mapped to that command. Use this function to determine if a given command is keymapped or not; if the result is zero, it's not.

skapi.KeyState(cmid)

Long

Returns the state of the key specified by cmid, which may be a physical key name such as "K" or "{space}" or a logical command ID such as cmidPointState or cmidJump. The result is zero if the key is up, 1 if the key is lit up (in the case of Caps Lock, Num Lock, and Scroll Lock), or 2 if the key is down.

skapi.MouseButton(ibutton, kmo)

None

Sends a mouse button event to the game. The game must have keyboard focus for this to work. skapi.MouseButton cannot send button clicks to an Alt+Tabbed window. ibutton is the mouse button ID of the button to click. kmo is an optional key motion code, and specifies the direction of button motion: press only (kmoDown), release only (kmoUp), or normal press-and-release (kmoClick). If omitted, kmo defaults to kmoClick.

If the game is not running, skapi.MouseButton sends button clicks to the Windows desktop. You can use this feature to automate game startup and Zone login.

skapi.MouseMove(xp, yp)

None

Sends a mouse motion event to the game. xp and yp are the pixel coordinates of the target location. If you're running AC in windowed mode, xp and yp are client coordinates within the window; otherwise they're screen coords, so the same numerical values work in either case. xp and yp may be positive or negative. Positive values measure down from the upper left corner of the window; negative values measure up from the lower right. For resolution independence, specify targets near the right or bottom edges using negative xp or yp.

If the game is not running, skapi.MouseMove sends motion events to the Windows desktop. You can use this feature to automate game startup and Zone login.

Note: Despite hours of experimentation, I was unable to get skapi.MouseMove to work at the character login screen under Win9X. My best guess is that the Decal memlocs for mouse motion are valid only while you're logged in, and that the login screen itself requires different memlocs that nobody has researched yet. MouseMove does work properly once you're logged in. Let me repeat that this is strictly a Win9X problem; under Win2K/XP, MouseMove works at the login screen as well as when logged in.

What this means is that if you want to write a script that logs you out and back in again, e.g. for muling purposes or just to stay out of people's way, and you want the script to work under Win9X, you're going to have to get a bit tricky. My advice is to do something like this:

  • Before logging out, figure out the XY coords on the login screen of the character you want to log back in as.
  • Position the mouse at those coordinates on the 3D gameplay screen.
  • Use keyboard commands ("~{esc}" for Shift+Esc) instead of mouse clicks to log out.
  • Wait for the login screen to appear.
  • Without moving the mouse, issue a double-click command to log back in as the desired character.

I apologize for the inconvenience, but this is the best I can do for now. If anybody has an idea for a better solution that won't break every month on patch day, I'd love to hear about it.

skapi.SelectAco(aco)

None

Selects the given ACO, i.e. causes the targeting highlight to appear around the object in game.

skapi.SetAcoSelPrev(aco)

None

Sets the previously selected ACO. You can then use cmidPreviousSelection to make it the current selection. This method is provided mainly for ACScript compatibility; the preferred technique is to use skapi.SelectAco.

skapi.SetStackCount(citem)

None

Adjusts the stack count of the selected ACO to citem items, much as if you had typed "T citem Enter", but without any keyboard interaction. The next use of that ACO (such as dropping it or moving it to a different inventory slot) will then split off the specified number of items, leaving a partial stack behind. Note that there is no visual feedback for this function; the stack count shown in the lower right corner of the screen does not change. Only the client's internal stack count is affected.

skapi.OutputSz(sz, opm, cmc)

None

Outputs a text string to the debug log, the console, and/or the in-game chat windows as specified by the output mode opm. opm values may be combined using the bitwise OR operator ("|" in JScript, "Or" in VBScript) to send output to multiple destinations (e.g. opmConsole | opmDebugLog to send output to both the console and the debug terminal, as in ac.Debug). If opm is omitted, the output goes by default to both the console and the primary chat window.

cmc is an optional chat message color; if omitted, it defaults to a pale blue color. cmc has no effect on output modes other than opmChatWnd*.

For viewing output sent to the debug log, use a utility such as DebugView.

skapi.OutputLine(sz, opm, cmc)

None

Outputs a complete line of text by appending a CRLF to sz and passing it to skapi.OutputSz. The opm and cmc arguments are the same as for skapi.OutputSz.

skapi.AddStatusText(szText)

None

Displays a text message in the 3D game window (not in the chat window). szText is the text to be displayed.

skapi.Tell(szRecipient, szMsg)

None

Sends a chat message to a named recipient without using the keyboard or the chat typein buffer. szRecipient is the name of the intended recipient; szMsg is the text of the message.

skapi.Chat(chrm, szMsg)

None

Sends a chat message to selected recipients without using the keyboard or the chat typein buffer. chrm is a chat recipient mask specifying the intended recipients; szMsg is the text of the message.

Note that skapi.Chat cannot be used for executing @ commands. It works only for actual chatting.

skapi.InvokeChatParser(szText)

None

Parses a line of text as if it had been typed into the in-game chat buffer. szText is the text to be parsed. In contrast to skapi.Chat, skapi.InvokeChatParser can be used to execute @ commands.

skapi.Salvage( )

None

Invokes the Salvage function on the Ust panel (which is assumed to be open).

skapi.TradeAccept( )

None

Accepts the terms of a Secure Trade.

skapi.TradeEnd( )

None

Cancels Secure Trade, as if the other player had closed their trade window.

skapi.TradeDecline( )

None

Withdraws acceptance of the terms of a Secure Trade.

skapi.TradeReset( )

None

Invokes the Clear All function on the Secure Trade panel.

skapi.VendorBuyAll( )

None

Buys all the items in the merchant Buy list.

skapi.VendorBuyListClear( )

None

Clears the merchant Buy list.

skapi.VendorSellAll( )

None

Sells all the items in the merchant Sell list.

skapi.VendorSellListClear( )

None

Clears the merchant Sell list.

skapi.Logout( )

None

Logs out of the game and returns to the character login screen, without using the keyboard or mouse. No confirmation is required. Note that this merely starts the logout animation and returns immediately. It's up to you to wait for the login screen to appear before trying to log back in.

In-game controls

The following functions allows you to display and manipulate a Decal-style control panel in game. Clicking on the panel controls generates events in the SkunkWorks plugin, which reads out the state of the controls and forwards it to your script via the OnControlEvent event.

I haven't gone to much trouble to sugar-coat the Decal view schema syntax. A Decal view schema (for those who haven't seen one) is an XML text detailing the layout and properties of a control panel and its constituent controls. Unfortunately the Decal implementors have neglected to document this area in any detail; the best alternatives I've found are Vincent's Decal Controls Primer for SkunkWorks (thanks, Vincent!) and Zyrca's Decal Controls Documentation.

For working examples of this feature in action, see the ControlSample, ChoiceSample, ListSample, and RouteMaker demo projects.

If you don't need a custom control panel but just want a simple way to send text commands to your script from in game, see the OnCommand event.

Method or property

Result type

Description

skapi.ShowControls(szSchema, fActivate)

None

Adds an in-game control panel of your design to the Decal bar. szSchema may be either an XML string specifying the panel layout in form of a Decal view schema, or the name of an XML file containing such a schema. If fActivate is true, the panel is displayed in the open state (with the controls visible); if false, it's simply added to the Decal bar in the minimized state. If the schema title (the name that appears in the Decal bar) is the same as that of an existing panel, the existing panel is replaced by the new schema.

Once your panel has been displayed, you can call skapi.ShowControls(szPanel, fActivate), where szPanel is the name of the panel, to activate (restore) or deactivate (minimize) it without re-creating it.

There's a limit of approximately 64K on the size of XML strings that can be accepted by skapi.ShowControls. If your schema is bigger than that, save it to an XML file (i.e. one with the .xml extension) and pass the filename to skapi.ShowControls in place of szSchema.

skapi.GetControlProperty(szPanel, szControl, szProperty)

None

Interrogates the value of a particular property of a named control. szPanel is the name of the panel containing the control, szControl is the name of the control, and szProperty is the name of the property. Because of the nature of the two-way interprocess communication between your script and the SkunkWorks filter, you don't get the property value back directly from GetControlProperty. Instead, it comes in the form of an OnControlProperty event, which you must handle in the usual way by registering a handler, calling skapi.WaitEvent, etc.

You can interrogate properties that take arguments, such as list.Data(x,y), by encoding the argument values as part of the szProperty string, like so:

skapi.GetControlProperty("MyPanel", "MyList",
  "Data(0,3)")

You can also invoke control methods in similar fashion:

skapi.GetControlProperty("MyPanel", "MyList",
  "DeleteRow(5)")

skapi.GetControlProperty("MyPanel", "MyList",
  "Clear()")

If the method returns a value (such as list.AddRow), it does so by way of OnControlProperty. For methods that don't return a value, no event is fired.

skapi.SetControlProperty(szPanel, szControl, szProperty, vValue)

None

Sets the value of a particular property of a named control. szPanel is the name of the panel containing the control, szControl is the name of the control, szProperty is the name of the property to set, and vValue is the new value. Use this to display messages in StaticText controls, to change the color or caption of a PushButton, etc.

You can set properties that take arguments, such as list.Data(x,y), by encoding the argument values as part of the szProperty string, like so:

skapi.SetControlProperty("MyPanel", "MyList",
  "Data(0,3)", "SomeValue")

skapi.RemoveControls(szPanel)

None

Removes a previously displayed control panel. szPanel is the name of the panel to remove.

skapi.Hotkey(cmid, fEnable)

None

Enables or disables a hotkey event. cmid specifies the key, and may be a physical key name such as "K" or "{space}" or a logical command ID such as cmidPointState or cmidJump. If fEnable is True, hotkey events are enabled for that key; if False, hotkey events are disabled. When enabled, an OnHotkey event fires whenever that keystroke is received, whether it's an actual physical keystroke by the user or a scripted keystroke from skapi.Keys.

Decal-side UI scripting

If you're designing a moderately complex in-game user interface for your script, you may find that skapi.GetControlProperty and skapi.SetControlProperty aren't flexible or responsive enough to meet your needs. One way to get around these difficulties is by embedding script code directly in your XML view schema, like so:

<view title='MyControls'>
    <control progid='DecalControls.FixedLayout'>
        <control name='myButton' progid='DecalControls.PushButton'
          text='Click Me' left='10' top='0' width='60' height='20' />
    </control>
    <script language='VBScript'>
    <![CDATA[
        
        Public Function myButton_Accepted(nID)
        
        ' Your event-handling code here.
        
        End Function
        
    ]]>
    </script>
</view>

For a more complex, real-world example, see the included FileBrowser sample script.

Embedding script code directly in your UI like this is not the same as adding a script file to your SkunkWorks project. There are several key differences:

These differences reflect the purpose and nature of Decal-side UI script. Because your embedded code is called directly from Decal, it can be much more responsive than a GetControlProperty/OnControlProperty turnaround cycle. You can also manipulate Decal UI objects (such as PushButton controls and the like) using their native APIs, in true object-oriented fashion. Plus you have direct access to a wide range of Decal services and facilities through the pluginsite object.

You can also define Decal hotkeys and timers in your view schema, and handle the events using embedded script:

<view title='MyControls'>
    <control progid='DecalControls.FixedLayout'>
        ...
    </control>
    <hotkey name='myHotkey' key='X' enabled='True'/>
    <timer name='myTimer'/>
    <script language='VBScript'>
    <![CDATA[
        
        Public Function myHotkey_Hotkey(source)
        
            Call myTimer.Start(1000)
        
        End Function
        
        Public Function myTimer_Timeout(source)
        
            Call myTimer.Stop()
        
        End Function
        
    ]]>
    </script>
</view>

Decal-side hotkeys and timers declared in this way are distinct from those created by skapi.Hotkey and skapi.TimerNew, and do not raise any OnHotkey or OnTimer events in your script. (You can, however, call FireControlEvent from your Decal-side hotkey and timer handlers.)

The <![CDATA[ ]]> tags in the above examples are there to prevent the XML parser from trying to interpret the embedded script as XML. Using these tags lets you use <, >, and & symbols freely in your script code without confusing the XML parser. If you prefer not to embed large blocks of code directly in the XML, you can do it indirectly by referencing one or more external script files like so:

<view title='MyControls'>
    <control ... >
     ...
    </control>
    <script language='VBScript' src='FileA.vbs'/>
    <script language='VBScript' src='FileB.vbs'/>
    ...
</view>

I've used VBScript in the example but you're not limited to VBScript for embedding purposes. You can use any Windows scripting language you like, so long as all of the script for any one view panel is in the same language. You can't mix scripting languages within the same panel.

Of course your Decal-side code would be less than useful without some way to communicate and synchronize with your main script. This can be done using the FireControlEvent and skapi.CallPanelFunction API functions described in detail below. Using these functions you can bundle up large blocks of data to pass back and forth, instead of having to laboriously set up every control individually from your main script.

Note that your main script needn't actually be script to use this feature. Compiled COM scripts can use embedded Decal-side script just as easily as interpreted scripts can. (Of course the embedded code itself must be script. There's no way to embed compiled code into your XML view schema. However you can use the custom plugin features in the next section to communicate with compiled plugin code.)

Decal-side event handlers relate to OnControlEvent as follows:

The set of API functions available to Decal-side UI script is as follows. Note that these are not methods of any object, but are just ordinary functions visible globally to your Decal-side code.

Function

Result type

Description

FireControlEvent(szControl)

None

Queues an OnControlEvent message for your main script. szControl (a string) will appear as the value of the szControl argument of OnControlEvent. This can be the actual name of an existing control, or any other text you like.

ShowControls(szSchema, fActivate)

None

Displays a new in-game control panel using the given schema. This works just like skapi.ShowControls except that it can be called from Decal-side script. Useful for creating cooperating systems of pop-out sub-panels.

RemoveControls( )

None

Closes the calling panel and removes it from the Decal bar. This is not quite the same as skapi.RemoveControls in that one panel can't use it to close another panel. For complicated technical reasons that I won't go into here, each panel can only close itself.

CallPanelFunction(szPanel, szFunction, args)

Variant

Calls an embedded function in some other view panel and returns the result. szPanel is the name of the panel containing the desired function, and szFunction is the name of the function to call. args is an optional argument list to pass to the embedded function. You can pass up to five arguments this way. The embedded function must be declared with the same number of formal parameters as the actual number of arguments passed. To pass more than five arguments, build an array (a VBArray in JScript) and pass that. See also skapi.CallPanelFunction.

OutputDebugLine(sz)

None

Outputs a line of text to the debug log. Use this to generate diagnostic output from your Decal-side code, which otherwise can be quite difficult to debug.

To output text to the chat window, use native Decal APIs such as pluginsite.WriteToChatWindow.

In addition, your Decal-side code has direct access to the following Decal objects. I'm not going to document their detailed methods and properties here, on the theory that you wouldn't have read this far into this section without a certain degree of familiarity with Decal. However there are several resources you can use to gain that familiarity if you don't have it. These include the Decal source, the Visual Basic object browser, and the OLEView interface browser that ships with Visual Studio.

Object

Description

pluginsite

A reference to the IPluginSite object passed in by Decal to the SkunkWorks plugin. This is the primary object for accessing Decal APIs and functions.

hooks

A reference to Decal's ACHooks object, which provides access to Decal's client hook functions. You can create event handlers for ACHooks events by defining handler functions named hooks_EventName (e.g. hooks_OnCommandLineText).

view

A reference to the IView object representing this panel. This object gives access to the view panel's methods and properties. You can create event handlers for the view's events by defining handler functions named view_EventName (e.g. view_Activate for the view's Activate event).

named controls

In addition to the view object, there is an object for each named control on your panel. So for instance in the example at the top of this section, the methods and properties of the myButton control can be accessed as myButton.TextColor, myButton.Font, etc. The specific methods and properties available depend on the control type. You can create event handlers for a control's events by defining handler functions named ControlName_EventName (e.g. myButton_Accepted for the Accepted event of the control named myButton).

Finally, the following function is available to your main script for communicating with your Decal-side script:

Method or property

Result type

Description

skapi.CallPanelFunction(szPanel, szFunction, args)

None

Calls an embedded function in your Decal-side view panel script. szPanel is the name of the panel containing the desired function, and szFunction is the name of the function to call. args is an optional argument list to pass to the embedded function. You can pass up to five arguments this way. The embedded function must be declared with the same number of formal parameters as the actual number of arguments passed. To pass more than five arguments, build an array (a VBArray in JScript) and pass that. Note that the called function still executes on the Decal side, even when triggered from your main script. Any result returned by the Decal-side function is discarded, and no result is returned from skapi.CallPanelFunction.

Custom plugins

If Decal-side UI scripting still doesn't get you the UI performance you need, SkunkWorks allows you to set up two-way communication between your script and a custom Decal plugin of your design. You might build a plugin in C++ or Visual Basic to take advantage of the full range of Decal UI facilities as well as the higher performance of compiled code, while still using the SkunkWorks scripting capability for the bulk of your macro's logic.

A plugin written for this purpose can communicate with the running script by connecting to the SkunkWorks filter in the usual Decal fashion. For example in VB you'd do something like this:

Private WithEvents swf As SWFilter.SWFilterCls

And in your IPlugin_Initialize function:

Set swf = Site.NetworkFilter("SWFilter.SWFilterCls")

To send a message to the running script, use the following function:

Method or property

Result type

Description

swf.QueueMsgForScript(szMsg)

None

Queues a message for the running script. szMsg (a string) is the text of the message. The script receives the message by way of OnPluginMsg.

To receive a message from the running script, implement the following event:

Event name and parameters

Description

swf.OnScriptMsg(szMsg)

A message has been received from the running script. szMsg (a string) is the text of the message.

CAUTION: SWFilter has other methods and properties used by the SkunkWorks plugin to implement native SkunkWorks functions. Please do not call these other methods from your custom plugin. For stable operation, use only the methods and events documented here.

On the script side, messages from the plugin are received by way of OnPluginMsg. To send a message to the plugin, use this function:

Method or property

Result type

Description

skapi.QueueMsgForPlugin(szMsg)

None

Queues a message for a custom plugin of your design. szMsg is the text of the message. The plugin receives the message by way of swf.OnScriptMsg.

SkunkWorks does not interpret the content of these messages in any way, but simply passes them through from plugin to script and vice versa. Your code is free to impose any structure and format you like on these messages, so long as they remain valid text strings. As a matter of convention, it might be wise to prefix messages to or from a particular plugin with the name of that plugin, just to avoid confusion in the (unlikely) case of multiple custom plugins.

Screen and pixel functions

Method or property

Result type

Description

skapi.dxpRes

Long

Your configured gameplay screen width, in pixels, as specified in the in-game Sound/Graphics options panel. This is equivalent to ACScript's Width global, and remains constant even as the actual screen resolution changes during login. Note also that if you change your resolution settings in mid-game, skapi.dxpRes will not change until the next time you log in.

skapi.dypRes

Long

Your configured gameplay screen height, in pixels. Equivalent to ACScript's Height global.

skapi.dxpWindow

Long

The actual width in pixels of the AC window's client area, i.e. the area in which the game screen itself is displayed, excluding any window borders. If you play in full screen mode (the default), then there are no borders and this is the same as the screen width. This is a dynamic quantity that changes along with the window size as you log in.

skapi.dypWindow

Long

The actual height in pixels of the AC window's client area. Like skapi.dxpWindow, this is a dynamic quantity that changes along with the window size.

skapi.clrPixel(xp, yp)

Long

Returns the color of the pixel at coordinates xp,yp as a long in standard Windows RGB format (red in the low byte, green in byte 1, blue in byte 2, zero in byte 3). If you're running AC in windowed mode, xp and yp are client coordinates within the window; otherwise they're screen coords, so the same numerical values work in either case. xp and yp may be positive or negative. Positive values measure down from the upper left corner of the window; negative values measure up from the lower right. For resolution independence, specify pixels near the right or bottom edges using negative xp or yp.

skapi.PxtNew(xp, yp, clrBefore, clrAfter)

PXT

Creates and returns a pixel transition (PXT) object that can be used to monitor the state of a particular pixel. xp and yp are the pixel coordinates, and may be positive or negative (as in skapi.clrPixel). clrBefore is the pixel color before the transition; if you pass clrNil as clrBefore, the current pixel color is used. clrAfter is the pixel color expected after the transition. See PXT properties for more information.

skapi.xpLeft3D

Long

The X-coordinate of the left edge of the 3D view.

skapi.xpRight3D

Long

The X-coordinate of the right edge of the 3D view.

skapi.ypTop3D

Long

The Y-coordinate of the top edge of the 3D view.

skapi.ypBottom3D

Long

The Y-coordinate of the bottom edge of the 3D view.

Game events

SkunkWorks, like ACScript before it, is an event-driven scripting system. That means much of what your script does will be in reaction to things that happen around you (or to you) in the game world. You find out about these game events by providing event handlers, i.e. functions you write that get called when things happen that you might be interested in.

Unlike ACScript, event handling in SkunkWorks is based on objects rather than ordinary procedures. To handle an event, define a JScript or VBScript class that has a method with the same name as the event you want to handle (e.g. OnAddToInventory for the OnAddToInventory event) and with parameters as indicated in the table below. Then create an instance of that class and register it using skapi.AddHandler. When the event occurs, your handler method will be called with those parameters. You can use the same handler object to handle several different event types by defining a handler method for each one, and registering the object multiple times. You can also register several different handlers for the same event type; this is useful for modularizing your code.

If you're using the COM interface to access the SkunkWorks API, you can receive events in either of two ways: by creating and registering handler objects as just described, or by using COM-style event sinking via skapi.skev.

Events arrive continuously while the game is running and are queued by SkunkWorks for processing. They remain in the queue until you call skapi.WaitEvent, which then dequeues the events, does any internal processing needed to maintain object tables and inventory, and calls the handlers you've registered. This model of event processing (which emulates ACScript's) has several consequences you should be aware of:

The methods for registering and unregistering handlers and for receiving events are as follows:

Method or property

Result type

Description

skapi.AddHandler(evid, handler)

None

Registers an event handler. evid is the event ID of the desired event. handler is a handler object as described above, and must have a method with the same name and parameters as the event specified by evid. You can register the same handler for several different events by calling skapi.AddHandler multiple times with different evids (but it had better have a method for each of them).

There are a number of server messages described in the AC protocol documentation for which no corresponding SkunkWorks event exists. You can register to handle these messages directly, and do the decoding yourself in script, by calling skapi.AddHandler with a message type or mty in place of evid. Derive an mty from the protocol docs as follows:

  • For top-level messages, shift the message ID left by 16 bits (or equivalently, multiply by 65536). So for instance message 0037 (Local Chat) has an mty of 0x00370000. (If you pass an unshifted message ID such as 0x0037 to skapi.AddHandler, it will mistake it for an evid and do something random.)
  • For subtypes of message F7B0 (Game Event) or F7B1 (Client Event), concatenate F7B0 (or F7B1) with the subtype ID. So for instance message F7B0 0x0013 (Login Character) has an mty of 0xF7B00013.

There's a known issue when trying to register an mty greater than 0x7FFFFFFF from JScript. Such large numbers are treated by JScript as positive Doubles, whereas skapi.AddHandler expects a Long. If you get an overflow error when registering a large mty from JScript, try registering mty−0x100000000 instead. (That's mty minus 0x100000000.) This puts it back within the range of valid Long values, without changing the actual 32-bit bit pattern. (Note that this is a problem with JScript only; VBScript hex values are already valid Longs.)

Server messages registered in this way are passed to your script by way of OnRawServerMessage. Please see the documentation of that event for more information, including some important cautions.

skapi.RemoveHandler(evid, handler)

None

Removes an event handler previously registered with skapi.AddHandler. evid is the event ID of the event for which you want the handler removed; handler is the same handler object you passed to skapi.AddHandler. If you've registered the same handler object with more than one evid, you can remove them all at once by passing evidNil (zero). To remove all handlers for a given evid, pass null or Nothing in place of handler (but be warned that this will remove even the default ACMsg_* handler as well). Passing evidNil together with null/Nothing removes all handlers from all events (including the default ACMsg_* handlers).

skapi.WaitEvent(cmsec, wem, evid)

Long

Processes queued events and calls registered event handlers. cmsec is a timeout value in milliseconds; if omitted, it defaults to zero. wem is one of four WaitEvent modes; if omitted, it defaults to wemNormal. evid is an event ID required by wemSpecific and ignored by all other modes.

If wem is wemNormal, WaitEvent waits up to cmsec milliseconds for an event to arrive. It then processes events until the timeout elapses or the queue is empty, whichever comes first. If cmsec is zero (or omitted), there is no timeout; WaitEvent processes events until the queue is empty, however long that takes. (Note that this can be quite a while if events are arriving quickly and your handlers are handling them slowly.) In this mode WaitEvent returns the count of events processed.

If wem is wemSingle, WaitEvent waits up to cmsec milliseconds for a single event of any type, processes it, and then returns the evid of the event processed (or evidNil if no event arrives before the timeout elapses).

If wem is wemSpecific, WaitEvent waits up to cmsec milliseconds for an event of the type specified by the third parameter evid. In the meantime it processes and calls handlers for any other events that arrive. wemSpecific is therefore not a guarantee that only the specified handler will fire; any handler can potentially fire while waiting for the desired event. If no such event arrives before the timeout, WaitEvent returns the evid of the last event processed (which may be evidNil if no events were processed). If the desired event does arrive, WaitEvent processes it and returns its evid (which will be the same as the evid passed in). So the way to tell if you got what you were looking for is if evidOut == evidIn; any other evid indicates a timeout.

If wem is wemFullTimeout, WaitEvent processes events for the full timeout interval specified by cmsec, and then returns the count of events processed. If you need to wait a significant interval (much more than a second, say) for something to happen in game, this is the way to do it, because it keeps the queue from overflowing while you're waiting. Note that this is not a terribly good way to do precise timing, since the time taken to process any single event (including the time taken by your handlers) is highly variable and can therefore overshoot the specified timeout by an unpredictable amount.

skapi.skev

Object

An object of type SkevCls that acts as a COM event source for applications that wish to use COM-style event sinking. This object exposes a COM event for each game event in the table below. See COMSample.vpb for a simple example of how to use this object.

skapi.cmsecQueueDelay

Long

The number of milliseconds elapsed since the last message to be processed was placed in the message queue. As noted above, messages are not processed immediately on arrival but are queued for later processing by WaitEvent when your script gets around to it. cmsecQueueDelay provides a rough measure of how long that takes, and therefore how far behind real time your script is running.

The event types you can handle are as follows. For each type there is a corresponding event ID with a similar name, e.g. evidOnAddToInventory for the OnAddToInventory event. It's worth mentioning that it's the game's own communications protocol, not me, that determines what event types are available. So although it might be handy to have events to tell you when the selection changes, or which pack is open on the inventory panel, or whether the merchant interface is open or closed, there aren't any such, since those are purely UI issues of no concern to the server, and hence not part of the protocol. If you're thinking of wishing for a new event, I'm open to suggestions, but please check the AC protocol documentation first to see what's possible before submitting your request.

What follows is my best understanding of what the events mean and how they work. But since I didn't invent them, it's possible I've got the details wrong in some cases. If you see any such discrepancies, be sure to let me know.

ACScript aficionados will notice that the event parameters listed in the table differ in many cases from those of the corresponding ACScript events. Mostly I have replaced OIDs with ACOs, and eliminated some redundant parameters (such as the name of the ACO) whose values can easily be obtained from the ACO. In a few cases (such as OnApproachVendor) I've added extra parameters to provide additional information to the handler. In no case, however, have I left out any information provided by ACScript; you just might have to get it from the ACO instead of having it passed directly as a parameter.

Of course the old ACScript style of event handling still works if you prefer to continue doing things that way. In that case, declare your handlers as you always have, as static procedures with parameters as described in the ACScript documentation. Note, though, that the automatic registration of standard ACMsg_* handlers works only for event types defined by ACScript. For event types new to SkunkWorks, you must register your handler explicitly using either skapi.AddHandler or ac.RegisterHandler.

Event name and parameters

Description

OnActionComplete( )

The last action taken by your character (spellcasting, tradecraft, whatever) has completed.

OnAddToInventory(aco)

An item or pack has just been inserted into your inventory, for whatever reason: you picked it up, you moved it from one pack to another or from place to place within a pack, you made it, you bought it, somebody gave it to you, etc. aco is the item in question; its acoContainer and iitem properties tell you where it was inserted.

OnAdjustStack(aco, citemPrev)

A stack of items in your inventory has been split or merged. aco is the stack from which items are being taken or into which items are being merged. citemPrev is the old stack size, prior to the split or merge; the new stack size may be obtained from aco.citemStack. In the case of a stack split, you'll get a separate OnObjectCreate event for the portion being split off; in the case of a merge you may see an OnRemoveFromInventory event if the source stack came out of your inventory.

OnAnimationSelf(dwA, dwB, dwC, dwD, dwE)

Your character is doing some animation on screen: waving, cheering, fidgeting, making something, healing, attacking, whatever. The five parameters have something to do with what sort of animation it is; I don't know much more about it than that. Provided mainly for ACScript compatibility.

OnApproachVendor(acoMerchant, mcmBuy, cpyMaxBuy, fractBuy, fractSell, coaco)

You have approached a merchant in order to do business. acoMerchant is the merchant; you can find out things like his or her name, geographical location, and so forth from the properties of this ACO. mcmBuy is a set of merchant category masks telling you what categories of item the merchant will buy (which are not always the same as the categories he or she sells). cpyMaxBuy is the maximum value of item the merchant will buy; so even if the category is flagged in mcmBuy, if your item is too expensive, the merchant won't take it. fractBuy, a floating-point number between zero and one, is the fraction of an item's face value the merchant will pay for it; for instance a fractBuy of 0.8 means the merchant pays 80% of face value. fractSell is the merchant's markup on items he or she sells; a fractSell of 1.25 means you'll pay 25% over face value to shop there.

coaco is a collection of ACOs representing the items the merchant has for sale, in no particular order; this includes regular stock items as well as items recently purchased from other players. You can find out more about the individual items (their names, values, mcm categories, etc) by checking the ACO properties of these ACOs. In particular, you can use aco.citemAvail to distinguish regular stock items (citemAvail < 0) from recently bought items (citemAvail > 0).

You can also use filters generated by skapi.AcfNew to filter the collection for items matching various criteria, or use coaco methods to weed out uninteresting items.

OnAssessCreature(aco, fSuccess, cbi, cai, chi)

You've attempted to assess a creature, NPC, or fellow player. aco is the creature being assessed; you can get its name and other information from its ACO properties. fSuccess gives a rough idea whether you succeeded or failed (true for success, false for failure); however even if you fail you might still get some useful information. cbi, cai, and chi may contain additional information, depending on what kind of creature it is and how successful you were at assessing it. See OAI properties for details.

OnAssessItem(aco, fSuccess, ibi, iai, iwi, iei, ipi)

You've attempted to assess an item. aco is the item being assessed. fSuccess gives a rough idea whether you succeeded or failed (true for success, false for failure); however even if you fail you might still get some useful information. ibi, ibi, iwi, iei, and ipi may contain additional information, depending on what kind of item it is and how successful you were at assessing it. See OAI properties for details.

OnChatBoxClick(aco)

You have clicked on a highlighted player name in the chat window. aco is that player.

OnChatBoxMessage(szMsg, cmc)

A message of some kind has appeared in the in-game chat window. This may be a chat message or tell from another player, a global broadcast message, a spellcasting message, or any text whatsoever generated by the client. (Note that messages generated by other plugins do not cause this event to fire.) szMsg is the text of the message, and cmc is a chat message color code.

OnChatBroadcast(acoSender, szMsg, cmc)

Some NPC (typically a crafter or collector) has made a broadcast announcement (e.g. "Bring me the teeth of the Ivory Gromnie" or whatever). acoSender is the ACO of the NPC sending the message, szMsg is the text of the message, and cmc is a chat message color code. You can get the sender's name from acoSender.szName.

OnChatEmoteCustom(acoSender, szMsg)

A player has typed out an emote message. acoSender is the sender; szMsg is the text of the message. Use acoSender.szName to get the sender's name.

OnChatEmoteStandard(acoSender, szMsg)

A player has used a predefined emote command. acoSender is the sender; szMsg is the text of the message. Use acoSender.szName to get the sender's name.

OnChatLocal(acoSender, szMsg)

A player has sent a broadcast chat message. acoSender is the sender; szMsg is the text of the message. Use acoSender.szName to get the sender's name.

OnChatServer(szMsg, cmc)

You've received a message from the server regarding some game event; e.g. "You make a fire infusion", "Your base Healing skill is now 147!", etc. szMsg is the text of the message; cmc is a chat message color code that you can use to do some rough classification of the various messages.

OnChatSpell(acoSender, szMsg)

Somebody is spellcasting nearby. acoSender is the spellcaster; szMsg is the text of the magical spell phrase ("Shurov Thiguz" or whatever).

OnCloseContainer(aco)

You've closed the container panel on the container indicated by aco.

OnCombatMode(fOn)

You've entered or exited combat mode. fOn is a Boolean specifying your new state: true if you're entering combat mode, false if you're exiting.

OnCommand(szCmd)

The user has typed a script command into the game's chat line or the miniconsole's Command tab. szCmd is the command string the user typed.

Commands typed into the game's chat line must be prefixed by a distinctive string to distinguish them from ordinary chat. The default prefix is "/sw " (without quotes, but with a trailing space), but you can change this by editing the contents of the Prefix blank on the miniconsole's Command tab. If no prefix is specified (i.e. if the blank is empty), you cannot enter commands from the chat line.

If you prefer, you can use the Hotkey dropdown to configure a hotkey that brings up the Command panel and allows you to type in your script commands there instead of into the chat line. In this case the /sw prefix is not needed. A dropdown list of recent commands is also available, which you can cycle through using the uparrow and downarrow keys.

Whether you enter commands into the chat line or the Command panel, pressing Enter terminates the command and sends the command string to the script where it fires the OnCommand event with the text of the command in szCmd. This gives you a way to control your script from in game without using tells. See the CommandSample demo script for an example of how to use this feature.

OnControlEvent(szPanel, szControl, dictSzValue)

The user has clicked a control on an in-game control panel you displayed using skapi.ShowControls. szPanel is the name of the active panel, szControl is the name of the control that was clicked, and dictSzValue is an instance of Scripting.Dictionary containing a name,value pair for each named control on the panel.

Typical logic for handling this event goes something like this: First, check szPanel to make sure it matches the name of your panel. (The SkunkWorks console uses this same mechanism to display its in-game miniconsole, and you don't want to be responding to the wrong controls.) Next, switch on szControl to decide what action to take depending on which control caused the event. Finally, extract any values you need from dictSzValue by indexing with the names of controls containing those values. See the ControlSample demo script for an example of this logic.

OnControlProperty(szPanel, szControl, szProperty, vValue)

Your script has called skapi.GetControlProperty to interrogate the value of a control property. This event is the response. szPanel, szControl, and szProperty are the same values passed to GetControlProperty, to help you match up events with the calls that generated them. vValue is a Variant containing the returned value.

OnCraftConfirm(szOdds)

You have attempted a crafting operation with the "Crafting Chance of Success Dialog" option enabled. szOdds is a text string giving your chances of success as a percentage (e.g. "99" for a 99% chance of success). This event fires when the confirmation panel appears in game.

OnDeathMessage(szMsg)

I believe this happens when you kill a creature. szMsg is the text of the message ("Gnawer Shreth is torn to ribbons by your assault!").

OnDeathOther(acoVictim, acoKiller, szMsg)

Someone (not you) has been killed nearby. acoVictim is the unfortunate departed, acoKiller is the one who did the deed, and szMsg is the text of the message ("Gnawer Shreth tears Joe Blow to ribbons!").

OnDeathSelf(acoKiller, szMsg)

You have been killed. acoKiller is the one who did the deed, and szMsg is the text of the message ("Gnawer Shreth tears <your name> to ribbons!").

OnDisconnect( )

Your server connection has been dropped and the game is at the "Server Connection Lost" screen. Note that for now this works only while logged in. I currently have no good way to detect disconnects that happen at the login screen.

OnEnd3D( )

You've logged out and are on your way back to the game's login screen.

OnEndPortalOther(aco)

Someone (not you) has emerged from a portal nearby. aco is that person.

OnEndPortalSelf( )

You have emerged from portal space into the game world. You'll also see this when you first materialize after logging in.

OnFellowCreate(szName, fShareExp, oidLeader, cofellow)

You have created or joined the fellowship named by szName. fShareExp is a Boolean indicating whether XP is to be shared among the members of the fellowship. oidLeader is the object ID of the fellowship leader. This is an OID rather than an ACO because the leader may be far away at the time you join. cofellow is a collection of Fellow objects describing the members of the fellowship. See skapi.cofellow for details of Fellow object properties.

OnFellowDisband( )

Your fellowship has been disbanded.

OnFellowDismiss(oid)

A member has been dismissed from your fellowship. This is an OID rather than an ACO because the member may be far away at the time.

OnFellowInvite(szSender)

You have been invited to join a fellowship. szSender is the name of the player seeking to recruit you. This is the event that causes the confirmation dialog to appear in game. After you accept the invitation, OnFellowCreate fires with the fellowship information.

OnFellowQuit(oid)

A member has quit your fellowship. This is an OID rather than an ACO because the member may be far away at the time.

OnFellowRecruit(fellow)

A new member has been recruited into your fellowship. fellow is a Fellow object describing the new member. See skapi.cofellow for details of Fellow object properties.

OnFellowUpdate(fellow)

The server has sent updated stats for a member of your fellowship. fellow is a Fellow object containing the updated stats. See skapi.cofellow for details of Fellow object properties.

OnHotkey(cmid, vk)

A keystroke has been received for which hotkey events have been enabled via skapi.Hotkey. This may be either an actual physical keystroke by the user or a scripted keystroke from skapi.Keys. cmid is the key specification you passed to skapi.Hotkey and may be a physical key name such as "K" or "{space}" or a logical command ID such as cmidPointState or cmidJump. vk is the numerical virtual key code of the actual physical key.

OnItemManaBar(aco, fractMana)

Sent periodically to update the mana bar in the lower right corner of the screen when you have an enchanted item selected. aco is the selected item; fractMana is a number between zero and one indicating the fraction of full charge remaining. This doesn't tell you the actual number of mana points remaining, or how long you have before the mana runs out; for that you need to assess the item.

OnLeaveVendor(acoMerchant)

You have finished dealing with a merchant by closing the merchant window, or by walking away so that it closed automatically, or by opening a new merchant window on a different merchant. acoMerchant is the merchant you were previously dealing with.

OnLocChangeCreature(aco)

Some monster or NPC is moving around nearby. aco is that creature; to find its location, use aco.maploc.

OnLocChangeOther(aco)

Some player (other than yourself) is moving around nearby. aco is that player; to find his or her location, use aco.maploc.

OnLocChangeSelf(maploc)

You are moving around. maploc is your new location (which you can also get from skapi.acoChar.maploc). Note that this is the server's idea of your location, and may be slightly out of date; see skapi.maplocCur for more up-to-date information.

OnLogon(acoChar)

You've logged in and the skapi properties described in Information about your character (such as your current health, stamina, and mana levels) have been set up. acoChar is you, and is the same as skapi.acoChar.

Receiving this event does not mean that you've materialized in the game world; this generally comes while still in portal space. Look for OnEndPortalSelf to signal your arrival in game.

OnMeleeDamageOther(szName, dhealth, dmty)

You've inflicted damage on an opponent in combat. szName is your opponent's name (sorry, the packet doesn't include an OID so I can't give you an ACO). dhealth is the number of health points your opponent lost as a result of your attack, and dmty is the damage type you inflicted.

Note that despite the name, this event applies to missile as well as melee combat.

OnMeleeDamageSelf(szName, dhealth, dmty, bpart)

You've received damage in combat. szName is the name of your attacker, dhealth is the number of health points you lost, dmty is the damage type you received, and bpart is the body part that was injured.

Note that despite the name, this event applies to missile as well as melee combat.

OnMeleeEvadeOther(szName)

An opponent dodged your attack. szName is the opponent's name.

Note that despite the name, this event applies to missile as well as melee combat.

OnMeleeEvadeSelf(szName)

You dodged an attack. szName is the attacker's name.

Note that despite the name, this event applies to missile as well as melee combat.

OnMeleeLastAttacker(aco)

Someone or something has attacked you. aco is the attacker. Unlike the other OnMelee* events, OnMeleeLastAttacker gives you the attacker's actual ACO rather than just its name. Note that if you're being attacked on several fronts simultaneously, you'll get a lot of these events with different ACOs.

Note that despite the name, this event applies to all forms of combat, including magic and missile as well as melee.

OnMoveItem(aco, acoContainer, iitem)

An item has been moved from one slot to another in your inventory. aco is the item; acoContainer is the pack it used to be in, and iitem is the position it used to occupy in that pack. If aco is itself a pack, then acoContainer is you and iitem is the pack slot from which it was removed. The new inventory position is given by aco.acoContainer and aco.iitem.

OnMoveItem is similar to OnRemoveFromInventory, except that OnMoveItem fire when an item is moved from one inventory slot to another, whereas OnRemoveFromInventory fires when the item is removed from your inventory completely.

OnMyPlayerKill(aco, szMsg)

You have killed another player in a PK duel. aco is that other player, and szMsg is the taunting death message.

OnNavStop(nsc)

A navigation action started by skapi.GoToMaploc has ended. nsc is a navigation stop code describing the outcome, and may be one of nscArrived (your character arrived safely at the specified destination), nscTimeout (your character failed to arrive within the specified timeout interval), or nscCanceled (navigation was stopped by a call to skapi.CancelNav).

OnObjectCreate(aco)

An object has moved into your vicinity or materialized nearby (or else you've materialized near an existing object). aco is that object; see its ACO properties for information about it.

OnObjectCreatePlayer(aco)

A player has moved into your vicinity or materialized nearby (or else you've materialized near an existing player). aco is that player; see its ACO properties for information about it.

OnObjectDestroy(aco)

An object is being destroyed or otherwise removed from the scene. aco is that object; see its ACO properties for information about it. Note that there are a number of ways in which objects can implicitly disappear without triggering this event, including moving out of range or being sold or given away. This covers only those cases in which the object is explicitly consumed, killed, or merged into another object.

OnOpenContainer(aco)

You've opened a chest, corpse, pack on the ground, or other such container and now have access to its contents. aco is the container object (i.e. the chest or pack or whatever); use its citemContents property and AcoFromIitem method to access the contents.

Note, though, that the actual creation of the contained items happens separately via OnObjectCreate. So although OnOpenContainer tells you which items are in the container, the properties of any given item won't be filled in until OnObjectCreate is received for that item. If you find an item in the container whose aco.szName is empty, that's a clue that that item hasn't been created yet and you'll need to wait for OnObjectCreate of that item before accessing its properties.

OnOpenContainerPanel(aco)

The client has opened the container panel in response to your action of using a corpse, chest, or other container. aco is the container being opened. This event is distinct from OnOpenContainer in that it fires every time the container panel is opened, even if the container contents are already known from a previous occurrence of OnOpenContainer.

OnPluginMsg(szMsg)

A message has been received from a custom plugin. szMsg is the text of that message. See Custom plugins for detailed information on how to set up two-way communication between your script and a custom Decal plugin of your design.

OnPortalStormed( )

You've been portal stormed away from where you were. I believe this event can happen while still in portal space, so you may need to wait for OnEndPortalSelf before checking skapi.maplocCur to find your new location.

OnPortalStormWarning(psw)

You've received a portal storm warning from the server. psw is a portal storm warning code indicating the severity of the storm.

OnRawServerMessage(mty, mbuf)

A raw, undecoded server message is being passed to your script for custom decoding. mty is the message type, and mbuf is a message buffer object containing the message data. See Mbuf properties for details on how to access that data. For information on specific server message formats, see the AC protocol documentation.

OnRawServerMessage does not fire for all server messages, but only for those you have specifically registered to receive using skapi.AddHandler. There is no evid for OnRawServerMessage; instead, register using the mty (message type) as described under skapi.AddHandler.

CAUTION: This is a powerful feature that could easily become a performance killer if abused, so use it sparingly. Note also that by decoding messages in your script you are bypassing Messages.xml and thus are on your own if monthly protocol changes break your decoding logic.

OnRemoveFromInventory(aco, acoContainer, iitem)

An item has been removed from your inventory. aco is the item; acoContainer is the pack it used to be in, and iitem is the position it used to occupy in that pack. If aco is itself a pack, then acoContainer is you and iitem is the pack slot from which it was removed.

OnRemoveFromInventory is similar to OnMoveItem, except that OnMoveItem fire when an item is moved from one inventory slot to another, whereas OnRemoveFromInventory fires when the item is removed from your inventory completely.

OnSetFlagOther(aco, iflag, fOn)

A nearby character has been promoted to PK status, or demoted to non-PK status. aco is that character, iflag is always 4 for PKs, and fOn is a Boolean indicating whether the PK flag is being set or cleared. There may be other uses for this message but so far no one has discovered them.

OnSetFlagSelf(iflag, fOn)

You have been promoted to PK or demoted to non-PK. iflag is always 4, and fOn is a Boolean indicating whether the PK flag is being set or cleared.

OnSpellAdd(spell)

Some creature or object has cast a spell on you affecting your stats. spell is a spell object containing the specifics of the spell and its effects. See Spell properties for details.

OnSpellCastSelf(spellid)

You succeeded in learning a spell from a scroll. spellid is the numerical spell ID of that spell.

NOTE: For historical reasons, this event is misnamed. It should be named OnSpellLearn. Under the old magic system (prior to June 2002), it fired when you learned a spell by research and also whenever you cast the spell thereafter. Under the new magic system (since June 2002), there is no spell research, and this event fires only when you first learn the spell from a scroll, and not when you cast the spell. Unfortunately it's too late to change the event name without breaking a lot of existing code.

OnSpellExpire(spellid, layer)

A spell cast on you has worn off. spellid is the numerical spell ID. layer is the layering count of the spell. If the identical spell has been cast on you more than once, the different instances will have different layer numbers.

OnSpellExpireSilent(spellid, layer)

A spell has worn off silently (for instance when you take off a magic ring). spellid is the numerical spell ID. layer is the layering count of the spell. If the identical spell has been cast on you more than once, the different instances will have different layer numbers.

OnSpellFailSelf(arc)

You failed to cast a spell. arc is an action result code giving the reason for the failure. You may also see this event in other situations than spellcasting (e.g. charging too far, missile misfire, etc).

OnStart3D( )

You've selected a character and clicked the "Enter" button to log in to the game. Usually comes before the OnLogon event.

OnStartPortalSelf( )

You've stepped into a portal and are in portal space. Look for OnEndPortalSelf to tell you when you've arrived back in the game world.

OnStatLevel(lvl)

You've gained a character level. lvl is your new level number (also available from skapi.lvl).

OnStatSkillExp(skid, szSkill, diff, expTotal, dsklvl)

You've gained experience in some skill. skid is the skill ID of the skill in question; szSkill is the skill name in string form. diff is the difficulty of the task that earned you the XP, which determines how much XP that task is worth. expTotal is the total amount of experience you've earned in this skill since character creation. dsklvl is the number of skill levels you've gained in this skill since character creation, i.e. the difference between your current (unbuffed) skill level and the skill level given by the standard formula for that skill (e.g. (Focus + Coord)/3 or whatever).

OnStatTotalBurden(burden)

The total weight you're carrying has changed. burden is the new total in BU.

OnStatTotalExp(exp)

Your total experience has changed. exp is the new total.

OnStatTotalPyreals(cpy)

Your total cash balance (the number of pyreal coins you're carrying) has changed. cpy is the new total.

OnStatUnspentExp(exp)

Your unspent experience has changed. exp is the new figure.

OnTargetHealth(aco, fractHealth)

Sent periodically to update the health bar in the lower right corner of the screen when you have a player or creature selected. aco is the selected player or creature; fractHealth is a number between zero and one indicating the fraction of full health remaining. This doesn't tell you the actual number of health points remaining; for that you need to assess the creature.

OnTell(acoSender, acoReceiver, szMsg)

Somebody has sent you a tell. acoSender is the sender, acoReceiver is you, and szMsg is the text of the message. Use acoSender.szName to get the sender's name.

OnTellAllegiance(szSender, szMsg)

Your monarch or allegiance speaker has sent a broadcast message to the entire allegiance using @a. szSender is the name of the sender; szMsg is the text of the message.

OnTellCovassal(szSender, szMsg)

One of your covassals (possibly you) has sent a broadcast message using @c. szSender is the name of the sender, unless it's you; in that case szSender is the empty string. szMsg is the text of the message.

OnTellFellowship(szSender, szMsg)

A member of your fellowship (possibly you) has sent a message to the entire fellowship using @f. szSender is the name of the sender, unless it's you; in that case szSender is the empty string. szMsg is the text of the message.

OnTellFollower(szSender, szMsg)

You are a monarch and one of your followers has sent you a message using @m. szSender is the name of the sender; szMsg is the text of the message. This event probably ought to be called OnTellMonarch, but for compatibility reasons it's named for the sender instead.

OnTellMelee(acoSender, acoReceiver, szMsg)

Like OnTell; I'm not sure precisely what circumstances lead to which event.

OnTellMisc(acoSender, acoReceiver, szMsg, cmc)

Like OnTell; I'm not sure precisely what circumstances lead to which event. cmc is a chat message color code.

OnTellPatron(szSender, szMsg)

Your patron has sent a broadcast message to his or her vassals using @v. szSender is the name of the sender; szMsg is the text of the message. This event probably ought to be called OnTellVassals, but for compatibility reasons it's named for the sender instead.

OnTellServer(acoSender, acoReceiver, szMsg)

Like OnTell; I'm not sure precisely what circumstances lead to which event.

OnTellVassal(szSender, szMsg)

One of your vassals has sent you a message using @p. szSender is the name of the sender; szMsg is the text of the message. This event probably ought to be called OnTellPatron, but for compatibility reasons it's named for the sender instead.

OnTimer(timer)

A timer object has reached its programmed timeout interval. timer is the timer in question. Note that OnTimer is a one-shot event that does not automatically repeat. You can make it repeat by simply restarting the timer from within the event handler.

OnTipMessage(szMsg)

A message of some kind has appeared in the upper left corner of the game screen. szMsg is the text of the message.

OnTradeAccept(aco)

One of the participants in a secure trade has accepted the terms of the deal. aco is the player doing the accepting.

OnTradeAdd(aco, side)

An item has been added to the secure trade window. aco is the item; side is 1 for the right side (the items you're giving), 2 for the left side (the items you're receiving).

OnTradeEnd(arc)

Secure trade has ended. arc is an action result code giving the reason; arcSuccess indicates a successful trade.

OnTradeReset(aco)

One of the participants in a secure trade has cleared the trade window. aco is that player.

OnTradeStart(acoSender, acoReceiver)

Secure trade has been initiated. acoSender is the player initiating the trade; acoReceiver is the trading partner.

OnVitals(health, stamina, mana)

One or more of your vital stats has changed. health, stamina, and mana are the new values of those stats. Note that these are your current levels, not your maximum levels.

Miscellaneous functions

Method or property

Result type

Description

skapi.szVersion

String

The build version of the SkunkWorks API in the form major.minor.build, e.g. "1.1.287".

skapi.szArgs

String

The contents of the Arguments blank on the SkunkWorks console. The argument text is not parsed out or broken down in any way, but simply passed as a single string exactly as it appears in the blank. You are of course free to parse it any way you like in your script, or use ac.Arguments to retrieve a collection of semicolon-delimited substrings.

skapi.plig

Long

A player in game status code telling you whether the game is running and if so, how far along you are in the login process: at the login screen, in portal space, or all the way into the game world.

skapi.fInChatBuffer

Boolean

True if the in-game chat buffer has the keyboard focus (the rectangular yellow highlight) and is ready to accept typein, false if it does not.

Note that since this works by reading client memory, the value of skapi.fInChatBuffer can change without notice at any time, whether or not you've called skapi.WaitEvent.

skapi.fPointerBusy

Boolean

True if the game's mouse pointer is in the busy (hourglass) state, false otherwise.

Note that since this works by reading client memory, the value of skapi.fPointerBusy can change without notice at any time, whether or not you've called skapi.WaitEvent.

skapi.fRunningOnNT

Boolean

True if you're running Windows NT, 2000, or XP; false if you're running 98 or Me.

skapi.szDirAC

String

The full path of your AC installation directory.

skapi.szVersionAC

String

The build version of the AC executable (ACClient.exe), e.g. "1.0.33.0". This can be useful if your script saves away information (such as OIDs) that becomes invalid on patch day.

skapi.szDirDecal

String

The full path of your Decal installation directory.

skapi.szDirSkunkWorks

String

The full path of your SkunkWorks installation directory.

skapi.Sleep(cmsec)

None

Suspends script execution for cmsec milliseconds. No messages are processed or events dispatched during the sleep interval, so use this function sparingly, and only for short intervals when you specifically do not want events to fire. For longer waits, use skapi.WaitEvent with wemFullTimeout, which processes messages while waiting.

skapi.SetIdleTime(csec)

None

Sets the client's idle timer to the number of seconds specified by csec. This prevents your character from logging out from inactivity until csec seconds have passed.

skapi.LaunchAC(szAccount, szPassword, szWorld)

None

Launches AC using the Turbine launcher. All three arguments are optional.

szAccount specifies the name of the account to log into. If omitted or empty, the default account from Launcher.ini is used.

szPassword specifies the password for that account. If omitted or empty, the Turbine launcher will pause at the login screen for you to enter your password. If szPassword is nonempty, login proceeds automatically without a pause. While this can be convenient for automatically restoring a lost connection, it is also unsafe because it requires you to code your password in cleartext in your script. I do not recommend that you use this feature unless you are completely sure that all other plugins and third-party apps on your machine are trustworthy. I cannot be responsible for accounts hijacked due to careless use of this automated login feature.

szWorld specifies the name of the world to log into. If omitted or empty, the Turbine launcher will pause at the world selection screen and wait for you to choose a world manually. If szWorld is nonempty, world selection proceeds automatically without a pause.

Note that skapi.LaunchAC just kicks off the login process and returns; it does not wait for the character selection screen to appear. It's up to you to determine when it's safe to proceed with character selection by (for instance) polling skapi.plig in a loop.

skapi.ActivateAC( )

None

Activates AC and brings it to the foreground, if it's not already there. This is similar to the function of the Activate AC checkbox on the Project Options dialog, but under script control.

skapi.hwndAC

Long

The window handle (HWND) of the AC game window, or zero if the game is not running.

skapi.DwReadMemloc(pdw)

Long

Reads a DWORD from an arbitrary address in client memory. pdw is the address to read from. If there is no physical memory at the given address, or if the memory is not readable for any reason, an error occurs.

skapi.TimerNew( )

Timer

Returns a timer object that can be used to measure time intervals and raise script events. See Timer properties for more information.

skapi.WavNew(szFile)

WAV

Returns a WAV object that can be used to play sounds. szFile is the name of the .wav file to play. Use wav.Play to actually play the sound. See WAV properties for more information.

ACO properties

The following is a description of the properties of ACO objects. Not every game object will have meaningful values for all properties; for instance only multi-use objects such as keys or healing kits will have non-zero values for cuseLeft and cuseMax.

In addition to the properties listed here, every ACO supports all the legacy properties of ACScript objects as returned by ac.GetObject. See the ACScript documentation for details of those properties.

Method or property

Result type

Description

aco.oid

Long

The unique Object ID of this ACO. (See ACOs and OIDs.)

aco.szName

String

The name of this ACO (e.g. "Pyreal", "Bundle of Arrowshafts", "Drudge Slinker", "Merchant Ledine"). Note, though, that housing hooks assume the name of the hooked item, so just because an item is named "Couch" doesn't mean it's a couch. It could be a hook containing a couch. Use aco.ocm == ocmHook to distinguish these cases.

aco.szPlural

String

The plural name of this ACO (e.g. "Pyreals", "Bundles of Arrowshafts", "Flasks of Water").

aco.oty

Long

The object type of this ACO. This is a group of bitmasks reflecting various properties of the object. oty values are not mutually exclusive but can occur in various combinations; for instance lifestones are also selectable and non-pickupable; corpses are also containers; etc. Other bits may also be set whose meaning has not yet been deciphered.

aco.ocm

Long

The object category of this ACO. ocm values are not mutually exclusive but can occur in various combinations; for instance a player is also either a PK or a non-PK; a merchant is also an NPC; etc.

aco.olc

Long

The object location category of this ACO.

aco.mcm

Long

The merchant category mask of this ACO. For many ACOs this will be mcmNil (zero); however anything that can be bought or sold at a merchant stall will have a nonzero mcm identifying the category it's sold under. This is true for any saleable ACO, be it in your inventory, on the ground, or in a merchant's catalog. This differs from ACScript, in which merchant category information is available only for items in the merchant catalog provided by OnApproachVendor.

aco.szMcm

String

The string name of the merchant category to which this ACO belongs, as it appears on the merchant category menu in game (e.g. "Weapons", "Cooking Items", "Books, Paper"). It's this category name, not the mcm proper, that determines how items are grouped together in the merchant's catalog. Different mcm values can have the same name; for instance, mcmWeaponsMelee and mcmWeaponsMissile are both called "Weapons" on the merchant menu, and are displayed together as one category.

Like aco.mcm, aco.szMcm is valid for any saleable ACO, not just those in the merchant catalog provided by OnApproachVendor.

aco.eqm

Long

The equipment coverage mask of this ACO. For non-equippable items this will be eqmNil (zero), but for equippable items it will be some combination of equipment masks. The exact combination of bits depends on the coverage of the particular item; for instance a pair of leggings might have eqmUpperLegsOuter | eqmLowerLegsOuter, reflecting its coverage of both upper and lower legs (but not the abdomen). Similarly, a ring will have eqmLRing | eqmRRing, reflecting its ability to occupy either ring slot.

aco.cpyValue

Long

The value of this item in pyreals (or zero if the item has no value).

aco.material

Long

The material code of the stuff this object is made of.

aco.workmanship

Single

The quality of workmanship of this item, on a scale of 0-10, with 10 being best.

aco.citemStack

Long

If the ACO represents a stack of items, the number of items in the stack. For non-stackable items this is always one.

aco.citemMaxStack

Long

If the ACO represents a stackable item, the maximum number of items that can be stacked. For non-stackable items this is one.

aco.cuseLeft

Long

For multi-use items such as keys or healing kits, the number of uses remaining. For salvage bags, the number of units of salvaged material. Zero for most other items.

aco.cuseMax

Long

For multi-use items such as keys or healing kits, the maximum number of uses per item. For salvage bags, the maximum number of units the bag can hold (i.e. 100). Zero for most other items.

aco.maploc

Maploc

For objects with a definite location in the game world (such as players, NPCs, chests, portals, loot lying on the ground, and so forth), aco.maploc gives the map coordinates of the ACO in the form of a maploc object. See Maploc properties for details. For items being carried, worn, or wielded by you or another player, or contained inside some other object, aco.maploc is null/Nothing.

aco.burden

Long

The weight of this item in burden units. If the item is a container, this includes the weight of the contents.

aco.fInInventory

Boolean

True if the ACO is in your inventory (i.e. it's in a pack that you're carrying, or is the pack itself), false if not. Note that equipment you're wearing does not count as part of your inventory. Also since you are your main pack, skapi.acoChar.fInInventory is true.

aco.acoContainer

ACO

The ACO (such as a chest or pack) that contains this ACO, or null/Nothing if the ACO is not contained in anything.

aco.iitem

Long

The ordinal position (counting from zero) of this ACO in its container, or iitemNil (−1) if the ACO is not contained in anything.

aco.acoWearer

ACO

The ACO of the player or NPC wearing or wielding this ACO, or null/Nothing if it's not being worn or wielded.

aco.eqmWearer

Long

The equipment slot(s) in which this ACO is being worn or wielded, or eqmNil (zero) if it's not being worn or wielded. This can differ from aco.eqm by being more specific; for instance a ring will have eqm equal to eqmLRing | eqmRRing, because it fits in either ring slot, but when actually worn its eqmWearer must be either eqmLRing or eqmRRing (depending on which slot it's it), but not both. Note that items that cover more than one slot (such as clothing and armor) may have more than one bit set, just as for aco.eqm.

aco.oidMonarch

Long

If the ACO is a player, the OID of that player's monarch, or oidNil if the player is not a member of any allegiance group. This is an OID rather than an ACO since the monarch might be on the other side of the world, or not logged in, and therefore not currently in the object table.

aco.spellid

Long

If the ACO (such as a wand or scroll) has an associated spell, the numerical spell ID of that spell.

aco.citemAvail

Long

If the ACO is part of the coaco collection passed in to OnApproachVendor, the number of such items available from that merchant. For items the merchant regularly carries in unlimited supply, citemAvail is −1. For items sold to the merchant by other players, citemAvail is the actual number available. For unique items such as weapons and armor, this is usually one. For manufactured items such as applesauce, it may be greater than one.

aco.distApproach

Single

The approximate distance in MU that your character will approach this object in order to use it. For instance if you select a merchant and call aco.Use, this is the distance your character will walk to before the merchant window opens (which may be different for different merchants). In the case of a lifestone, this is the distance your character will approach to before performing the lifestone animation. And so forth for various other kinds of usable ACOs.

aco.fContainer

Boolean

True if the ACO (such as a chest or pack) can contain other items, false if it cannot.

aco.fOpen

Boolean

True if the door or chest represented by this ACO is in the "open" animation state, false if it's in the "closed" state. Note that this is valid only for ACOs that have open/close animations, such as doors and chests. It will not work on corpses or packs, for instance.

aco.citemMax

Long

The maximum number of items that can be contained in this ACO, or zero if it's not a container.

aco.citemContents

Long

The number of items actually contained in this ACO, or zero if it's not a container.

aco.coacoContents

COACO

A collection of the items contained in this ACO, or null/Nothing if it's not a container.

Note that aco.coacoContents cannot be modified using coaco.AddAco or coaco.RemoveAco. To add or remove items in a chest or in your inventory, use ACO functions such as aco.MoveToPack or aco.Use.

aco.AcoFromIitem(iitem)

ACO

Returns the item at ordinal position iitem (counting from zero) in the container, or null/Nothing if there is no item at that position, or if the ACO is not a container.

aco.MoveToPack(ipack, iitem, fAllowStacking)

None

Moves the ACO into a pack without having to drag it there with the mouse. ipack specifies the target pack, with pack zero being the main pack. iitem is the target position in the pack (numbering from zero), and defaults to zero if omitted. fAllowStacking is a Boolean, and defaults to true if omitted; if true, the item in question may be stacked with other similar items; if false, no stacking takes place.

Note that aco.MoveToPack starts the move and then returns without waiting for it to complete. To find out when the move has finished, wait for the OnAddToInventory event.

aco.MoveToContainer(acoContainer, iitem, fAllowStacking)

None

Moves the ACO into a container without having to drag it there with the mouse. acoContainer is the target container, and may be a pack, an open chest or corpse, etc. iitem is the target position in the container (numbering from zero), and defaults to zero if omitted. fAllowStacking is a Boolean, and defaults to true if omitted; if true, the item in question may be stacked with other similar items; if false, no stacking takes place.

Note that aco.MoveToContainer starts the move and then returns without waiting for it to complete. To find out when the move has finished, wait for the OnAddToInventory or OnRemoveFromInventory event (depending on whether you're moving items into or out of inventory).

aco.MoveToStack(acoStack)

None

Merges the ACO onto a stack without having to drag it there with the mouse. acoStack is the target stack, and must contain the same kind of items as the ACO being moved. If the combined stack would exceed acoStack.citemMaxStack items, the source stack is automatically split and the excess items left behind.

Note that aco.MoveToStack starts the move and then returns without waiting for it to complete. To find out when the move has finished, wait for the OnAdjustStack event.

Note that, despite its name, aco.MoveToStack can be used for other things besides stacking. For instance if acoStack is a merchant or other NPC, aco.MoveToStack hands over the item or adds it to the merchant's sell list as appropriate. There may be other uses as well, so feel free to experiment.

aco.Use( )

None

Uses the ACO. This function is for items can be used all by themselves, such as buff gems or food items.

aco.UseOnAco(acoTarget)

None

Uses the ACO on another object specified by acoTarget, without having to click it with the mouse. If acoTarget is omitted, the currently selected object is taken as the target. This function is (obviously) for items that do something to another item, e.g. keys on doors, oils on foods, healkits on people, etc.

aco.AddToBuyList(citem)

None

Adds an item from the merchant's inventory to the merchant Buy list. aco must be a member of the coaco passed in to OnApproachVendor. For stackable items, citem is the number of items to add; if omitted, it defaults to one.

aco.AddToSellList( )

None

Adds an item from your inventory to the merchant Sell list. To sell a partial stack, you must split the stack first.

aco.AddToTrade( )

None

Adds this item to the Secure Trade window, which must already be open.

aco.AddToUst( )

None

Adds this item to the Ust window, which must already be open.

aco.Drop( )

None

Drops this item on the ground.

aco.GiveToAco(acoTarget)

None

Gives the ACO to another object (player or NPC) specified by acoTarget, without having to click it with the mouse. If acoTarget is omitted, the currently selected object is taken as the target.

aco.Tell(szMsg)

None

Sends a chat message (@tell) to the player represented by this ACO, without using the keyboard or the chat typein buffer. szMsg is the text of the message.

aco.fExists

Boolean

True if this ACO is still in the SkunkWorks object table, false if it has gone out of range and been purged.

As you move around the game world, or as creatures around you move, ACOs are continually being added to the object table as they come into tracking range, and purged from it as they go out of range. This purging keeps the object table from growing without bound. If your script hangs onto an ACO (for instance by storing it in a global variable or collection) past the time when it gets purged, SkunkWorks will no longer track that object, and its properties will no longer be updated, but you'll still have a reference to it. You can detect such stale objects by checking aco.fExists.

aco.icon

Long

The icon number of this object in Portal.dat. An ACO's icon is the 2D picture that represents the item in your inventory or in the Assess panel. Icon numbers can be useful for distinguishing items that have the same name, such as different color packs, or different denominations of trade notes. You can also use aco.icon to get icon numbers for display in your in-game control panels.

aco.iconOverlay

Long

The icon number in Portal.dat of this object's icon overlay (if any).

aco.iconUnderlay

Long

The icon number in Portal.dat of this object's icon underlay (if any).

aco.ioc

Long

A bitmask of icon outline colors that apply to this object. Enchanted items, for instance, have a blue halo around their icons. Healing foods have a red halo, and so on.

aco.model

Long

The model number of this object in Portal.dat. An ACO's model is the 3D wireframe used to render the object in the 3D scene. Model numbers can be useful for classifying monsters by species; for instance all Banderlings have the same model number.

aco.oai

OAI

Holds the last OnAssess information received for this ACO, or null/Nothing if the ACO has never been assessed since its creation via OnObjectCreate. The information is timestamped so you can tell how long ago it was received. aco.oai is set whenever an OnAssess event fires for this ACO, regardless of whether the assessment was triggered manually using the Examine Selected command, or programmatically using skapi.AssessAco. See OAI properties for details.

COACO properties

A coaco is an ordered collection of ACOs. Coacos crop up in a number of places in the API, such as in aco.coacoContents and acf.CoacoGet. You can also create collections for your own use via skapi.CoacoNew.

Coacos share the general properties of generic collections, and can be accessed and enumerated in the same way. See Collections for examples of those generic access methods. Coacos also have some unique properties and methods of their own.

Coacos come in two flavors. Mutable coacos are those generated for your use by skapi.CoacoNew, acf.CoacoGet, and the like. Since these coacos belong to you, you can edit their contents freely using the methods listed below. On the other hand, coacos embedded in other SkunkWorks objects, such as aco.coacoContents, are immutable and cannot be directly modified by these methods. Immutable coacos change only in response to game events or function calls such as OnAddToInventory or aco.MoveToPack. You can, however, call coaco.CoacoClone on an immutable coaco to obtain a mutable copy of it.

The following methods and properties apply to all coacos, mutable or not:

Method or property

Result type

Description

coaco.Count

Long

The number of items in the collection.

coaco.Item(i)

ACO

Returns the i th element of the collection (where item numbering starts at zero). Since Item is the default property, you can also abbreviate this as coaco(i).

coaco.FContainsAco(aco)

Boolean

Returns True if the given aco is a member of the collection, False otherwise.

coaco.CoacoClone( )

COACO

Creates and returns a new, mutable coaco with the same contents as coaco.

For mutable coacos, these additional methods are available:

Method or property

Result type

Description

coaco.Clear()

None

Removes all items from the collection, resetting it to an empty state.

coaco.AddAco(aco, iitem)

None

Adds an ACO to the collection. iitem is optional; if omitted, aco is appended to the end of the collection. If iitem is specified, aco is inserted at that ordinal position in the collection (counting from zero). Thus coaco.AddAco(aco, 0) inserts at the beginning of the collection.

coaco.RemoveAco(aco)

None

Removes aco from the collection if present. Note that this does not remove or delete the ACO from the game world or from SkunkWorks' global object tables, only from this collection.

coaco.RemoveIitem(iitem)

None

Removes the ACO at ordinal position iitem (counting from zero). Note that this does not remove or delete the ACO from the game world or from SkunkWorks' global object tables, only from this collection.

coaco.AddCoaco(coaco2)

None

Performs a set union operation by appending to coaco those ACOs from coaco2 that don't already exist in coaco. To perform a set union without modifying the original coaco, use coaco.CoacoClone to create a working copy first.

coaco.RemoveCoaco(coaco2)

None

Performs a set difference operation by removing from coaco those ACOs that also exist in coaco2. To perform a set difference without modifying the original coaco, use coaco.CoacoClone to create a working copy first.

coaco.Intersect(coaco2)

None

Performs a set intersection operation by removing from coaco those ACOs that do not also exist in coaco2. To perform a set intersection without modifying the original coaco, use coaco.CoacoClone to create a working copy first.

coaco.Sort(sortby)

None

Sorts the collected ACOs into the order specified by sortby. This can be one of the predefined sort criteria from the table below, or a custom sort specification that you write yourself.

Predefined sort criteria include:

skapi.sortbyName

Sorts alphabetically by aco.szName. Case is ignored.

skapi.sortbyValue

Sorts by aco.cpyValue.

skapi.sortbyDist

Sorts by 3D distance from your character.

To reverse the sort order, use skapi.sortbyReverse(sortby). So for instance where skapi.sortbyName sorts in ascending order (A-Z), skapi.sortbyReverse(skapi.sortbyName) sorts in descending order (Z-A).

You can also combine sort criteria for more complex sorts. So for instance

coaco.Sort(skapi.sortbyName(skapi.sortbyReverse(skapi.sortbyValue)))

sorts items into ascending order by name, and where the names are the same, into descending order by value.

You're not limited to these predefined sort criteria, but are free to implement your own by defining a sortby object with the following interface:

Method or property

Result type

Description

sortby.CompareAcos(aco1, aco2)

Long

Compares two ACOs and returns a number less than zero if aco1 should come before aco2 in the sort order, greater than zero if aco1 should come after aco2, and equal to zero if the two ACOs are considered equal for sorting purposes.

So for instance to implement the sort-ascending-by-name-then-descending-by-value example directly in VBScript, you might do something like this:

    Class SortbyCls
        Public Function CompareAcos(aco1, aco2)
        '   Compare names.
            CompareAcos = StrComp(aco1.szName, aco2.szName, vbTextCompare)
            If CompareAcos = 0 Then
            '   Names are equal; compare values (in reverse order).
                CompareAcos = aco2.cpyValue - aco1.cpyValue
            End If
        End Function
    End Class
    
    Call coaco.Sort(New SortbyCls)

Or in JScript:

    function SortbyCls()
        {
        this.CompareAcos = My_CompareAcos;
        }
    
    function My_CompareAcos(aco1, aco2)
        {
    //  Compare names.
        if (aco1.szName < aco2.szName)
            {
            return -1;
            }
        else if (aco1.szName > aco2.szName)
            {
            return 1;
            }
        else
            {
        //  Names are equal; compare values (in reverse order).
            return aco2.cpyValue - aco1.cpyValue;
            }
        }
    
    coaco.Sort(new SortbyCls());

You can make the comparison calculation as complex as you like, involving any combination of ACO properties. Bear in mind, though, that your compare function will be called many times in the course of a single call to coaco.Sort, and may affect sorting performance if the calculation is too complex.

ACF properties

An ACF is an ACO collection filter, which you can use to get a filtered subset of ACOs in the game world. You can create an ACF by calling skapi.AcfNew. By default, a newly created ACF is set to include everything, i.e. all the ACOs in your possession and in the world around you, and to exclude nothing. You can narrow down the set of included ACOs by setting various properties of the ACF to exclude certain types of objects. You don't have to specify all the ACF properties, only those you care about. Properties you don't specify have no effect on the resulting collection, i.e. they filter nothing out.

It's important to realize that setting the ACF properties doesn't by itself do any filtering. It just sets up the filtering criteria. To do the actual filtering, you must call acf.CoacoGet. This builds and returns a collection containing those ACOs that meet all the filtering criteria you specified. The more criteria you specify, the fewer ACOs you end up with. Again, the ACF itself contains no ACOs and cannot be enumerated. The collection returned by CoacoGet is where the filtered ACOs are stored, and that collection is what you enumerate to access the filtered ACOs.

The point of this division of labor between ACF and collection is that it lets you set up your criteria once (during initialization of your script, for instance) and then call CoacoGet repeatedly as needed to get updated collections as ACOs come and go in the game world. You don't have to respecify the criteria each time.

Each ACF you create with skapi.AcfNew has its own set of filtering criteria. You can create as many ACFs as you like, program them to filter different subsets, and store them in global variables for future use. As your script executes, call CoacoGet as needed on your prefabbed ACFs to get the different filtered collections.

See ACF examples for some typical combinations of filtering criteria.

ACFs have the following properties:

Method or property

Result type

Description

acf.szName

String

Specifies filtering by name (aco.szName). ACOs whose names match the specified string are included (provided they meet the other filtering criteria); those that don't match are excluded. Simple wildcarding using ? and * is supported, e.g. "*Banderling*" to match Banderlings of all ranks.

acf.eqm

Long

Specifies filtering by equipment type (aco.eqm). ACOs matching the specified types are included; those that don't match are excluded. Multiple types can be specified using bitwise OR, e.g. eqmNecklace | eqmBracelets | eqmRings to include jewelry of any kind, and to exclude all non-jewelry.

acf.mcm

Long

Specifies filtering by merchant category (aco.mcm). ACOs matching the specified categories are included; those that don't match are excluded. Multiple categories can be specified using bitwise OR, e.g. mcmFletchingItems1 | mcmFletchingItems2 to include fletching items of either kind, and to exclude all non-fletching items.

acf.ocm

Long

Specifies filtering by object category (aco.ocm). ACOs matching the specified categories are included; those that don't match are excluded. Multiple categories can be specified using bitwise OR.

acf.olc

Long

Specifies filtering by object location category (aco.olc). ACOs matching the specified categories are included; those that don't match are excluded. Multiple categories can be specified using bitwise OR.

acf.oty

Long

Specifies filtering by object type (aco.oty). ACOs matching the specified types are included; those that don't match are excluded. Multiple types can be specified using bitwise OR.

acf.material

Long

Specifies filtering by material type (aco.material). ACOs made of the specified material are included; those made of something else are excluded.

acf.workmanshipMin

Single

Specifies filtering by aco.workmanship. ACOs of equal or greater workmanship are included; those of lesser workmanship are excluded. Used in combination with acf.workmanshipMax, this lets you specify a range of acceptable workmanship values.

acf.workmanshipMax

Single

Specifies filtering by aco.workmanship. ACOs of equal or lesser workmanship are included; those of greater workmanship are excluded. Used in combination with acf.workmanshipMin, this lets you specify a range of acceptable workmanship values.

acf.burdenMin

Long

Specifies filtering by aco.burden. ACOs of equal or greater burden are included; those of lesser burden are excluded. Used in combination with acf.burdenMax, this lets you specify a range of acceptable burden values.

acf.burdenMax

Long

Specifies filtering by aco.burden. ACOs of equal or lesser burden are included; those of greater burden are excluded. Used in combination with acf.burdenMin, this lets you specify a range of acceptable burden values.

acf.acoWearer

ACO

Specifies filtering by aco.acoWearer. ACOs being worn or wielded by the specified player are included; those not being worn or wielded by that player are excluded. So for instance to include only items you yourself are wearing, set acf.acoWearer to skapi.acoChar. To include only items wielded by some specific other player, set acf.acoWearer to the ACO of that player. Note that can't retrieve another player's clothing or armor this way (just as you can't select them in game), only their wielded weapon and shield.

acf.cpyMin

Long

Specifies filtering by aco.cpyValue. ACOs of equal or greater value are included; those of lesser value are excluded. Used in combination with acf.cpyMax, this lets you specify a range of acceptable item values.

acf.cpyMax

Long

Specifies filtering by aco.cpyValue. ACOs of equal or lesser value are included; those of greater value are excluded. Used in combination with acf.cpyMin, this lets you specify a range of acceptable item values.

acf.citemStackMin

Long

Specifies filtering by aco.citemStack. ACOs with equal or greater stack sizes are included; those with lesser stack sizes are excluded. Used in combination with acf.citemStackMax, this lets you specify a range of acceptable stack sizes.

acf.citemStackMax

Long

Specifies filtering by aco.citemStack. ACOs with equal or lesser stack sizes are included; those with greater stack sizes are excluded. Used in combination with acf.citemStackMin, this lets you specify a range of acceptable stack sizes.

acf.cuseLeftMin

Long

Specifies filtering by aco.cuseLeft. ACOs with equal or greater use counts are included; those with lesser use counts are excluded. Used in combination with acf.cuseLeftMax, this lets you specify a range of acceptable use counts.

acf.cuseLeftMax

Long

Specifies filtering by aco.cuseLeft. ACOs with equal or lesser use counts are included; those with greater use counts are excluded. Used in combination with acf.cuseLeftMin, this lets you specify a range of acceptable use counts.

Note that for salvage bags, aco.cuseLeft indicates the number of items salvaged.

acf.distMin

Single

Specifies filtering by distance. ACOs at least distMin map units away are included; those nearer than that are excluded. Used in combination with acf.distMax, this lets you specify a range of acceptable distances.

There are 240 meters per map unit, so to specify a range in meters, set distMin to distInMeters/240. To specify a range in yards, set distMin to distInYards/262.5.

acf.distMax

Single

Specifies filtering by distance. ACOs no farther than distMax map units away are included; those farther away are excluded. Used in combination with acf.distMin, this lets you specify a range of acceptable distances.

There are 240 meters per map unit, so to specify a range in meters, set distMax to distInMeters/240. To specify a range in yards, set distMax to distInYards/262.5.

acf.maploc

Maploc

The location from which distance is measured. By default this is your character's current location (skapi.maplocCur), but you can set it to any location to filter based on distance from that location.

acf.oidMonarch

Long

Specifies filtering by allegiance (aco.oidMonarch). Players with the specified monarch are included; all others are excluded. To include only unpledged players, i.e. those with no monarch at all, set acf.oidMonarch to oidNil.

Note that this is an OID and not an ACO (since the monarch in question may not be logged in at the time). To get an OID from an ACO, use aco.oid.

acf.Reset( )

None

Undoes all filtering criteria and resets the filter to its default state, so that all ACOs are included.

acf.CoacoGet(coaco)

COACO

Applies the specified filtering criteria and returns a collection of matching ACOs. This includes only those ACOs that match all the specified criteria, so the more criteria you specify, the fewer ACOs you'll get. coaco is an optional collection to filter. If specified, CoacoGet returns a filtered subset of the given collection. If omitted, CoacoGet returns a filtered subset of all ACOs in existence.

See COACO properties for information on how to access the individual ACOs or further refine the collection by adding or removing items.

acf.AcoGet(coaco)

ACO

Applies the specified filtering criteria and returns an arbitrary single ACO from the set of matching ACOs. If no ACO matches all the specified criteria, acf.AcoGet returns null or Nothing. coaco is an optional collection to filter, with the same meaning as in acf.CoacoGet.

acf.CoacoGetSorted(sortby, coaco)

COACO

Like acf.CoacoGet, except that the resulting collection is sorted in the order specified by sortby. So for instance acf.CoacoGetSorted(skapi.sortbyName) returns a collection sorted alphabetically by name. See coaco.Sort for details on how to specify sort order.

acf.AcoGetSorted(sortby, coaco)

ACO

Like acf.AcoGet, except that the ACO returned (if any) is the one that comes first in the sort order specified by sortby. So for instance acf.AcoGetSorted(skapi.sortbyDist) returns the single nearest ACO. See coaco.Sort for details on how to specify sort order.

When all you need is the single nearest, or most valuable, or most whatever ACO, using acf.AcoGetSorted is more efficient than retrieving and sorting an entire collection in order to pick out just one item.

acf.FMatchAco()

Boolean

Returns true if the given aco matches the filter criteria, false if it does not match.

ACF examples

Here are some examples of the use of ACF criteria to solve typical filtering problems. Note that in each case (unless otherwise noted) you must call acf.CoacoGet( ) to actually apply the filter and produce an ACO collection.

To get a collection of all monsters within radar range:

acf.ocm = ocmMonster;
acf.distMax = 0.3; // Outdoor radar radius is 0.3 map units.

To get a collection of all player killers within radar range:

acf.ocm = ocmPK;
acf.distMax = 0.3;

To find nearby mage shops:

acf.szName = "*Mage*";
acf.ocm = ocmMerchant;
acf.distMax = 1.0;

To find valuable jewelry on the ground:

acf.eqm = eqmNecklace | eqmBracelets | eqmRings;
acf.olc = olcOnGround;
acf.cpyMin = 1000; // Don't bother with stuff worth less than 1000 py.

To find members of your own monarchy:

acf.ocm = ocmPlayer;
acf.oidMonarch = skapi.acoChar.oidMonarch;

To find unpledged players:

acf.ocm = ocmPlayer;
acf.oidMonarch = oidNil;

To get all the armor you're wearing (note that you can also do this using skapi.CoacoFromEqm):

acf.mcm = mcmArmor;
acf.acoWearer = skapi.acoChar;

To get the weapon(s) wielded by some specific player:

acf.eqm = eqmWeapon | eqmRangedWeapon | eqmFocusWeapon;
acf.acoWearer = acoPlayer;

To get a collection of all the weapons and shields being wielded by everybody in your vicinity (including your own weapon, shield, and armor):

acf.olc = olcEquipped;

To find all the trade notes in your inventory:

acf.szName = "Trade Note";
acf.olc = olcInventory;

To find trade notes just in your main pack:

acf.szName = "Trade Note";
acf.olc = olcInventory;
coaco = acf.CoacoGet(skapi.acoChar.coacoContents); // Filter just my main pack.

To get a collection of all headgear available from a particular merchant:

acf.eqm = eqmHead;
coaco = acf.CoacoGet(coacoMerchant); // Filter the merchant item collection.

where coacoMerchant is the object collection provided by OnApproachVendor.

Obviously many more combinations are possible, but these few examples should illustrate the basic ideas.

Maploc properties

A maploc is an object representing a geographical location in the game world, i.e. a set of coordinates on the map of the world. maploc.lat and maploc.lng—latitude and longitude—are the most interesting properties and the ones you should use for your distance calculations. Don't be confused by maploc.x and maploc.y, which are valid only within a given landblock. If you try to do arithmetic on x and y without taking landblock into account, you'll probably get the wrong answer.

Maplocs have the following properties:

Method or property

Result type

Description

maploc.lat

Single

Latitude in standard map units. Positive values represent north latitudes, negative values south.

maploc.lng

Single

Longitude in standard map units. Positive values represent east longitudes, negative values west.

maploc.landblock

Long

A 32-bit landblock identifier. Landblocks are like big invisible checkerboard squares on the landscape, 192 meters on a side, each with a unique landblock ID. This is the same value found in ac.Location[0].

maploc.x

Single

X offset within the landblock, measured in meters. Values range from 0 to 192, and start over again at zero when you cross a landblock boundary. This is the same as ac.Location[1].

maploc.y

Single

Y offset within the landblock, measured in meters. Values range from 0 to 192, and start over again at zero when you cross a landblock boundary. This is the same as ac.Location[2].

maploc.z

Single

Altitude in meters. This is the same as ac.Location[3].

maploc.head

Single

Compass heading in degrees. Values range from 0 to 360, measured clockwise from due north. This is the same as ac.Location[4].

maploc.headRad

Single

Compass heading in radians (for those of you who prefer it that way). Values range from 0 to 2π, measured clockwise from due north.

maploc.fIndoors

Boolean

True if the location is indoors (whether in a dungeon or in a building above ground), false if outdoors.

maploc.fInDungeon

Boolean

True if the location is in a dungeon, false above ground.

maploc.dungid

Long

If the location is in a dungeon, the numerical dungeon ID of that dungeon, or zero if above ground. Each dungeon has a unique dungid, which you can use to tell which dungeon (if any) you're in. See ACMaps.com for a list of known dungeon IDs.

maploc.sz(precision)

String

Returns a string representation of the latitude and longitude in standard format (e.g. "48.90S 62.34E"). precision is optional and defaults to two decimal places.

maploc.rgv

VBArray

A five-element VBArray containing landblock, x, y, z, and head, in the same order as in ac.Location. Provided for compatibility.

maploc.Dist2DToMaploc(maploc2)

Single

Calculates the 2D distance (ignoring altitude) from maploc to maploc2. Distance units are the same as for maploc.lat and maploc.lng, i.e. standard map units. To get distance in meters, multiply by 240.

maploc.Dist3DToMaploc(maploc2)

Single

Calculates the 3D distance (taking altitude into account) from maploc to maploc2. Distance units are the same as for maploc.lat and maploc.lng, i.e. standard map units. To get distance in meters, multiply by 240.

maploc.HeadToMaploc(maploc2)

Single

Calculates the compass heading in degrees from maploc to maploc2. Values range from 0 to 360, measured clockwise from due north. This the direction you'd have to face, standing at maploc, to be aimed at maploc2.

maploc.lbt

LBT

Returns a landblock terrain object giving access to Cell.dat terrain info for the landblock containing this maploc. If the maploc is in a dungeon, maploc.lbt is null (in JScript) or Nothing (in VBScript).

Note that reading in terrain data from disk takes time, so you do not want to be saying maploc.lbt.this and maploc.lbt.that all over the place in your code. For a given maploc, you want to say maploc.lbt just once and store the result in a variable. Then you can say lbt.this and lbt.that as much as you please without re-reading the terrain data each time.

A landblock terrain object has the following properties:

Method or property

Result type

Description

lbt.landblock

Long

The 32-bit landblock ID of this landblock.

lbt.latMin

Single

The latitude of the landblock's southern edge.

lbt.latMax

Single

The latitude of the landblock's northern edge.

lbt.lngMin

Single

The longitude of the landblock's western edge.

lbt.lngMax

Single

The longitude of the landblock's eastern edge.

lbt.rgz(ix, iy)

Single

A 9x9 array of altitude samples defining the terrain of this landblock. ix and iy must be in the range 0-8 inclusive; the grid points thus defined are 24 meters or 0.1 MU apart. Altitudes are measured in meters above sea level.

lbt.rgitex(ix, iy)

Single

A 9x9 array of texture codes defining the appearance of this landblock. ix and iy must be in the range 0-8 inclusive. I don't have a definitive list of what all the various texture codes mean, but basically there's a different code for each distinct terrain type (snow, rock, grass, desert, lake, swamp, etc).

lbt.fImpassable

Boolean

True if this landblock is an impassable water barrier, false if you can cross its borders freely. Note that this says nothing about unclimbable slopes; this has strictly to do with water barriers.

Mbuf properties

A Message Buffer (Mbuf) object contains raw server message data to be decoded by your script. Because of the way data is packed into the message, you must read it out serially, one field at a time, using the methods described below. For details of specific message formats, see the AC protocol documentation. Bear in mind that all messages begin with a 4-byte header containing the message type, so you need to skip over that (using mbuf.Get_DWORD or mbuf.SkipCb(4)) to get to the first message-specific field. In addition, subtypes of message 0xF7B0 have an extra 12-byte sub-header to read or skip over to get to the message-specific content; subtypes of message 0xF7B1 have an extra 8-byte sub-header.

Method or property

Result type

Description

mbuf.cbOfMsg

Long

The total number of bytes in the message.

mbuf.ibInMsg

Long

The current byte offset in the message from which data is being read.

mbuf.ResetRead( )

None

Resets the read position to the beginning of the message.

mbuf.Align_DWORD( )

None

Aligns the read position to the next DWORD boundary.

mbuf.SkipCb(cb)

None

Skips over cb bytes of data.

mbuf.Get_BYTE( )

Byte

Reads and returns one byte of data from the current read position.

mbuf.Get_WORD( )

Integer

Reads and returns one word (16 bits) of data from the current read position.

mbuf.Get_PackedWORD( )

Integer

Reads and returns one PackedWORD (8 or 16 bits) of data from the current read position.

mbuf.Skip_PackedWORD( )

None

Skips over one PackedWORD (8 or 16 bits) of data at the current read position.

mbuf.Get_DWORD( )

Long

Reads and returns one DWORD (32 bits) of data from the current read position.

mbuf.Get_PackedDWORD( )

Long

Reads and returns one PackedDWORD (16 or 32 bits) of data from the current read position.

mbuf.Skip_PackedDWORD( )

None

Skips over one PackedDWORD (16 or 32 bits) of data at the current read position.

mbuf.Get_QWORD( )

Double

Reads and returns one QWORD (64 bits) of data from the current read position.

mbuf.Get_float( )

Single

Reads and returns a single-precision float (32 bits) from the current read position.

mbuf.Get_double( )

Double

Reads and returns a double-precision float (64 bits) from the current read position.

mbuf.Get_String( )

String

Reads and returns a variable-length text string from the current read position.

mbuf.Skip_String( )

None

Skips over a variable-length text string at the current read position.

mbuf.Get_WString( )

String

Reads and returns a variable-length Unicode string from the current read position.

mbuf.Skip_WString( )

None

Skips over a variable-length Unicode string at the current read position.

OAI properties

An OnAssess Info (OAI) object packages up the information returned by an OnAssessCreature or OnAssessItem event and attaches it to the ACO for later use. The exact properties of an OAI depend on whether it's a creature or an item being assessed, as well as on the success or failure of the assessment. When in doubt, test each sub-object (cbi, chi, ibi, etc.) to see if it's null or Nothing before trying to access its fields.

These properties are common to all OAIs:

Method or property

Result type

Description

oai.cmsecSinceAssess

Long

The elapsed time in milliseconds since this assessment information was received. This gives you an idea of how current the info is and whether you might need to reassess.

oai.fSuccess

Boolean

A rough indication of whether the assessment succeeded or failed (true for success, false for failure); however even if it failed you might still get some useful information.

For ACOs representing creatures (be they players, monsters, or NPCs), the OAI may have some or all of the following additional properties. Not all fields are present for all creatures; how much info you get depends on how good you are at assessing and how resistant the creature is to being assessed.

Method or property

Result type

Description

oai.cbi

Object

A Creature Basic Information object with the following properties:

cbi.species

Long

The numerical species code of the creature's species.

cbi.lvl

Long

The creature's level.

cbi.healthCur

Long

The creature's current health.

cbi.healthMax

Long

The creature's maximum health.

cbi.raw

Dictionary

An associative array of raw item properties. This array is indexed by compound property keys (see ibi.raw for details) to yield the actual property values. Use this to access newly introduced properties not yet supported natively by SkunkWorks.

oai.cai

Object

A Creature Attribute Information object with the following properties:

cai.strength

Long

The creature's Strength attribute.

cai.endurance

Long

The creature's Endurance attribute.

cai.quickness

Long

The creature's Quickness attribute.

cai.coordination

Long

The creature's Coordination attribute.

cai.focus

Long

The creature's Focus attribute.

cai.self

Long

The creature's Self attribute.

cai.staminaCur

Long

The creature's current stamina.

cai.staminaMax

Long

The creature's maximum stamina.

cai.manaCur

Long

The creature's current mana.

cai.manaMax

Long

The creature's maximum mana.

oai.chi

Object

A Creature Human Information object with the following properties:

chi.rank

Long

The creature's allegiance rank.

chi.cfollower

Long

If the creature is a monarch, the number of followers in the monarchy.

chi.fPK

Boolean

True for player killers, false otherwise.

chi.szGender

String

The creature's gender ("Male" or "Female").

chi.szRace

String

The name of the creature's heritage group (e.g. "Sho").

chi.szProfession

String

The creature's profession ("Adventurer", "Sharpshooter", "Mage", "Tailor", etc).

chi.szFellowship

String

The name of the creature's fellowship, if any.

chi.szMonarch

String

The name of the creature's monarch.

chi.szPatron

String

The name of the creature's patron.

chi.loyalty

Long

Obsolete.

chi.leadership

Long

Obsolete.

For ACOs representing items (anything that's not a creature), the OAI may have some of the following additional properties. Not all fields are present for all items; how much info you get depends on how good you are at assessing and how resistant the item is to being assessed.

Method or property

Result type

Description

oai.ibi

Object

An Item Basic Information object with the following properties:

ibi.oty

Long

The object type of the item.

ibi.cpyValue

Long

The value of the item in pyreals.

ibi.burden

Long

The weight of the item in burden units.

ibi.material

Long

The material code of the stuff the item is made of.

ibi.workmanship

Single

The quality of workmanship of the item, on a scale of 0-10, with 10 being best. For salvaged materials, this is the average quality of all the material that went into this salvaged lump.

ibi.citemSalvaged

Long

The number of items salvaged to make this lump of material. For non-salvaged items, this will be 1.

ibi.szDescSimple

String

A simple description of the item, if you failed to assess it.

ibi.szDescDetailed

String

The item's detailed description, if you succeeded in assessing it.

ibi.szComment

String

A comment string for this item (e.g. "Use this item to open it.").

ibi.szInscription

String

The item's inscription, if any.

ibi.szInscriber

String

The author of the inscription.

ibi.cpageTotal

Long

For books and papers, the total number of pages.

ibi.cpageUsed

Long

For books and papers, the number of pages actually written on.

ibi.fOpen

Boolean

For doors and chests, true if the item is open, false if closed.

ibi.fLocked

Boolean

For doors and chests, true if the item is locked, false if unlocked.

ibi.diffLock

Boolean

For lockable doors and chests, the lockpick difficulty of the lock.

ibi.manaCur

Long

For mana stones, the amount of mana stored in the stone.

ibi.fractEfficiency

Double

For mana stones, the stone's mana transfer efficiency (between zero and one).

ibi.probDestroy

Double

For mana stones, the probability that the stone will be destroyed on use.

ibi.skidWieldReq

Long

For high-level weapons and armor, the skill ID of the skill required to wield this item, or skidNil if there is no skill requirement.

ibi.lvlWieldReq

Long

If skidWieldReq is not skidNil, the skill level required to wield this item. If skidWieldReq is skidNil, the character level required to wield this item.

ibi.fractManaConvMod

Double

The Mana Conversion modifier of this item. For instance a Mana Conversion bonus of +8% shows up as 0.08 here. An item with no Mana Conversion bonus has a fractManaConvMod equal to zero.

ibi.vitalRestored

Long

For foods and potions, the vital stat (health, stamina, or mana) restored by consuming this item.

ibi.dlvlRestored

Long

For foods and potions, the number of points of vital stat restored by consuming this item.

For healing kits, the number of skill points added to your healing skill when using this item.

ibi.fractRestorationBonus

Double

For healing kits, the bonus multiplier used to calculate the number of health points restored.

ibi.manaCost

Long

For enchanted gems, the mana cost of using the gem.

ibi.ctink

Long

The number of times this item has been tinkered.

ibi.szTinkerer

String

The name of the player who last tinkered this item.

ibi.szImbuer

String

The name of the player who imbued this item.

ibi.szCreator

String

The name of the player who applied Ivory to this no-drop item to make it droppable, and is therefore the only player allowed to wield it. (There may be other uses of this property, but if so I don't know what they might be.)

ibi.ckeyOnRing

Long

The number of keys on a keyring.

ibi.fAttuned

Boolean

True if the item is Attuned to your character (no-give).

ibi.fBonded

Boolean

True if the item is Bonded to your character (doesn't drop on death).

ibi.fRetained

Boolean

True if the item is flagged as Retained (can't be sold or salvaged).

ibi.fDropOnDeath

Boolean

True if the item always drops on death.

ibi.fDestroyOnDeath

Boolean

True if the item is destroyed on death.

ibi.fUnenchantable

Boolean

True if the item cannot be buffed (such as Covenant armor).

ibi.fSellable

Boolean

True if the item can be sold at NPC merchant shops.

ibi.fIvoryable

Boolean

True if Ivory can be applied to the item to make it hookable.

ibi.fDyeable

Boolean

True if the item can be dyed.

ibi.fUnlimitedUses

Boolean

True if the item can be used an unlimited number of times.

ibi.rareid

Long

The Rare ID of a Rare item.

ibi.raw

Dictionary

An associative array of raw item properties. This array is indexed by compound property keys (described below) to yield the actual property values. Use this to access newly introduced properties not yet supported natively by SkunkWorks.

The ID message sent from server to client in response to an ID request contains up to five key/value arrays, selected by different bitmasks. There is one DWORD array (mask 0x0001), one Boolean array (mask 0x0002), one double-precision floating point array (mask 0x0004), one string array (mask 0x0008), and one QWORD array (mask 0x2000). The compound property key used for indexing ibi.raw is a DWORD containing the mask in the high-order word and the specific property key in the low-order word. So for instance to access the Imbues property (key 0xB3 of the DWORD array), the compound key would be 0x000100B3. Similarly, the compound key for Last Tinkerer property (key 0x27 of the strings array) would be 0x00080027. (Of course, not all properties are present in every ID message.)

For more details on the encoding of item properties in the ID message, see the AC protocol documentation.

oai.iai

Object

An Item Armor Information object with the following properties:

iai.al

Long

The item's buffed armor level, expressed as an integer (e.g. 180).

iai.protSlashing

Single

The item's Slashing protection, expressed as a floating-point number, where 1.00 is average.

iai.protPiercing

Single

The item's Piercing protection.

iai.protBludgeoning

Single

The item's Bludgeoning protection.

iai.protFire

Single

The item's Fire protection.

iai.protAcid

Single

The item's Acid protection.

iai.protCold

Single

The item's Cold protection.

iai.protElectrical

Single

The item's Electrical protection.

oai.iwi

Object

An Item Weapon Information object with the following properties:

iwi.dmty

Long

The damage type of this weapon. More than one dmty bit may be set if the weapon inflicts more than one type of damage.

iwi.speed

Long

The speed of this weapon (smaller numbers are faster).

iwi.skid

Long

The skill ID of the skill needed to wield this weapon (Axe, Dagger, whatever).

iwi.dhealth

Long

The base damage (in health points) this weapon can inflict.

iwi.scaleDamageRange

Double

A fraction between zero and one indicating the lower end of the damage range. For instance, a damage value of 12 combined with a scaleDamageRange of 0.75 indicates a normal damage range of 9-12.

iwi.scaleDamageBonus

Double

A scale factor indicating any damage bonus. A value of 1.00 means normal damage (no bonus).

iwi.scaleDefenseBonus

Double

A scale factor indicating any Melee Defense bonus granted by this weapon. A value of 1.00 means normal defense skill (no bonus).

iwi.scaleMissileDBonus

Double

A scale factor indicating any Missile Defense bonus granted by this weapon. A value of 1.00 means normal defense skill (no bonus).

iwi.scaleMagicDBonus

Double

A scale factor indicating any Magic Defense bonus granted by this weapon. A value of 1.00 means normal defense skill (no bonus).

iwi.scaleAttackBonus

Double

A scale factor indicating any attack bonus granted by this weapon. A value of 1.00 means normal attack skill (no bonus).

iwi.scalePvMElemBonus

Double

A scale factor indicating the elemental damage bonus (if any) of a high-wield wand in PvM combat. A value of 1.00 means normal damage (no bonus). iwi.dmty indicates the elemental damage type.

iwi.scalePvPElemBonus

Double

A scale factor indicating the elemental damage bonus (if any) of a high-wield wand in PvP combat. A value of 1.00 means normal damage (no bonus). iwi.dmty indicates the elemental damage type.

iwi.dhealthElemBonus

Long

The elemental damage bonus (if any) of a high-wield bow. iwi.dmty indicates the elemental damage type.

iwi.dwHighlights

Long

Poorly understood information about which words are highlighted in red or green on the assess panel.

iwi.distRange

Double

A missile weapon's approximate range in meters. Note that this may not agree with the rounded-off range figure shown in the in-game Assess panel. To get range in MU, divide by 240.

iwi.vLaunch

Double

A missile weapon's launch speed in meters per second.

iwi.fCriticalStrike

Boolean

True if this weapon is imbued with Crtical Strike, False otherwise.

iwi.fCripplingBlow

Boolean

True if this weapon is imbued with Crippling Blow, False otherwise.

iwi.fArmorRending

Boolean

True if this weapon is imbued with Armor Rending, False otherwise.

iwi.fSlashRending

Boolean

True if this weapon is imbued with Slash Rending, False otherwise.

iwi.fPierceRending

Boolean

True if this weapon is imbued with Pierce Rending, False otherwise.

iwi.fBludgeonRending

Boolean

True if this weapon is imbued with Bludgeon Rending, False otherwise.

iwi.fAcidRending

Boolean

True if this weapon is imbued with Acid Rending, False otherwise.

iwi.fColdRending

Boolean

True if this weapon is imbued with Cold Rending, False otherwise.

iwi.fFireRending

Boolean

True if this weapon is imbued with Fire Rending, False otherwise.

iwi.fLightningRending

Boolean

True if this weapon is imbued with Lightning Rending, False otherwise.

iwi.fPhantasmal

Boolean

True if this weapon is a Phantom weapon, False otherwise.

oai.iei

Object

An Item Enchantment Information object with the following properties:

iei.difficulty

Long

The difficulty of the enchantment (i.e. the level of Arcane Lore skill you need to use it).

iei.spellcraft

Long

The Spellcraft level of the spell.

iei.manaCur

Long

The amount of mana charge currently stored in the item.

iei.manaMax

Long

The maximum mana charge the item can hold.

iei.csecPerMana

Single

The rate at which the item consumes mana, expressed as the negative of the number of seconds per mana point. So if the rate is one point every 30 seconds, the number you find here is −30 (that's minus 30).

iei.fractEfficiency

Double

The mana transfer efficiency of this item, expressed as a number between zero and one.

iei.szRaceReq

String

If there's a race requirement (e.g. "You must be Sho to use this item's magic"), this property holds the name of the required race (e.g. "Sho").

iei.skidReq

Long

If there's a secondary skill requirement ("Your Dagger skill must be at least 147"), this property holds the skill ID of the required skill (e.g. skidDagger), or skidNil (zero) for no requirement.

iei.sklvlReq

Long

If there's a secondary skill requirement, this property holds the skill level required (e.g. 147).

iei.rankReq

Long

The allegiance rank required to use the item's magic, or zero for no rank requirement.

iei.cspellid

Long

The number of spells on this item. This includes spells permanently on the item, spells cast by the item on its wearer, and spells temporarily cast on the item.

iei.rgspellid(i)

Long

The numerical spell ID of the i th spell on this item. This includes spells permanently on the item, spells cast by the item on its wearer, and spells temporarily cast on the item.

iei.rgflagsSpell(i)

Long

Spell flags associated with the i th spell on this item. The exact interpretation of these flags is not fully known, but they seem to indicate which spells are currently active.

oai.ipi

Object

An Item Portal Information object with the following properties:

ipi.lvlMin

Long

The lower level restriction on this portal, or −1 if there is no restriction.

ipi.lvlMax

Long

The upper level restriction on this portal, or −1 if there is no restriction.

ipi.szDest

String

A string describing the portal destination. This often (but not always) includes map coordinates.

ipi.flags

Long

A bitmask of portal flags, not fully understood, but thought to contain such information as whether or not the portal can be tied or summoned.

PXT properties

Pixel Transition (PXT) objects are created by skapi.PxtNew and allow you to monitor the state of individual pixels on screen and trigger actions when those pixels change. Their properties are:

Method or property

Result type

Description

pxt.xp

Long

The X-coordinate of the pixel to be checked; may be positive or negative as in skapi.clrPixel.

pxt.yp

Long

The Y-coordinate of the pixel to be checked; may be positive or negative as in skapi.clrPixel.

pxt.clrBefore

Long

The color of the pixel before the transition, in Windows RGB format.

pxt.clrAfter

Long

The expected pixel color after the transition, in Windows RGB format.

pxt.clrCur

Long

The current pixel color at pxt.xp,pxt.yp, in Windows RGB format.

pxt.clrCheck

Long

The pixel color as of the last call to pxt.FCheck or pxt.FWait, in Windows RGB format.

pxt.pxtReverse

PXT

Returns a new PXT object with clrBefore and clrAfter swapped. This new PXT thus watches for the reverse transition from the original PXT.

pxt.FCheck( )

Boolean

True if clrCur is more like clrAfter, false if it's more like clrBefore. Exact color match is not required, so long as the pixel color is closer to clrAfter than it is to clrBefore.

pxt.FWait(csecTimeout, fFireEvents)

Boolean

Waits for the pixel at xp,yp to change from clrBefore to clrAfter. Again, exact color match is not required; the wait is satisfied as soon as pxt.FCheck becomes true, i.e. when clrCur becomes more like clrAfter than like clrBefore. If csecTimeout seconds elapse before that happens, pxt.FWait returns False; otherwise it returns True. (Note that the timeout is measured in seconds, not milliseconds; however, you can pass in fractional or floating-point timeout values such as 1/2 or 1.5.)

fFireEvents is optional and defaults to false. If true, pxt.FWait passes the time by calling skapi.WaitEvent, allowing event handlers to fire. If false, FWait calls skapi.Sleep instead, preventing event handlers from firing.

pxt.FCheckXyp(xp, yp)

Boolean

Like pxt.FCheck, but uses the passed-in xp,yp coordinates in place of pxt.xp,pxt.yp. This is useful for checking pixel transitions whose color properties are fixed, but whose location may vary on screen.

pxt.FWaitXyp(xp, yp, csecTimeout, fFireEvents)

Boolean

Like pxt.FWait, but uses the passed-in xp,yp coordinates in place of pxt.xp,pxt.yp.

The point of all this "more like A than B" color matching is portability. Different video cards can produce slightly different colors even when rendering the same scene. So if you want your script to run properly on someone else's machine, it becomes necessary to look for pixels that are more like this color than that color, instead of checking for strict color equality.

You can create PXT objects on the fly, as you need them, e.g.:

skapi.PxtNew(xp, yp, clrBefore, clrAfter).FWait(3);

It's more efficient, however, to create all the PXTs you're going to need at the top of your script, assign them to globals, and then reuse them repeatedly as the script executes. The reason for this is that some setup computation is done by skapi.PxtNew in order to make the checking done by pxt.FCheck as fast as possible. This investment pays off only if you reuse the PXT.

Skinfo properties

Skinfo (skill info) objects are returned by skapi.SkinfoFromAttr, skapi.SkinfoFromVital, and skapi.SkinfoFromSkid, and provide access to detailed information about your skills and attributes. They have the following properties:

Method or property

Result type

Description

skinfo.szName

String

The name of this skill or attribute.

skinfo.szDesc

String

A text string describing the skill or attibute's function and benefits.

skinfo.dlvl

Long

The number of levels gained in this skill or attribute since character creation.

skinfo.lvlDefault

Long

Your default level in this skill or attribute. This is the raw level given by the standard skill formula for this skill (e.g. (Focus + Self)/4 or whatever), and is the level you would have if you had never practiced or improved this skill.

skinfo.lvlUnbuffed

Long

Your base or unbuffed level in this skill or attribute. Your actual level at any moment may be different due to buffs, debuffs, or vitae penalties.

skinfo.lvlCur

Long

Your actual current level in this skill or attribute, including the effects of any buffs, debuffs, or vitae penalties currently in effect. This may change from moment to moment as spells are applied or wear off.

skinfo.expInvested

Double

The total amount of experience you have invested in this skill or attribute.

skinfo.skts

Long

The skill training status of this skill, or sktsUntrained if it's an attribute and not a skill.

skinfo.DexpToLvl(lvlTarget)

Double

Returns the amount of additional skill experience needed to reach the given skill level from where you are now. lvlTarget is optional; if omitted, it defaults to your current skill level + 1. In that case, DexpToLvl tells you how much more XP you need to spend to advance one skill level. If you've already reached or passed the specified target level, DexpToLvl returns zero. If the skill in question is untrained or unusable, DexpToLvl returns -1. (Contrast this with skinfo.ExpFromLvl below.)

skinfo.ExpFromLvl(lvl)

Double

Returns the total amount of experience corresponding to the specified skill level for this skill or attribute, i.e. the amount of XP you'd have invested in the skill at that level, assuming no change to your basic attributes. (Contrast this with skinfo.DexpToLvl above.) This may be different for different skills, since different skills depend on different combinations of basic attributes. For untrained or unusable skills, ExpFromLvl returns -1.

skinfo.LvlFromExp(exp)

Long

Returns the skill level corresponding to the given amount of total experience, i.e. the level you'd have reached after investing that much XP in this skill or attribute, assuming no change to your basic attributes. This may be different for different skills, since different skills depend on different combinations of basic attributes. For untrained or unusable skills, LvlFromExp returns -1.

skinfo.attr1

Attribute

The first underlying attribute of this skill or vital. So for instance if the skill formula is (Coord + Focus)/3, skinfo.attr1 would be attrCoordination.

skinfo.attr2

Attribute

The second underlying attribute of this skill or vital. So for instance if the skill formula is (Coord + Focus)/3, skinfo.attr2 would be attrFocus. If it's a single-attribute skill, skinfo.attr2 is attrNil (zero).

skinfo.divisor

Long

The skill formula divisor of this skill or vital. So for instance if the skill formula is (Coord + Focus)/3, skinfo.divisor would be 3.

Spell properties

Spell objects are created when someone or something casts a spell on you. You can get a spell object for an active spell from the OnSpellAdd event or from skapi.cospell.

You can also get general information about spells that have not been cast using skapi.SpellInfoFromSpellid or skapi.CospellFromFamily.

Method or property

Result type

Description

spell.spellid

Long

The numerical spell ID of this spell.

spell.szName

String

The name of this spell.

spell.szDesc

String

A text string describing the spell's effects.

spell.skid

Long

The skill ID of the skill needed to cast this spell.

spell.family

Long

The spell family of this spell. Spells in the same family override or surpass each other when applied jointly, with only the most potent spell of each family taking effect. For instance, Strength Self II overrides Strength Self I because they're in the same family. Spells of different families, in contrast, do not interfere with each other, even if they affect the same stat, and so can produce cumulative effects. So for instance Weakness Other I can partially counteract Strength Self II because they're in different families and therefore both effects apply.

spell.diff

Long

The difficulty of this spell.

spell.mana

Long

The mana cost of this spell.

spell.range

Single

The range of this spell in map units (not yards), i.e. the maximum distance from which this spell can be cast on a target. To get the range in meters, multiply this value by 240; for range in yards, multiply by 262.5. For some spells, range is constant; for others it depends on your skill level in the skill denoted by spell.skid. For Self spells, range is zero.

spell.csecDuration

Long

The total duration of this spell in seconds. This is a constant for any given spell. For spells with no fixed duration (such as those cast by enchanted jewelry or armor), csecDuration is -1.

In addition, the following properties are defined for active spell objects obtained from OnSpellAdd or skapi.cospell (but not for those obtained from skapi.SpellInfoFromSpellid or skapi.CospellFromFamily).

Method or property

Result type

Description

spell.layer

Long

The layering count of this spell. If the identical spell is cast on you more than once, the different instances will have different layer numbers.

spell.acoCaster

ACO

The creature or object that cast the spell on you. Use its ACO properties to find out details such as its name, location, etc.

spell.csecElapsed

Long

The approximate time, in seconds, that this spell has been in effect. This is a dynamically changing quantity that increases with time.

spell.csecRemain

Long

The approximate time, in seconds, remaining until this spell expires. This is a dynamically changing quantity that decreases with time. For spells with no fixed duration (such as those cast by enchanted jewelry or armor), csecRemain is -1.

spell.flags

Long

A bitmask describing the effect of the spell. I don't fully understand the exact meaning of this information, but a few specific values are fairly well understood:

  • 0x9001 indicates that spell.avsAffected is an attribute ID.
  • 0x9002 indicates that spell.avsAffected is a vital stat ID.
  • 0x9010 indicates that spell.avsAffected is a skill ID.

spell.avsAffected

Long

The attribute, vital stat, or skill affected by this spell, as determined by spell.flags. May be zero for spells that don't directly affect specific single stats.

spell.dlvl

Single

The quantitative effect of this spell on the stat specified by spell.avsAffected. Usually this is an additive factor (e.g. +20 or -15 skill points), but in a few cases it's a multiplier (e.g. 0.95 for 5% vitae).

I've exposed spell.flags, spell.avsAffected, and spell.dlvl in the interests of full disclosure, even though they're not fully understood in all cases (at least not by me). You probably won't need to concern yourself with the specifics of these three properties, since their effects are already accounted for in skinfo.lvlCur.

Timer properties

Timer objects are created by skapi.TimerNew and provide a simple and accurate way of measuring elapsed time in your script. They can also be used to fire events at preset intervals.

Timers have the following properties:

Method or property

Result type

Description

timer.cmsec

Long

The number of milliseconds elapsed since the timer was created or reset. This is a read/write property, so you can reset the timer by setting timer.cmsec to zero.

If you set cmsec to a number less than zero, an OnTimer event will fire when the count reaches zero, i.e. when −cmsec milliseconds have elapsed. So for instance to fire an event 5 seconds in the future, set timer.cmsec to −5000. (You must of course call skapi.WaitEvent as usual to give the event a chance to fire.) Note that events fired in this way don't automatically repeat; to make them repeat, you must restart the timer in your event handler.

To cancel a timer event before it happens, set timer.cmsec to zero.

timer.tag

Variant

A place for you to store and retrieve an arbitrary value identifying this timer. If you have more than one timer generating OnTimer events, use timer.tag to tell them apart. This property is yours to do with as you like; SkunkWorks itself does not refer to it or interpret it in any way.

WAV properties

WAV objects are create by skapi.WavNew and can be used to play sounds under script control. Their properties are:

Method or property

Result type

Description

wav.szFile

String

The name of the .wav file to be played. This is a read/write property, so you can use it to set the filename or to read it out.

wav.Play( )

Long

Plays the .wav file named by wav.szFile. Returns zero if the function succeeds, non-zero otherwise. Note that this may not work if your soundcard is more than a couple of years old and you try to play a .wav file while the game is running. Many soundcards of that vintage can't do DirectSound and wave output at the same time. Newer soundcards are better in this regard.

Collections

Although questions of general programming technique are beyond the scope of this document, dealing with collections seems to be a FAQ even for experienced coders. Since many of the methods described above return collections, I will briefly explain how they work. Let me emphasize that the examples in this section apply to collections of any sort, not just of ACOs.

The collection object itself has two properties: Count, which is the number of elements in the collection, and Item(i), which returns the i th element of the collection (where item numbering starts at zero). So one way to iterate over the ACOs in a collection of ACOs would be like this, in JScript:

var i, aco;
for (i = 0; i < coaco.Count; i++)
    {
    aco = coaco.Item(i);
    ...
    }

Or in VBScript:

Dim i, aco
For i = 0 To coaco.Count - 1
    Set aco = coaco.Item(i)
    ...
Next

However, both languages offer built-in facilities for doing this somewhat more concisely. In JScript:

var e = new Enumerator(coaco);
var aco;
for (; !e.atEnd(); e.moveNext())
    {
    aco = e.item();
    ...
    }

In VBScript:

Dim aco
For Each aco In coaco
    ...
Next

For further details, see Microsoft's online JScript and VBScript documentation.

VBArrays

VBArrays seem to be another area that tends to trip up JScript coders, so I'll give a brief primer on their use. VBScript users can skip over this section, since VBArrays are native to VBScript and there's nothing tricky about their use in that language.

I've tried to avoid the use of VBArrays in the SkunkWorks API, preferring objects with named properties wherever possible. The ACScript API, however, has lots of VBArrays, and there are a couple of compatibility functions that take them as well.

In order to use a VBArray in JScript, you must first convert it to a native JScript array using the toArray method:

var vba = maploc.rgv;

// A VBArray

var x = vba[1];

// Error

var jsa = vba.toArray();

// A JScript array

var y = jsa[2];

// Success

For details, see Microsoft's online documentation.