Jump to content

Functional WEIDU


DavidW

Recommended Posts

Don't waste you time on trying to make it user-friendly. By doing so you risk switching your priorities from actually modding to writing code that none will use in the end.

 

The only stuff that really needs to be included in WeiDU are the most basic/universal macros/functions, by adding things like Cam's ones the only real achievement you may hope for is the size increase of the executable, 1-2 people who will use them don't count. Anything specific should be kept to the mod's lib.

 

 

PS To clarify, my point is that you can never prepare for everything, so the best course of action within such a small community as ours is to concentrate on your own needs. We don't aim to save the humanity, we can only either to make mods for a small number of players or to write code for a tiny number of modders - and you can bet their needs will differs from yours.

 

Being self-centered is all nice and good (and all too common imo), but on the other hand, if you do make WeiDU user-friendly, a lot more people will be able (and thus more willing) to create new mods. Isn't that the point of modding? It shouldn't be some elite club with only a handful of modders who are code-junkies. There are plenty of skilled writers who cannot put their skills to use because of being less skilled at coding, or lacking a code monkey to aid them whenever they need it.

 

That's just the other side of the coin, however. I'm sure we could argue back and forth endlessly, such as "No, I won't make it user friendly! Why don't they learn to code?", and "No, why don't you stop being selfish and make modding available to more people?". And quite frankly, I'm not into arguing. Just giving my 2gp.

Link to comment

Don't waste you time on trying to make it user-friendly. By doing so you risk switching your priorities from actually modding to writing code that none will use in the end.

 

The only stuff that really needs to be included in WeiDU are the most basic/universal macros/functions, by adding things like Cam's ones the only real achievement you may hope for is the size increase of the executable, 1-2 people who will use them don't count. Anything specific should be kept to the mod's lib.

 

 

PS To clarify, my point is that you can never prepare for everything, so the best course of action within such a small community as ours is to concentrate on your own needs. We don't aim to save the humanity, we can only either to make mods for a small number of players or to write code for a tiny number of modders - and you can bet their needs will differs from yours.

 

Being self-centered is all nice and good (and all too common imo), but on the other hand, if you do make WeiDU user-friendly, a lot more people will be able (and thus more willing) to create new mods. Isn't that the point of modding? It shouldn't be some elite club with only a handful of modders who are code-junkies. There are plenty of skilled writers who cannot put their skills to use because of being less skilled at coding, or lacking a code monkey to aid them whenever they need it.

 

In this particular case, it's fairly moot: the functional package I've written is a power tool for experienced coders and (I suspect) isn't really suitable for newcomers. (And in any case, it assumes a certain relaxedness about fairly abstract levels of programming.)

 

More broadly, I'm pretty convinced that a person (or team) that understands the structure of the game well enough to be able to write a worthwhile writing-heavy mod will have negligible trouble learning enough WEIDU to code that mod. By the same token, a person or team that has any significant trouble learning the code structure is probably not going to produce a mod worth playing in any case. This is a computer game, not a novel: writing ability is a necessary but not sufficient condition to produce a good dialog-heavy mod. And the "point of modding" is not to create more mods; it is to creategood mods.

Link to comment

I'm sure there's some interest in at least some of your functions.

 

Making a CRE file from scratch (this builds a 5th level Red Wizard)

...

