Ted's RPG Rant

A place to rant about RPG games, particularly the Temple of Elemental Evil. Co8 members get a free cookie for stopping by. Thats ONE cookie each, no seconds.

Tuesday, January 30, 2007

Scripting for Proto Values

Well I stayed up very late working on KotB last night, but I very quickly got sidetracked into writing this blog. There's nothing in here that I have discovered myself (though I did pen all the KotB scripts I am using as examples) but rather it is a compendium of things I have had to go look for when scripting for this or that, which I now present so you folks won't have to hunt so hard 8^D

So, lets say you want to recognise an object, or an NPC, not just by their number but according to some setting in the protos.tab. Here's some examples of how.

Firstly, let me say, you can't always get what you want. For instance, picking critters by faction: damn that would be useful. But we can't. When u try to read the faction flag, you get a changing number (certainly NOT what is in col 154 of ProtoEd) and if you try to script for it, it crashes. The reason for this (so I am told) has to do with how the .dll interprets that particular flag, and it doesn't just read it as an integer. Indeed, there is nothing in the .dll to output the contents of the flag (obj_f_npc_faction (363) for memory, it was a while back when I tried this) in a way we can understand, or so I was reliably informed when I pestered some gurus like Ag, S-S and C-Blue to help with this, so we can't just read it and say, "ahhh, yonder critter is faction X, lets manually remove him from the fight". I mean we CAN manually remove critters from a fight, but not based on faction.

Never fear! There are many things we CAN read. Some we already have seen, such as the flags: I wrote a whole tutorial on flags somewhere. Here's a quick recap from KotB:

________y = (attachee.critter_flags_get() & OCF_MECHANICAL)
________if attachee.map == 5033: ## office
________________if ((is_daytime() != 1) and (game.global_vars[19] == 1) and (y != 0)):
________________________game.leader.begin_dialog( attachee, 600 )

We read the specific critter flag by logically ANDing it with the whole flag byte: if the OCF_MECHANICAL bit is set, it returns a value of 1 to y, otherwise it returns a 0. This way I can have 2 different mobs for a single NPC in the same room and access them independantly. Under the right circumstances (nighttime, and the trigger variable [19] having been set) then the mob flagged OCF_MECHANICAL (which I presume is a leftover from Arcanum and means nothing in ToEE, but in any case is simply a handy 'nothing' flag that the game still recognises) will get its OF_OFF flag unset (ie it will switch on instead of OFF) and it will kick in. Of course, there is an accompanying thing so that the mob WITHOUT the OCF_MECHANICAL flag will then switch off.

Easy, and you can do it for any of the flags: object flags (OF), item flags (OIF), NPC flags (ONF) etc.

What about some of them other columns in protos.tab? Well, lets go through in no particular order...

NAME: Ok, this is a simple but important one: the item name. Item name is col 22 of ProtoEd: it can therefore differ from the prototype number. An example of this in action is find_npc_near: lets have a look at the script whereby Otis checks to see if Elmo is nearby:

def make_elmo_talk( attachee, triggerer, line):
________npc = find_npc_near(attachee,8000)
________if (npc != OBJ_HANDLE_NULL):
________return SKIP_DEFAULT

We've seen this before so I won't dwell on it: suffice it to say, the game looks for Elmo's NAME (8000) not his prototype number (14013) or even his IDed number (col 23, which in this case is 20008 - the line in Description.mes where the game gets the word "Elmo" so it can call him that after you have met him, instead of "drunk").

So how exactly do you read the name for moments like this? Well we could go thru Utilities.py and deconstruct the "find_npc_near" command that just got called, but I will leave you to do that for yourself (checking out the innards of that file is well worth doing for any modder). For now, I will simply say it is attachee.name == xxxxx. For instance:

________if attachee.name == 14696:

Easy :-) And you can see how important it is that everything have a name, which is why Darmagon went through and added a name (based on protoype # if not already set) to everything in the protos.tab - so spells, scripts doing things by name would always find a handle.

MATERIAL: No, not the material thing for meshes, I mean the material an object is made from. Want your Warp Wood spell to only warp wood? Of course you do ;^) Here is another little thing from KotB that shows how to find a wooden item: in this case, it is going by slot, so you get a 2-for-1, how to find an item by material AND by slot :-)