(I could add enforce_mage=>null to do the spells and script automatically, but that's really part of SCS proper, not this functional package.)

This is an idea I have thought about in the past. I found the mechanical task of building creatures relatively easy, but making choices for them was quite difficult. I specifically wanted to be able to automate the creation of NPCs that follow the rules used by player characters, given directions ranging from "this is a level 5 character who specializes in ranged combat, make sure they have all the stats and equipment they need" to "this character is an evil level 13 cleric who has 17 strength and always fights with a flail and large shield". I had trouble determining how to create a system that would handle these kinds of requests.

Link to comment

This is an idea I have thought about in the past. I found the mechanical task of building creatures relatively easy, but making choices for them was quite difficult. I specifically wanted to be able to automate the creation of NPCs that follow the rules used by player characters, given directions ranging from "this is a level 5 character who specializes in ranged combat, make sure they have all the stats and equipment they need" to "this character is an evil level 13 cleric who has 17 strength and always fights with a flail and large shield". I had trouble determining how to create a system that would handle these kinds of requests.

 

The new version of SCS (in production) can do some of that. It can't allocate generic equipment or stats, but it can take a creature with ability scores, levels, race, class and equipment, and sort out appropriate proficiencies and spells.

Link to comment

As much for my own benefit as anything else, here's some partial documentation. ("SFO" is "Stratagems Functional Overlay".)

 

 

1: Installing

 

 

1) put the folder "lib" into your mod directory (I'll upload a copy shortly; no promises that it works properly!)

2) create an empty subdirectory of your mod directory called "workspace" (or something different if

 

you'd rather)

3) Include the following code in the "ALWAYS" section of your TP2:

 

 OUTER_SPRINT ~scsroot~ ~[your mod directory]~
 OUTER_SPRINT ~workspace~ ~%scsroot%/[whatever you called the "workspace" directory]~
 INCLUDE ~%scsroot%/lib/install_sfo.tpa~

 

This will cause a delay of about 20 seconds in starting the install of your mod. At the end of it,

 

all the SFO functions will be loaded. Also, some variables related to spells will be set: for each

 

spell SPELL (identified by IDS entry):

 

- SPELL will be set to the spl file of the spell

- SPELL_LEVEL will be set to the level of the spell (counting 1st level as zero)

- SPELL_TYPE will be set to wizard, priest, or innate, as appropriate

- SPELL_SCROLL will be set to a scroll of the spell, if one exists

 

 

For instance, WIZARD_STONE_SKIN is set to SPWI408, WIZARD_STONE_SKIN_LEVEL to 3,

 

WIZARD_STONE_SKIN_TYPE to wizard, and WIZARD_STONE_SKIN_SCROLL to scrl1v.

 

SFO gives fairly gnomic error messages; you can make it more forthcoming by putting OUTER_SET debug_variable=2 into your component.

Link to comment

2: Simple lists and arrow lists

 

A simple list is just a string with spaces separating its entries, such as

 

minsc minsc2 minsc4 minsc6

 

An arrow list is a string of pairs of entries, with each pair linked by an arrow "=>". Such as:

 

minsc=>ranger dynaheir=>mage khalid=>fighter

 

SFO uses simple lists and arrow lists fairly extensively for data input. Useful tools for handling them are the functions

 

ACTION/PATCH_FUNCTION return_first_entry STR_VAR list RET entry list

 

ACTION/PATCH_FUNCTION return_first_pair STR_VAR list RET key value list

 

return_first_entry strips the first entry from the list and returns it and the new list. return_first_pair strips the first pair from the list and returns its first member as key, its second as value, and the new list. (These functions get used very extensively in building SFO; they might not be so useful in using it.)

Link to comment

3: Inner Functions

 

 

An "inner function" (for want of a better term) is a patch_function of a particular form.

 

PATCH_FUNCTION [inner function] INT_VAR offset_secondary STR_VAR filename file_prefix offset_base arguments RET value END

(In most cases not all of the variables are needed.)

 

 

For instance, this inner function reads the override script of a creature:

PATCH_FUNCTION CRE_read_script_override RET value 

This one sets the class of a creature to arguments:

PATCH_FUNCTION CRE_class STR_VAR arguments