def unequip( slot, npc):
________for npc in game.party:
________________item = npc.item_worn_at(slot)
________________if item != OBJ_HANDLE_NULL:
________________________if (item.obj_get_int(obj_f_material) != mat_wood):
________________________________holder = game.obj_create(1004, npc.location)

Item slot numbers can be found in ToEEWB: I will post them here though for better tutorial bang-for-your-buck.

0 = helmet
1 = necklace
2 = gloves
3 = primary weapon
4 = secondary weapon
5 = armour
6 = primary ring
7 = secondary ring
8 = boots
9 = ammo
10 = cloak
11 = shield
12 = robe
13 = bracers
14 = bardic item
15 = lockpicks

So to check for an item worn at this or that slot, just check:


where 'npc' and 'slot' get replaced by the appropriate handles. For instance, lets say you want to add a script so Terjon checks if u actually walk up to him wearing his dad's ultra-masculine pendant thingy (prototype 6139) and if so, says something about it without waiting for u to raise the issue. The pendant is flagged


I have never understood those "flag it multiple times" things, but Protos.tab does it a lot. Anyways, lets interpret this to mean "it can be warn in either ring slot or the necklace slot" because I think that is what it is probably trying to say. So we would check all 3 slots:

________item1 = triggerer.item_worn_at(1)
________item2 = triggerer.item_worn_at(6)
________item3 = triggerer.item_worn_at(7)

then test by item name (which for some reason is 3004):

________if (item1.name == 3004) or (item2.name == 3004) or (item3.name == 3004):
________________triggerer.begin_dialog( attachee, 990 )

And then have some appropriate dialogue at 990. (note, there may be a more elegant way of doing it than checking them individually like that, but thats ok :-)

Getting back to the material flag, we are checking the internal object flags again (we have used some of these before also) and in the case of material it is, to repeat it:

item.obj_get_int(obj_f_material) != mat_wood

where 'item' could be 'attachee' or 'triggerer' or something else depending on the circumstance. Mat_wood could of course be mat_metal, or mat_cloth, or mat_flesh, mat_glass, mat_force etc.

Do we use this to check whether we are talking to a living creature? Well we could (sort of - living or dead creature would be more accurate, but then you can't 'talk' to a dead thing, except in very special exceptions like Clarisse ;) but a better way would be by checking object type. This is the very first column in protos.tab.

________for pc in game.party:
________________if pc.type == obj_t_npc:
________________________return 0

This checks to see whether a member of the party is a PC or NPC follower. Again, this could be attachee.type or triggerer.type or whatever. And it could be obj_t_npc, or obj_t_armor like the pendant, or obj_t_scroll or obj_t_generic (all the items in the 12000s) or obj_t_weapon or whatever.

Here is another quick example of obj.type and flags (portal flags this time - they don't show up too often) in action: the Knock spell.

________if target.obj.type == obj_t_portal:

________________if ( target.obj.portal_flags_get() & OPF_LOCKED ):
________________________target.obj.float_mesfile_line( 'mes\\spell.mes', 30004 )
________________________target.obj.portal_flag_unset( OPF_LOCKED )

SIZE: Back to NPCs. Need to fine-tune your specific NPC rather than just by whether it IS one or not? Well, size is good :-) Easy, too:

________if attachee.get_size < STAT_SIZE_LARGE:

Nice how they go sequentially: you don't have to say '== small or == medium or == diminutive' etc, just '< LARGE'. A few things go that way - quest outcomes ('if game.quests[19].state < qs_botched') for instance - but it is not always easy to see the progression, so normally I just do it the long way.

Still worrying about whether it is dead or not?

________if critter_is_unconscious(attachee) != 1:

want him not only conscious but on his feet?

________and not attachee.d20_query(Q_Prone):

ok, I am getting carried away ;-) those are not prototype settings. Lets try to stay focused.

But what if we are still looking for a more specific type of NPC than just one who is smaller than LARGE? Race is easy enough, I have talked about that before:

pc.stat_level_get(stat_race) == race_Halfling
pc.stat_level_get(stat_race) == race_Human
pc.stat_level_get(stat_race) == race_Elf
pc.stat_level_get(stat_race) == race_Dwarf
pc.stat_level_get(stat_race) == race_Gnome etc