filename should always be set to the name of the object being patched (without .cre or similar); file_prefix should be set to cre, itm, spl or whatever. (I don't know why I called this "prefix".)

 

offset_base is used if the inner function is being used to read or edit some repeating entry, such as an ability header. In this case, offset_base should be set to the start of the header. (Yes, this should be an INT_VAR: stupid design mistake, too embedded to change now.) offset_secondary is only used if the inner function is being used to read or edit an effect, and should be set to the start of the effect; if it's an extended effect, offset_base should be set to the start of the relevant ability.

 

There are over 1700 inner functions and most are generated automatically. For pretty much any commonly-used field on an object in the IE, there is a read function (with a name like CRE_read_allegiance or ITM_read_description_string) that takes no arguments and returns the value of the field, and a write function (with a name like CRE_allegiance or ITM_description_string) that sets the field to arguments and returns nothing. If the field is numerical (save_vs_spell, say) there are also functions CRE_save_vs_spellGT and CRE_save_vs_spellLT that set the field equal to the argument only if this would increase it (GT) or decrease it (LT) I'll give a detailed listing of these inner functions later.

 

In the case of fields identified by an IDS file entry (like the class or allegiance of a creature) the relevant read function returns not the value of the field, but the associated IDS entry; the write function reverses this. So calling CRE_allegiance with an argument of ENEMY sets the field to 255, for instance.

 

You can perfectly well write your own inner functions: there's nothing special about them other than the standardised form.

Link to comment

4: Inner function patches

 

The value of inner functions is that they can be fed as input to other functions. For this purpose:

 

- an inner function array is an associative array each of whose key-value pairs consists of an inner function and its argument.

 

- an inner function inline array is an arrow list each of whose key=>value pairs again consists of an inner function and its argument.

 

In defining inner function arrays and inline arrays, if you're using the standard inner functions in SFO (but not if you write your own) you can leave off the prefix: so CRE_allegiance just becomes allegiance, for instance.

 

An example of defining an inner function array:

ACTION_CLEAR_ARRAY patch_data
ACTION_DEFINE_ASSOCIATIVE_ARRAY patch_data BEGIN
allegiance=>ENEMY
kit=>BARBARIAN
level=>18
class=>FIGHTER
END

 

The same thing as an inline array:

OUTER_SPRINT inline_patch ~allegiance=>ENEMY kit=>BARBARIAN level=>18 class=>FIGHTER~

 

Incidentally, one awkward thing about inner function arrays is that they can't have the same function appearing twice. To get around that, you can put as many ' as you like (kit', kit'', kit''', ...) on the end of an inner function when defining the array. (There's no need to do this for an inline array - though it's harmless to do so.)

Link to comment

5: Applying an inner function patch

 

You can do this for the following values of <object>: creature, item, spell, store, area, effect. For each, there are four key functions:

 

ACTION_FUNCTION edit_<object> STR_VAR <object> edits editstring tv
ACTION_FUNCTION clone_<object> STR_VAR <object> edits editstring
ACTION_FUNCTION edit_all_<object>s STR_VAR  edits editstring tv
ACTION_FUNCTION make_<object> STR_VAR <object> edits editstring

 

(Only spell, item and creature have make_<object> defined.)

For edit_<object>, <object> is a simple list of the objects you want to edit (without .cre or whatever). For clone_<object>, it's an arrow list of the form old_object=>new_object. For make_<object>, it's the filename of the object you want to create.

 

tv is set to no by default. If you set it to yes, then in edit_<object> each entry in the list is prefixed by "_" on a TUTU install, and in edit_all_<object>s, only objects beginning with "_" are edited.

 

edit should be set to an inner function array; editstring, to an inner function inline array. (Either can also be left blank.)

 

As you can probably guess, the result is that for each function=>argument pair in the argument of edits and editstring, the function is called with that argument. For instance, given the patch defined above,

LAF edit_creature STR_VAR creature=~minsc7 minsc 8 minsc9 minsc10 minsc12 minsc14~  edits=patch_data END

turns Minsc into a hostile 18th level barbarian (don't ask why). The same effect could be achieved by

ACTION_FOR_EACH num IN 7 8 9 10 12 14 BEGIN
  LAF edit_creature STR_VAR creature=EVALUATE_BUFFER ~minsc%num%~ editstring=inline_patch END
END

 

At this stage, note that there isn't any use for read inner functions, nor for inner functions that make use of offset_base and offset_secondary.

Link to comment

6. Applying a conditional patch

 

There are three special inner functions that take (the name of an) array of inner functions as an argument: patch_conditionally, delete_entries, clone_entry (yes, the nomenclature is inconsistent; so sue me). Each has an inline form (patch_cond_inline, delete_entries_inline, clone_entry_inline) which takes an inline array of inner functions instead.

 

In each case, the array needs to have a special form (a "conditional array"). As well as the usual list of function=>argument pairs, it must have these three entries:

 

match=>[an inner function]
check=>[a string]
type=>[a string]

Optionally, it can also have

match_parameter=>[a string].

type should be set to whatever header type you want to patch (or to "none" for the base file). The idea is that SFO goes through all the headers of this type and runs the match function on each, optionally with match_parameter as its argument. Whenever the result is equal to "check", the function either

 

- applies the rest of the array to the header (patch_conditionally); or

 

- makes a copy of the header immediately after the header and applies the rest of the array to it (clone_entry); or

 

- deletes the header and ignores the rest of the array (delete_entries)

 

 

If you are using the standard inner functions for match, you can omit either CRE or CRE_read, so that match=>CRE_read_level, match=>read_level, and match=>level deliver the same result.

 

For instance, this code makes all fighters into barbarians:

ACTION_CLEAR_ARRAY patch_data
ACTION_DEFINE_ASSOCIATIVE_ARRAY patch_data BEGIN
type=>none
match=>class
check=>FIGHTER
kit=>BARBARIAN
END
LAF edit_all_creatures STR_VAR edits=~patch_conditionally=>patch_data~ END

 

This code deletes the creatures at x-location 1021 and x-location 1405 from area ar5040:

 

ACTION_CLEAR_ARRAY patch_data
ACTION_DEFINE_ASSOCIATIVE_ARRAY patch_data BEGIN
 delete_entries_inline=>~match=>actor_x_loc
                                           check=>1021
                                           type=>actor~
 delete_entries_inline'=>~match=>actor_x_loc
                                          check=>1405
                                           type=>actor~

END
LAF edit_area STR_VAR area=ar5040 edits=patch_data END

(Note the ' on the second function; otherwise, the second entry would overwrite the first in the definition of patch_data).

 

A few notes:

 

(1) delete_entries doesn't work on ability or extended effect headers. If you want to delete the latter, either use ITM_delete_opcodes (which takes a simple list of integers as argument and deletes all effects with that opcode), or (for some more complicated condition) run a patch_conditionally to flag all effects meeting the condition with opcode 999, and then use ITM_delete_opcodes.

 

(2) to go through all entries of a given type, use match=>returns_true, check=>true

 

(3) to combine conditions, you can use the inner function combine_checks, which takes as argument (via match_parameter) an arrow list of function=>value pairs and returns 1 if each function evaluates to value.

Link to comment

I think I can name what I find the most disappealing - the abundance of copypasted by necessity lines. ACTION_CLEAR_ARRAY and ACTION_DEFINE_ASSOCIATIVE_ARRAY take a lot of space and shadow the real code. Visibility suffers, so I usually end up using a 2da table for the input.

 

Being self-centered is all nice and good (and all too common imo), but on the other hand, if you do make WeiDU user-friendly, a lot more people will be able (and thus more willing) to create new mods. Isn't that the point of modding? It shouldn't be some elite club with only a handful of modders who are code-junkies.

I speak from personal experience. There are some powerful macros available around the scene, but I have found only the basic ones to be of use for my needs.

 

This is an idea I have thought about in the past. I found the mechanical task of building creatures relatively easy, but making choices for them was quite difficult. I specifically wanted to be able to automate the creation of NPCs that follow the rules used by player characters, given directions ranging from "this is a level 5 character who specializes in ranged combat, make sure they have all the stats and equipment they need" to "this character is an evil level 13 cleric who has 17 strength and always fights with a flail and large shield". I had trouble determining how to create a system that would handle these kinds of requests.

Lol, I had such an idea too once ;D

 

 

PS Actually, I have changed my mind. It might be useful indeed, provided the user is willing to learn the new "language". For global tweaks you still need fine-tuned code to avoid the installation taking hours, but simple people may want to not know which offset is what.

Link to comment

I think I can name what I find the most disappealing - the abundance of copypasted by necessity lines. ACTION_CLEAR_ARRAY and ACTION_DEFINE_ASSOCIATIVE_ARRAY take a lot of space and shadow the real code. Visibility suffers, so I usually end up using a 2da table for the input.

 

I found that for a while. My solution was eventually to include code through a function rather than directly through WEIDU's INCLUDE. The function does a couple of substitutions, so that

 

MAKE_PATCH
this=>that
END

becomes

 

ACTION_CLEAR_ARRAY patch_data
ACTION_DEFINE_ASSOCIATIVE_ARRAY patch_data BEGIN
this=>that
END

and

PUSH string newstuff

becomes

SPRINT string ~%string% new stuff~

 

PS Actually, I have changed my mind. It might be useful indeed, provided the user is willing to learn the new "language". For global tweaks you still need fine-tuned code to avoid the installation taking hours, but simple people may want to not know which offset is what.

Actually, I haven't found major slowdowns coding this way, even for global runs (but then, I'm reasonably relaxed about small increases in install time as a price to pay for clear and easy-to-maintain code). And this particular simple person is very keen not to have to look up an offset whenever I use it or come back to old code. I am very bored of having to track bugs in my code down to a mistyped offset or a WRITE_LONG instead of a WRITE_SHORT.

Link to comment

7: functions and inner functions for CRE files

 

Standard read/write inner functions (these come in CRE_<function> and CRE_read_<function> forms, and <CRE>_functionGT and <CRE>_functionLT if appropriate):

 

CRE_xp_value

CRE_xp_total

CRE_state_[sleeping/berserk/panic/stunned/helpless/frozen_death/stone_death/exploding_death/flame_death/acid_death/dead/silenced/charmed/poisoned/hasted/slowed/infravision/blind]

CRE_state_[diseased/feebleminded/nondetection/bless/improved_invisibility/chant/luck/drawuponholymight/chantbad/blur/mirrorimage/confused/hidded]

color_[metal/minor/skin/leather/armor/hair]

CRE_hide_in_shadows

CRE_detect_illusions

CRE_set_traps

CRE_lore

CRE_open_locks

CRE_move_silently

CRE_find_traps

CRE_pick_pockets

CRE_resist_[fire/cold/electricity/acid/magic_fire/magic_cold/slashing/crushing/piercing/missile/magic]

CRE_level1 [level is a synonym]

CRE_level2

CRE_level3

CRE_str [strength is a synonym]

CRE_str_ex [strength_ex is a synonym]

CRE_int [intelligence is... you get the idea]

CRE_wis

CRE_dex

CRE_con

CRE_cha

CRE_script_[override/race/class/general/default]

CRE_specifics

CRE_save_vs[death/wands/poly/polymorph/breath/spell/spells]

CRE_thac0

CRE_hp_max

CRE_hp_current

CRE_dv

CRE_dialog

CRE_animation_code

CRE_effect_type

CRE_item_resource

CRE_item_charges_[1/2/3]

CRE_item_slot

CRE_memorized_spell

CRE_spell_resource

CRE_opcode

CRE_parameter1

CRE_parameter2

CRE_timing

CRE_duration

CRE_target

CRE_probability1

CRE_probability2

 

Flag-based read/write inner functions:

 

CRE_allegiance

CRE_kit

CRE_class

CRE_race

CRE_animation

CRE_general

 

 

Special inner functions:

 

CRE_gender: CRE_read_gender returns the gender field, CRE_gender sets both the gender and the sex field

CRE_hitpoints: sets both current and maximum

CRE_strip_script: takes a simple list as argument, removes any scripts in the list

CRE_swap_script: takes an arrow list as argument, substitutes any key script possessed for the associated value

CRE_invert_scripts: takes an arrow as argument, swaps the two scripts if both are possessed

CRE_locate_script: takes a string as argument, returns 1 if the string corresponds to a possessed script and 0 otherwise

CRE_insert_script: takes as argument the phrase "<newscript> above <oldscript>" or "<newscript> below <oldscript>" and, if oldscript is possessed, inserts newscript adjacent to it, moving other scripts if necessary

CRE_insert_script_high: takes as argument a script, and inserts it in script_override, if necessary moving other scripts down

CRE_insert_script_low: takes as argument a script, and inserts it in script_default, if necessary moving other scripts up

CRE_add_spells: takes as argument a list of spells and adds them. Spells can be in file form (spwi312) or in IDS form (WIZARD_FIREBALL). The initial WIZARD_ or CLERIC_ can be omitted provided the result is unambiguous (so add_spells=>spwi304, add_spells=>WIZARD_FIREBALL, and add_spells=>FIREBALL all give the same result). If a number is placed in parentheses immediately after the spell (SPWI304(8) or FIREBALL(8), for instance), that number of copies are added.

CRE_remove_spells: takes as argument a list of spells (in the same format as above) and removes them. If the argument is instead "wizard", "innate", "priest", or "all", all spells of the appropriate type are removed. (Note that spells are identified by prefix, not by type: remove_spells=>wizard removes all spells of the form SPWIabcd.)

CRE_immunity_to_spell: takes as argument a list of spells (in the format above) and gives immunity to each

CRE_immunity_to_string: takes as argument a list of integers, and gives immunity to the strings associated with them

CRE_immunity_to_icon: takes as argument a list of integers, and gives immunity to the icons associated with them

CRE_immunity_to_opcode: takes as argument a list of opcodes, and gives immunity to the strings associated with them

CRE_enforce_[saves/thac0/hp]: takes as argument one of "exact", "at_worst", and "at_best", and sets the appropriate parameters accordingly. (The hit point algorithm is a little approximate, especially for dual-classed humans.)

CRE_add_items: takes as input a list of items and adds them, putting them into the appropriate slot. You can override the slot, and specify number of items, by putting either in parentheses - BOLT01(20) or SW1H04(SHIELD) for instance. If you want to do both, use commas, as in add_items=>~bolt02(20,AMMO2)~.

CRE_remove_items: takes as input a list of items and removes them all.

CRE_swap_items: takes as input an arrow list of item pairs and swaps the first for the second in each pair.

CRE_add_effect_inline: takes as input an arrow list of effect data in the same format as ADD_CRE_EFFECT, and adds the appropriate effect. Assumes (and tries to impose) EFFv1.

CRE_quick_effect: as CRE_add_effect_inline, but preset to timing: permanent, target: self.

CRE_read_proficiency: takes as argument a proficiency (in the format given in STATS.IDS, minus the WEAPON prefix) and returns the number of stars in it

CRE_add_proficiencies: takes as argument an arrow list where the keys are proficiencies and the values are integers, and increases each proficiency by the listed number.

CRE_set_proficiencies: as above, but the values are set, not added.

CRE_delete_opcodes: takes as argument a list of integers, and deletes any effects with those integers as opcodes

CRE_is_[warrior/thief/lawful/chaotic/lcneutral/good/evil/geneutral/dead]: returns 1 if the appropriate condition holds, and 0 otherwise.

CRE_make_[lawful/chaotic/lcneutral/good/evil/geneutral]: alters the appropriate alignment component while leaving the other part unchanged.

CRE_read_item_in_slot: takes as argument a slot (in the ADD_CRE_ITEM format) and returns the item in that slot, if any.

CRE_has_spell: takes as argument a spell, and returns 1 if the creature has it, 0 otherwise.

CRE_check_if_moron: returns 1 if the creature has GENERAL=MONSTER and is not a doppelganger, beholder, dragon, or mind flayer, if the creature has RACE=GOLEM, or if the creature is a zombie or skeleton (but not skeleton warrior)

CRE_log_me: appends the creature's resource name to the file ~%workspace%/<argument>~, which is created if it does not already exist

CRE_modernise: attempts to convert the creature to EFF v1.0 if it isn't already (this piggybacks off the WEIDU-shipped function)

CRE_enforce_animation: if the creature is a a humanoid of standard class, assign the appropriate animation. Doesn't work for, e.g., dragons or beholders.

CRE_set_name: sets the name to the string in argument

CRE_say_name: takes an integer N as argument, looks up string @N, and SAYs it to the name field.

Link to comment

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...