but we also have the CATEGORY value in col 163 (not to be confused with the one in col 30, from category.mes):

________obj.is_category_type( mc_type_humanoid )

where 'obj' could again be attachee or whatever handle you grab it by. 'mc_type_humanoid' could be mc_type_animal or mc_type_outsider or mc_type_undead or mc_type_giant or whatever.

SUBTYPE, on the other hand is this:

________obj.is_category_subtype( mc_subtype_fire )

which is not that surprising ;-)

ALIGNMENT you already know:

________pc.stat_level_get(stat_alignment) == LAWFUL_GOOD // individuals

________game.party_alignment == NEUTRAL_EVIL or game.party_alignment == CHAOTIC_EVIL or game.party_alignment == CHAOTIC_NEUTRAL or game.party_alignment == LAWFUL_EVIL // parties

but here is a method for checking alignment as a flag (easier in some circumstances):

________alignment = target_item.obj.critter_get_alignment()
________if (alignment & ALIGNMENT_EVIL) or ( (alignment & ALIGNMENT_NEUTRAL) and not (alignment & ALIGNMENT_GOOD) ):

Note it is checking any evil, any good etc. You can also check the Law - Chaos line:

________alignment = target_item.obj.critter_get_alignment()
________if (alignment & ALIGNMENT_CHAOTIC) or ( (alignment & ALIGNMENT_CHAOTIC) and not (alignment & ALIGNMENT_LAWFUL) ):

You know DEITY:

________pc.stat_level_get( stat_deity ) == 1 // Boccob


________pc.stat_level_get(stat_strength) >= 10


________pc.stat_level_get( stat_gender ) == gender_male (or gender_female)

and CLASS:

________pc.stat_level_get(stat_level_cleric) >= 1

but you won't have come across HIT DICE before (not in my tutorials, anyways!):

________if obj.hit_dice_num <= 10:

What about PORTRAITS? Yep, we can even read and change them (I can't for the life of me think of many moments to do so, but I guess it might be fun to change the portrait of a character to all bloodied and battered if he is yanked out of combat by falling to low HP):

________attachee.obj_set_int( obj_f_critter_portrait, 6500 )

(That one's from 'Flaming Sphere', which I assume was done by Darmagon and is just a masterpiece of scripting). To read a portrait, I would assume it would be:

________x = attachee.obj_get_int( obj_f_critter_portrait)

but that one is untested.

How about SKILLS? Well you should be able to read them already:

________pc.skill_level_get(npc, skill_bluff) >= 7

But what about manipulating them? Here is how Darmagon implemented Analyse Dweomer - a +20 on Spellcraft so you would recognise anything going on:

________spell_obj.item_condition_add_with_args('Skill Circumstance Bonus', skill_spellcraft, 20)

This powerful command can be used to add other bonuses besides circumstance ones, as Darmargon also discovered - but they don't come off in a hurry, so use at your peril!

________game.party[0].condition_add_with_args('Shield Bonus',8,0) // has the effect of raising the armor class of the left-most party member by 8!

As for checking for FEATS:

________if (not target.has_feat( feat_evasion )) and (not target.has_feat( feat_improved_evasion )):

where, for the last time, 'target' could be 'attachee' or 'triggerer' or however else you define it.

Well, that just leaves ROTATION, col 31 :-) We'll end this with a little script that gets quite a workout in KotB: it causes the NPC to turn and face the other way, whatever their initial rotation.

def away(attachee):
________x = attachee.rotation
________x = x+3
________if x >= 6:
________________x = x - 5.99
________attachee.rotation = x

Sunday, January 07, 2007


Wow, its been over a month since I updated this thing! Doesn't seem that long, but then thats probably because I have a couple tutorials simmering away: one being 'Animations part 2' which has a few things to add (but a lot more to test first) and the other being a 'how to' thing on implementing brawling, which I started writing because I thought I had it cracked then stopped when I realised I was kidding myself. Not that brawling is that hard, but I wanted to take it to the next step - duelling - and since the brawl subroutine thingy interprets the use of weapons in a brawl as cheating and starts a free-for-all, it got real ugly. I could keep it one-on-one by booting the other NPCs from the fight every round, but damn that looked like a cheap hack. Finally I side-stepped the issue by just making the dueller a different faction to everyone else, but that introduced a whole other host of problems since he is the leader of a pack of NPCs: leave him a different faction and start a general fight, and they of course go for him instead of the party (sigh). I finally got it going with many workarounds, and it ain't that ugly (works real nice actually) but I just know something is going to come back and bite me in the ass, like if one dueller takes the other out with an act of exertion and they both collapse a la Rocky II. More testing needed.

One things is for sure: when the PC dueller LOSES, its a sight to behold. And damn it is going to cause some feedback. "Hey when I agreed to a fight to the death, I didn't mean THIS!"

Anyways, while folks await my next tutorial, I am gonna take a leaf out of Yvy's "use cut-n-pastes" book a little something I lifted long long ago from SomethingAwful.com. Don't forget to drop by their site and give 'em their dues.

The subject of the following list is, "things that are worse than what you are suffering right now". I am suffering a lack of a blog - here is a bunch of stuff that is worse.

  • Racketeering

  • Unions

  • Disappointed kittens

  • A duck with no friends, alone in a pond of infinite potential

  • A baby panda who skinned his knee and daddy is too busy to kiss it

  • An Eskimo

  • A little kid with polio writing a letter to God but then running out of stamps

  • An octopus using its eight tentacles and 1,600 suction cups to type several lengthy suicide letters at once and its own ink to print them

  • A rake that doesn't know it's a rake and tries to get a job

  • An island with two palm trees but no coconuts

  • A porcupine getting married to a nectarine

  • A domino coming short of knocking down another domino

  • A mommy continent getting separated from a baby continent over a million years due to shifting of tectonic plates

  • A census taker who doesn't even know his own family

  • A lighthouse keeper who realizes that he is trapped in a Thomas Kinkade painting and quits his job to build the hillside cottage of his dreams.

  • A tramp who gets rich and buys a lycra bindle and a ten-million-dollar bottle of whiskey

  • A bunch of kids stealing a condor

  • A beaver dam made entirely out of sacred Indian totem poles

  • A continental divide that tears a hammock in two

  • A rope trick that is too realistic and scares a man into sobriety

  • A falconer with a fancy for stealing wigs

  • A man who becomes an angel the day after bells are outlawed

  • A time traveler who can't stand to be wrong, constantly time traveling to fix his own dumb mistakes

  • The man who would be king stuck in a long line at Arby's

  • A trampoline in zero-gravity

  • The biggest jumbo screen ever for the man with binocular eyes

  • A manatee driven mad by baseless guilt and shame

  • A revolving door tired of being the center of the universe

  • A monkey too old to bust open a walnut

  • A walnut living in a glass shell

  • A Tone Loc t-shirt in a thrift store with nobody around to appreciate it

  • A blindfolded mailbox being executed for refusing to talk (mailboxes cannot talk)

  • A hairdresser giving the best haircut she'll ever give in her life to a man who will be decapitated the next day

  • The most unique traffic cone in the world lost in a sea of identical traffic cones lining the edge of a road.

  • A man misses the bus and never sees his son again

  • The bus that hits that man's son, the future President of Sealand

  • A dozen turtles crawl across the road only to find out the promised land is all dried up due to a clogged storm drain

  • A man who falls asleep thinking about true love and has a dream about being eaten by a bear

  • The man who dreamed about being mauled by a bear once shot that very same bear's true love while robbing a convenience store

  • A beekeeper who lost his legs to war trains a hive of bees to act as new legs, learns to run again, enters the Olympics, and gets stung by a wasp an dies

  • An abused child who grows up until he's finally big enough to take on his old man, and then his old man drops dead of a stroke

  • A turtle too afraid to come out of its own shell due to acute agoraphobia trigged by post-traumatic stress disorder

  • A kangaroo who lives in a house with really low ceilings

  • A boy who wakes up one day and realizes for the first time that he no longer "gotta have his Pops."

  • A bunny rabbit so tired of poor TV reception that he uses his poor wife's head to reduce static

  • A clown who cries so much that thirsty birds peck at his eyes for moisture and laughs

  • A bulleted list update

  • O yeah, and by the way, Yvy's PREGNANT!!! 8^D I'm gonna be a dad!