Scripting Secrets

Discussion in 'Scripting' started by darmagon, May 28, 2006.

Remove all ads!
  1. darmagon

    darmagon stumbler in the dark

    Joined:
    Mar 22, 2005
    Messages:
    295
    Likes Received:
    0
    This thread is for the purpose of passing along specific scripting solutions to various problems you may encounter while trying to mod ToEE. Please see my "starter' posts below for examples of what I mean. Also, could I get a moderator to please sticky this thread? Thnx

    Darmagon
     
    Last edited by a moderator: May 28, 2006
  2. darmagon

    darmagon stumbler in the dark

    Joined:
    Mar 22, 2005
    Messages:
    295
    Likes Received:
    0
    Starter post number one. Inspired by this thread started by ShiningTed:unequip

    Quote:
    Originally Posted by Shiningted
    ...to disequip an npc? unequip_best_all doesn't do it for me
    try this:
    EDIT: this works only for pcs and npcs controlled by the party. other npcs have the nasty habit of auto-equipping whatever you give them (or is that only weapons)

    Code:
    def unequip( slot, npc): 
        item = npc.item_worn_at(slot) 
        if item != OBJ_HANDLE_NULL:
            holder = game.obj_create(1004, npc.location) 
            holder.item_get(item) npc.item_get(item) 
            holder.destroy()
    
    Usage: slot is the equipped slot number. I believe there are 16, numbered 0 to 15. see here for the list 0 is hats, 3 is for primary weapons. I forget the rest (I had a list but I misplaced it, sorry). npc is the character you want to unequip. So if you wanted to unequip the primary weapon of your party leader call the function like this.


    Code:
    unequip(3,game.party[0])

    You can add the function to the end of utilities.py but remember to:


    Code:
    from utilities import *
    either at the console or in your script before trying to call the function.

    Oh and remember to preserve the indentation in the function as that is important to python.

    Darmagon
     
    Last edited: Jun 2, 2006
  3. darmagon

    darmagon stumbler in the dark

    Joined:
    Mar 22, 2005
    Messages:
    295
    Likes Received:
    0
    points along simple shapes to position spawns etc.

    Okay Starter post #2.

    The following functions generate ToEE coordinates along, respectively, a circle and a line. the lists of locations returned are suitable for use with game.obj_create as the location parameter. Usage and furhter details are given after each function definition.

    First the circle:
    Code:
    def get_circle_point_list(center, radius,num_points):
    	p_list=[]
    	offx, offy = location_to_axis(center)
    	i = 0.00
    	while i < 2*pi:
    		posx = int(cos(i)*radius)+offx
    		posy = int(sin(i)*radius)+offy
    		loc = location_from_axis(posx,posy)
    		p_list.append(loc)
    		i = i + pi/(num_points/2)
    	return p_list
    
    usage should be fairly self explanatory: center is a ToEE location, radius is the radius in Toee units of the circle, and num_points is the number of points you want evenly distributed around the circle.

    For example you could use this function to create a ring of 15 Greater TEmple bugbears centered on the party leader at a distance of 10 units like this:
    Code:
     
    p_list = get_circle_point_list(game.party[0].location,10,15)
    for point in p_list:
        game.obj_create(14174, point)
    
    Second the line:
    Code:
    def get_line_point_list(origin,distance,angle,length,num_points):
    	p_list = []
    	line_step = length/num_points
    	offx, offy = location_to_axis(origin)
    	ra = float(angle)/180.0*pi
    	normx = cos(ra)
    	stepy = normx*line_step
    	normy = sin(ra)
    	stepx = normy*line_step
    	transx = int(normx*distance)
    	transy = int(normy*distance)
    	x = -normy*length/2
    	y = normx*length/2
    	i = 0
    	while i < num_points:
    		loc = location_from_axis(int(x)+offx+transx, int(y)+offy+transy)
    		p_list.append(loc)
    		x = x+stepx
    		y = y+stepy
    		i = i+1
    	return p_list
    
    Usage: origin a ToEE location, distance is the distance from the origin you want your line to be, angle is the angle of direction you want your line to be in (the actual line will be a line perpendicular to this angle), length is the length of the line and num_points is how many points you want to generate along that line.

    So if you want to generate 10 points along a 30 unit long line angled at 45 degrees with respect to the ToEE coordinate system, 10 units away from your party leader do this
    Code:
    p_list = get_line_point_list(game.party[0].location,10,-45,30,10)
    
    This is just a little bit of what can be done with this type of thing. If you want a specific geometry(say an ellipse, a square or just about any other shape you can imagine, please PM me and let me know) or point rotations, translations, scalings or shears please let me know.

    Darmagon
     
  4. Cerulean the Blue

    Cerulean the Blue Blue Meanie Veteran

    Joined:
    Apr 23, 2005
    Messages:
    1,962
    Likes Received:
    0
    MOVING THINGS AROUND THE SAME MAP

    There is a little known and little used python function in ToEE that allows you to move anything you can get a handle on to a different point on the same map instantaneously:
    Code:
    obj.move(location_from_axis(x, y))
    "obj" is the object you want moved, and x and y are the xy coordinates you want the object moved to. As I said, this move in instantaneous. You will not see the object move, it will just immediately be at the new location.

    Perhaps you want to move the whole party this way, ala Sitra's Signpost mod. This little script does just that:
    Code:
    from toee import *
    
    from utilities import *
    
    from __main__ import game
    
    def move(x,y):
    	for obj in game.party[0].group_list():
    		obj.move(location_from_axis(x,y))
    	return
    
    This script moves everyone and everything attached to the party as well as the party members, so animal companions, summoned and charmed creatures, rebuked undead, etc. all go as well.

    One drawback to this is that obj.move does not call the spread out routine that is called when you teleport or change maps. This means that, while your party will end up at the new coordinates, they will all be standing at those coordinates, in what appears to be a single blue circle, like single multi-headed, multi-limbed creature. Once you move the party they will spread out again, but it looks kind of strange at first.

    To combat this I added some code that preserves the party members spacial relationship to each other:
    Code:
    def move(x,y):
    	rx, ry = location_to_axis(game.party[0].location)
    	for obj in game.party[0].group_list():
    		cx, cy = location_to_axis(obj.location)
    		dx = rx - cx
    		dy = ry - cy
    		obj.move(location_from_axis(x-dx,y-dy))
    	return
    
    With this, the party leader appears at the target coordinates and the rest of the party appears around him in the same spacial relationship to him as they had when the script was called (the party leader can easily be changed to whatever PC or NPC or whatever initiated the script call).This method has some drawbacks as well. Another function of the spreadout routine is that it keeps party members from spawning in blocked sectors. Since it is not called by the move function, this script can move party members into blocked sectors, where they will be stuck. I purposely tried to make this happen in my testing, and ended up with the entire party, sans leader, on the Homlett map but stuck inside a house.

    Obviously, if this method is to be used the script should be tailored to each specific target location to ensure nothing gets stuck in blocked sectors. Or it may be possible to write our own spreadout routine that can do just that.
     
    Last edited: Dec 22, 2006
  5. Cerulean the Blue

    Cerulean the Blue Blue Meanie Veteran

    Joined:
    Apr 23, 2005
    Messages:
    1,962
    Likes Received:
    0
    Making Reliable Long Interval Time Events


    After much thought and effort trying to make this work using Spellslinger's Persistent Data, I came to the conclusion that it was simpler and easier to spit long duration timevents into a sequence of shorter ones. Since experience shows that timevents of 24 hour intervals fire consistently, I decided that 24 hours would be the maximum interval. Here is a example of how this works:
    Code:
    from toee import *
    
    from __main__ import game
    
    
    def set(time): # time is the delay until event happens, in hours.
    	""" These two lines can be put into whatever function you like."""
    	t = game.time.time_game_in_hours2(game.time) + time # Current elapsed game time plus the time delay, in hours
    	spawn(t)
    	return
    
    def spawn(time): # time is now the elapsed game time until event happens.
    	t = game.time.time_game_in_hours2(game.time) # Current game time in hours when this function is run.
    	if not game.global_flags[900]: # Flag for stopping timed event from happening once it has been initiated.
    		if time > t: # Not yet time for event to happen.
    			if time >= (t + 24): # 24 or more hours until event happens.
    				interval = 86400000 # 24 hours in milliseconds
    			else: # Less than 24 hours until event happens.  Start 
    				interval = 3600000 # 1 hour in milliseconds
    			game.timevent_add(spawn, (time), interval) # Sets timed event for determined interval.
    			return
    		game.obj_create(14025, game.party[0].location-5) # The event.
    	return
    
    This particular example spawns Furnok near the party after the specified amount of time, in hours, has passed. If you call set(30) Furnok appears after 30 hours. If you call set(744) he appears after 30 days and 1 hour. I have tested this script and it works across saved games as well.

    In set(time) I use one of the game.time methods from this post to get the total elapsed hours of game time at the time the function is called and add the specified event time in hours (time) to it, and then pass it to spawn(time). The function spawn(time) then uses the same game.time method to compare the current elapsed hours of game time to the specified event time and chooses either to use an interval of 24 hours or 1 hour for the timevent, in which it calls itself. Thus it breaks the time interval into a sequence of timevents, each one calling the spawn(time) function again. When the current elapsed hours match the specified event time, Furnok is spawned.

    I hope this is all clear. If you have questions, please do not post them in this thread. Post them in this thread.

    Attached are this sample script (_TimedEvent.py), and Thrommel's, the Woodcutter's, Tillahi's and Sargen's scripts modified to use this method. Note that this method will result in the events happening earlier in the game than they used to, even though I used the same interval as before. This is because this method now correctly accounts for time spent resting.
     

    Attached Files:

    Last edited: Dec 29, 2006
  6. Shiningted

    Shiningted I changed this damn title, finally! Administrator

    Joined:
    Oct 23, 2004
    Messages:
    12,740
    Likes Received:
    374
    Actual Skill Checks

    There have been an increasing number of dialogues coming in to KotB where the author expects, or asks for, actual d20 skill checks. These aren't hard to do, but have some drawbacks. Anyway, I have done a couple of simple versions: one only allows the standard 'high enough to beat the DC' version to appear if you pass a roll, the other does a proper roll and has pass and fail effects.

    Here is the variant on the current format, whereby a roll is made and the option to attempt only appears if you pass the roll: very simple, but also effective, and better than the 'once you have 6 ranks you always win' current situation.
    Code:
    def bluff( attachee, triggerer, dc ):
    	x = game.random_range(1,20)
    	y = x + triggerer.skill_level_get(attachee, skill_bluff)
    	if x == 1:
    		return 0
    	if y >= dc or x == 20:
    		return 1
    	return 0
    
    def dipl( attachee, triggerer, dc ):
    	a = game.random_range(1,20)
    	b = a + triggerer.skill_level_get(attachee, skill_diplomacy)
    	if a == 1:
    		return 0
    	if b >= dc or a == 20:
    		return 1
    	return 0
    
    def intim( attachee, triggerer, dc ):
    	c = game.random_range(1,20)
    	d = c + triggerer.skill_level_get(attachee, skill_intimidate)
    	if c == 0:
    		return 0
    	if d >= dc or c == 20:
    		return 1
    	return 0
    
    def info( attachee, triggerer, dc ):
    	e = game.random_range(1,20)
    	f = e + triggerer.skill_level_get(attachee, skill_gather_information)
    	if e == 1:
    		return 0
    	if f >= dc or e == 20:
    		return 1
    	return 0
    In each case you simply specify the dc and the game does the rest. You call them simply by placing the relevant check in the comparative part of the dialogue, and if the check works it appears as an option. Hence you can also have 'not' calls for failed checks, to give the players the choice of attempting something dumb, failing, and facing the conseqeunces (eg an attempt to intimidate a venerable dragon - if they are silly enough to try, and fail, they should face the consequences).

    An example of some working versions:

    {10}{[The person in black robes points his hand at you.] Halt! I sense my lord's arch-nemesis! Prepare to die!}{[The person in black robes points his hand at you.] Halt! I sense my lord's arch-nemesis! Prepare to die!}{}{}{}{}
    {11}{You must be mistaken! None of us worships that wretched do-gooder! Hail to Hextor, Herald of Hell!}{}{8}{bluff(npc, pc, 20)}{200}{pc.condition_add_with_args("Fallen_Paladin",0,0)}
    {12}{Leave my friends out of this. This is between you and me. Let me challenge you to a duel. If I win you let us go. If not you are allowed to do to me and my friends whatever you please.}{}{8}{pc.stat_level_get( stat_deity ) == 8 and dipl(npc, pc, 15)}{210}{}
    {16}{Wait! We did not do anything to harm you, let us go and we promise not to come back here ever again.}{}{8}{dipl(npc, pc, 10)}{370}{}


    The main drawback is that these lines don't light up as 'buff', 'intimidate' etc. Since players are meant to call their uses of these skills ("ok, DM, I try to Intimidate him") this is a step backward: so you have to place a simple call to the skill AND the roll (which can also be used to prevent people with no ranks in the skill getting a lucky roll and using it):

    {1}{Ug.}{Ug.}{}{}{}{}
    {2}{Bluff, bluff, bluff the stupid Ogre.}{}{-7}{pc.skill_level_get(npc, skill_bluff) >= 4 and bluff (npc, pc, 10)}{20}{}
    {3}{[Run.]}{}{1}{}{0}{}
    {4}{[Fight.]}{}{1}{}{0}{npc.attack(pc)}

    Ok, the second example is the full pass / fail version. If you could be bothered to do a 'fail' attempt (and that depends very much on the circumstances, but is obviously better if you can) then this will automatically check if the player has beaten the listed DC then send him to the pass or fail dialogue accordingly.

    Interesting aside: it took me literally months and months to get this going, because the parameters I was using qwere called 'pass' and 'fail', and 'pass' is apparently a command in Python. So the game tried to parse it as a command not a parameter, and failed, and that was that. Very annoying: I had no idea what was going on. I changed the parameters to 'ayup' and 'nope' and problem solved very quickly.
    Code:
    def sense_roll( attachee, triggerer, dc, ayup, nope ):
    	i = game.random_range(1,20)
    	j = i + triggerer.skill_level_get(attachee, skill_sense_motive)
    	if j >= dc:
    		triggerer.begin_dialog( attachee, ayup )
    	else:
    		triggerer.begin_dialog( attachee, nope )
    	return
    
    def intim_roll( attachee, triggerer, dc, ayup, nope ):
    	k = game.random_range(1,20)
    	l = k + triggerer.skill_level_get(attachee, skill_intimidate)
    	if l >= dc:
    		triggerer.begin_dialog( attachee, ayup )
    	else:
    		triggerer.begin_dialog( attachee, nope )
    	return
    
    def bluff_roll( attachee, triggerer, dc, ayup, nope ):
    	m = game.random_range(1,20)
    	n = m + triggerer.skill_level_get(attachee, skill_bluff)
    	if n >= dc:
    		triggerer.begin_dialog( attachee, ayup )
    	else:
    		triggerer.begin_dialog( attachee, nope )
    	return
    
    def dipl_roll( attachee, triggerer, dc, ayup, nope ):
    	o = game.random_range(1,20)
    	p = o + triggerer.skill_level_get(attachee, skill_diplomacy)
    	if p >= dc:
    		triggerer.begin_dialog( attachee, ayup )
    	else:
    		triggerer.begin_dialog( attachee, nope )
    	return
    
    def gath_roll( attachee, triggerer, dc, ayup, nope ):
    	q = game.random_range(1,20)
    	r = q + triggerer.skill_level_get(attachee, skill_gather_information)
    	if r >= dc:
    		triggerer.begin_dialog( attachee, ayup )
    	else:
    		triggerer.begin_dialog( attachee, nope )
    	return
    
    Obviously, 'ayup' is the line number to go to if the check succeeds, 'nope' is where to go if it fails, and 'dc' is the dc.

    Any problems, PM me or start a thread somewhere.
     
    Last edited: Jan 9, 2008
  7. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Bitwise flags

    Using vars/integers as bitwise flags:

    You can use variables or integer values as a sequence of flags. If you know some binary you probably already know what I mean. If you don't, read on.

    Decimal numbers can be translated to binary representation - a sequence of 1's and 0's. For instance: 42 in decimal representation is equivalent to 101010 in binary representation. (you can convert back and forth with the windows calculator in scientific mode)

    This can be exploited to use internal variables, like the often used obj_f_npc_pad_i_5, as a bunch of flags.
    An example:
    Code:
    a = attachee.obj_get_int(obj_f_npc_pad_i_5)
    if (this and that happened...):[INDENT]a = a | 8
    attache.obj_set_int(obj_f_npc_pad_i_5, a)
    [/INDENT]
    What this does -
    The first line reads the internal variable into a temporary variable called 'a' (mainly for convenience). If some event took place that you want to remember using this internal variable, change the fourth digit of 'a' (in binary representation) to 1.

    I hear you ask: What does the | symbol mean? How is 8 connected with the fourth digit?

    Powers of 2 correspond to a single digit in binary. Thus the conversion is (from decimal to binary):
    1 -> 1
    2 -> 10
    4 -> 100
    8 -> 1000
    16 -> 10000
    32 -> 100000
    etc.
    It may also help to think of these conversions as:
    1 -> 000001
    2 -> 000010
    4 -> 000100
    8 -> 001000
    16 -> 010000
    32 -> 100000

    So you see the Nth bit from the right represents the 'presence' or 'weight' of the number 2^(N-1).

    Using the | operator, I set the 4th bit from the right to 1. If I had typed a = a| 16, it would have set the fifth bit from the right to one. a = a | 4 for the third bit from the right. And so on.

    In order to set a particular bit to 0, the easiest way to do it in ToEE scripting that I know of is to subtract the number corresponding to the bit, e.g.
    a = (a | 8) - 8
    (you can skip the "| 8" part if you are sure that the bit is set to 1, otherwise you will mess things up)

    If you want to check whether a bit is set to 1, use the "&" operator:
    Code:
    a = obj_get_int(obj_f_npc_pad_i_5)
    if (a & 8 == 8):[INDENT]do xyz...
    [/INDENT]
    Note that I wrote a & 8 == 8, rather than a & 8 == 1. This is important, because the "& 8" operation acts as a bit mask that sets all digits to zero except the one corresponding to 8, so the result will either be 8 or 0.
    If you want to avoid the confusion, just write:
    Code:
    if (a & 8 != 0):[INDENT]run nifty script...
    [/INDENT]
    Some more indepth explanations:

    The | symbol represents the 'Logical OR' operator. It compares the binary digits of two numbers, and returns a new number where every bit is equal to 1 if at least one of the digits were 1, and 0 otherwise.
    For example: type into the console
    Code:
    26 | 12
    and the value returned will be 30.
    Let's translate this to binary: 26 | 12 in binary representation looks like
    Code:
    11010 | 01100 
    The only place where neither digit is 1 is the first (rightmost) digit, so in binary we'll get:
    11110
    Which equals 30 in decimal representation.


    The & symbol represents the 'Logical AND' operator. The principle is the same, except this time BOTH digits have to be 1 in order for the resulting digit to be 1 - for instance:
    Code:
    26 & 12
    will yield:
    Code:
    8
    translated to binary:
    Code:
    11010 & 01100
    will yield:
    Code:
    01000
    Which equals 8 in decimal.

    IMPORTANT NOTE:
    Don't try to use binary directly, e.g.
    a = a | 1000
    or
    a & 1000
    Because ToEE will treat the '1000' number as a decimal number, which is equal to 1111101000 in binary.


    Of course, everything mentioned here can be applied to global vars, e.g.
    Code:
    game.global_vars[456] = game.global_vars[456] | 4
    if game.global_vars[456] & 16 != 0:[INDENT]tralala
    [/INDENT]
    You can also use this to store two numbers in the same variable using some more clever techniques - for instance, suppose you want to store a year and a month into the same variable:
    Code:
    year = 579
    month = 4
    game.global_vars[567] = month + 32 * year
    
    and to read it back:
    Code:
    year = game.global_vars[567] / 32
    month = game.global_vars[567] - 32 * year
    
    An assumption is made here that months go from 1 to 12. This range of numbers takes 4 digits to represent, so I stored the month in the first 4 digits and the year in the digits above that. Multiplying the year by 32 shifts the equivalent binary number by 5 digits to the left, so the whole number will look like:
    Code:
    [year digits][month digits]
    To retrieve the year, I used division. Since the global variables are integers, this gets rid of the 'month' digits and shifts the year back into place. Once you have the year, you can get the month back by subtracting the year digits.
    If there had been other 'digit ranges', for instance 'hour digits' and 'minute digits', I would have to do this a little differently. I'll leave it as an exercise for the reader...
     
    Last edited: Jul 4, 2009
  8. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Written notes and text boxes:

    First off, you can display notes' contents using the command game.written_ui_show. The usage is as follows:

    game.written_ui_show( written_obj_handle )

    Where written_obj_handle is, of course, the written object's handle.

    How does it know what message to display?

    That information is encoded in the object internal variable obj_f_written_text_start_line. The great thing about this is that you can reuse an existing, generically named note proto, and set the variable to whatever you want. For instance:
    Code:
    a = game.obj_create(11001,game.leader.location)
    a.obj_set_int(obj_f_written_text_start_line, 10)
    game.written_ui_show(a)
    a.destroy()
    
    This will display the text from Otis' letter, and immediately destroy the note (so it's more like a pop-up box, similar to what goes on in the tutorial).

    In fact, this code snippet could probably be added to the utilities file as pop_up_box(message_id).

    The message itself appears in the help.tab file, and is referenced according the the written_ui.mes file. For more details, see one of Ted's tutorials.

    Executive summary:
    1. You can display text boxes like the ones in the game tutorial using the command written_ui_show()
    2. Check out the file written_ui.mes in the rules folder
    3. The above file references a line from help.tab
    4. A note object can be assigned any entry from within written_ui.mes by the command note_obj.obj_set_int(obj_f_written_text_start_line, 10) (with 10 being an index pointing to a line within written_ui.mes)
     
    Last edited: Aug 29, 2008
  9. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Checking if an object is within a rectangle:

    This function requires that utilities.py be imported, i.e. that in the beginning of the script file the line
    Code:
    from utilities import *
    appears.

    Code:
    def within_rect_by_corners(obj, ulx, uly, brx, bry):
        xx, yy = location_to_axis(obj.location)
        if ( (xx - yy) <= (ulx-uly)) and ( (xx - yy) >= (brx-bry) ) and ( (xx + yy) >= (ulx + uly) ) and ( (xx+yy) <= (brx+bry) ):
            return 1
        return 0
    
    
    This checks if a given object falls within the rectangle defined by an upper left corner and bottom right corner x,y coordinates (retrievable with the loc command). It should be noted that the rectangle's edges are parallel to the 'visual' x,y axes rather than the game engine's x,y axes (i.e. it's more intuitive for scripting purposes)

    Some explanations:

    In ToEE, the x,y axes are rotated 135 degrees with respect to what you'd expect. See attached image. The script does all the math for you and defines a rectangle as you'd expect it to be (same as in the 'intuitive axes' drawing).
     

    Attached Files:

  10. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Here's a couple of scripts I made to quickly display an object's Object flags and NPC flags via console:

    Code:
    def dof(obj): 
        ## A handy little function to display an objects' object flags
        col_size = 10
        object_flags_list = ['OF_DESTROYED','OF_OFF','OF_FLAT','OF_TEXT','OF_SEE_THROUGH','OF_SHOOT_THROUGH','OF_TRANSLUCENT','OF_SHRUNK','OF_DONTDRAW','OF_INVISIBLE','OF_NO_BLOCK','OF_CLICK_THROUGH','OF_INVENTORY','OF_DYNAMIC','OF_PROVIDES_COVER','OF_RANDOM_SIZE','OF_NOHEIGHT','OF_WADING','OF_UNUSED_40000','OF_STONED','OF_DONTLIGHT','OF_TEXT_FLOATER','OF_INVULNERABLE','OF_EXTINCT','OF_TRAP_PC','OF_TRAP_SPOTTED','OF_DISALLOW_WADING','OF_UNUSED_0800000','OF_HEIGHT_SET','OF_ANIMATED_DEAD','OF_TELEPORTED','OF_RADIUS_SET']
        lista = []
        for p in range(0, 31):
            lista.append(''.join([ object_flags_list[p],' - ',str(obj.object_flags_get() & pow(2,p) != 0) ]))
        lista.append(''.join([ object_flags_list[31],' - ',str(obj.object_flags_get() & OF_RADIUS_SET != 0) ]))
        lenmax = 1
        for p in range(0,31):
            if len(lista[p]) > lenmax:
                lenmax = len(lista[p])
        ##print 'lenmax = ',str(lenmax)
        print ''
        for p in range(0,col_size+1):
            len1 = len(''.join([ object_flags_list[p],' - ',str(obj.object_flags_get() & pow(2,p) != 0) ]))
            len2 = len(''.join([ object_flags_list[p+col_size+1],' - ',str(obj.object_flags_get() & pow(2,p+col_size+1) != 0) ]))
            if p >= col_size-1:
                hau = OF_RADIUS_SET
            else:
                hau = pow(2,p+2*col_size+2)
            har1 = ''
            har2 = ''
            for p1 in range(0, lenmax-len1+1):
                har1 += '  '
            for p2 in range(0, lenmax-len2+1):
                har2 += '   '
            if p < col_size:
                print ''.join([ object_flags_list[p],' - ',str(obj.object_flags_get() & pow(2,p) != 0) ]),har1,''.join([ object_flags_list[p+col_size+1],' - ',str(obj.object_flags_get() & pow(2,p+col_size+1) != 0) ]),har2,''.join([ object_flags_list[p+2*col_size+2],' - ',str(obj.object_flags_get() & hau != 0) ])
            else:
                print ''.join([ object_flags_list[p],' - ',str(obj.object_flags_get() & pow(2,p) != 0) ]),har1,''.join([ object_flags_list[p+col_size+1],' - ',str(obj.object_flags_get() & pow(2,p+col_size+1) != 0) ])
    
    
        return
    
    def dnf(obj): 
        ## A handy little function to display an objects' NPC flags
        col_size = 10
        npc_flags_list = ['ONF_EX_FOLLOWER','ONF_WAYPOINTS_DAY','ONF_WAYPOINTS_NIGHT','ONF_AI_WAIT_HERE','ONF_AI_SPREAD_OUT','ONF_JILTED','ONF_LOGBOOK_IGNORES','ONF_UNUSED_00000080','ONF_KOS','ONF_USE_ALERTPOINTS','ONF_FORCED_FOLLOWER','ONF_KOS_OVERRIDE','ONF_WANDERS','ONF_WANDERS_IN_DARK','ONF_FENCE','ONF_FAMILIAR','ONF_CHECK_LEADER','ONF_NO_EQUIP','ONF_CAST_HIGHEST','ONF_GENERATOR','ONF_GENERATED','ONF_GENERATOR_RATE1','ONF_GENERATOR_RATE2','ONF_GENERATOR_RATE3','ONF_DEMAINTAIN_SPELLS','ONF_UNUSED_02000000','ONF_UNUSED_04000000','ONF_UNUSED08000000','ONF_BACKING_OFF','ONF_NO_ATTACK','ONF_BOSS_MONSTER','ONF_EXTRAPLANAR']
        lista = []
        for p in range(0, 31):
            lista.append(''.join([ npc_flags_list[p],' - ',str(obj.npc_flags_get() & pow(2,p) != 0) ]))
        lista.append(''.join([ npc_flags_list[31],' - ',str(obj.npc_flags_get() & OF_RADIUS_SET != 0) ]))
        lenmax = 1
        for p in range(0,31):
            if len(lista[p]) > lenmax:
                lenmax = len(lista[p])
        ##print 'lenmax = ',str(lenmax)
        print ''
        for p in range(0,col_size+1):
            len1 = len(''.join([ npc_flags_list[p],' - ',str(obj.npc_flags_get() & pow(2,p) != 0) ]))
            len2 = len(''.join([ npc_flags_list[p+col_size+1],' - ',str(obj.npc_flags_get() & pow(2,p+col_size+1) != 0) ]))
            if p >= col_size-1:
                hau = ONF_EXTRAPLANAR
            else:
                hau = pow(2,p+2*col_size+2)
            har1 = ''
            har2 = ''
            for p1 in range(0, lenmax-len1+1):
                har1 += '  '
            for p2 in range(0, lenmax-len2+1):
                har2 += '   '
            if p < col_size:
                print ''.join([ npc_flags_list[p],' - ',str(obj.npc_flags_get() & pow(2,p) != 0) ]),har1,''.join([ npc_flags_list[p+col_size+1],' - ',str(obj.npc_flags_get() & pow(2,p+col_size+1) != 0) ]),har2,''.join([ npc_flags_list[p+2*col_size+2],' - ',str(obj.npc_flags_get() & hau != 0) ])
            else:
                print ''.join([ npc_flags_list[p],' - ',str(obj.npc_flags_get() & pow(2,p) != 0) ]),har1,''.join([ npc_flags_list[p+col_size+1],' - ',str(obj.npc_flags_get() & pow(2,p+col_size+1) != 0) ])
    
    
        return
    
    Also appears in t.py attached below.
     

    Attached Files:

    • t.zip
      File size:
      5.5 KB
      Views:
      4
    • dof.jpg
      dof.jpg
      File size:
      82.8 KB
      Views:
      92
  11. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Here's how the sell modifier works for bartering:

    For an appraise skill of 0, the sell modifier is 40% (meaning you sell at 40% price, assuming it's the right type of shopkeeper).

    For every skill above or below, add or subtract 3%, up to a maximum of 97% (requires appraise skill of 19), and a minimum of 0% (for an appraise skill of less than -13 - at which point NPCs won't buy anything from you).

    Here is a script I wrote to calculate that, which is meant for autoselling items:
    (the script also gives a bonus if you have a wiz/bard in your party, who are assumed to be capable of boosting the skill by +2 each)

    Code:
    def sell_modifier():
        highest_appraise = -999
        for obj in game.party:
            if obj.skill_level_get(skill_appraise) > highest_appraise:
                highest_appraise = obj.skill_level_get(skill_appraise)
        for pc in game.party:
            if pc.stat_level_get(stat_level_wizard) > 1:
                highest_appraise = highest_appraise + 2 # Heroism / Fox's Cunning bonus
                break
        for pc in game.party:
            if pc.stat_level_get(stat_level_bard) > 1:
                highest_appraise = highest_appraise + 2 # Inspire Competence bonus
                break
        if highest_appraise > 19:
            return 0.97
        elif highest_appraise < -13:
            return 0
        else:
            return 0.4 + float(highest_appraise)*0.03
    
     
  12. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Global Variables

    You can define global variables in ToEE, that are persistent across different saves, and are only reset when you exit and reopen the game.

    Usage example:
    Code:
    def func1():
        global x
        try:
            x = x + 1
        except:
            x = 0
        print x
    
    Each time you run func1(), it will print the next number in the sequence, even if you reload / load another save.


    Using Global Variables to Detect Save Scumming

    Here's the fun part. Since global variables are retained across saves, you can check if a global variable has been set when you expect it not to be set.

    Here is a classic example of usage:

    Code:
    def san_enter_combat(attachee, triggerer):
        global save_scum_1, save_scum_2
        try:
            if save_scum_1 > 0:
                attachee.float_line(1000, triggerer)
            try:
                if save_scum_2 > 0:
                    attachee.float_line(1001, triggerer)
            except:
                save_scum_2 = 1
        except:
            save_scum_1 = 1
    
    If you reload after starting a fight with this scripbearer, and initiate another fight, they will float line 1000.
    Second time, they will float line 1001.

    You can add checks for game-time proximity, incase it's a legitimate "alternate path" playthrough or somesuch.
     

    Attached Files:

  13. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    exec() - Executing Commands via Script

    Just found out a neat little trick.

    You can execute commands read from a file via the command exec().

    Usage example:

    Code:
    	code_file = open('script_code_file.txt','r')
    	try:
    		exec(code_file)
    	except:
    		print 'error!'
    	code_file.close()
    
    Where script_code_file.txt contains ordinary scripting code, just as if it were within a py file function!

    The try/except is used to catch errors and prevent them from ruining the rest of the script (e.g. failing to close the file after opening it)

    You can also execute strings, e.g.
    Code:
    a = 'a = 4'
    exec(a)
    
    Will assign a=4.

    I think this can be immensely useful for testing things, and perhaps dynamic scripting too (though perhaps that should be done more carefully).
     
  14. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Getting a Handle on Turned Off Objects

    Sometimes, you want a script to manipulate an object that is turned off (object flag OF_OFF is set).

    One way is to just give the turned-off object a heartbeat script and write the code in there. This is simple but may sometimes have disadvantages. (for instance, heartbeat timing is dependant on distance)

    Another way is to directly get a handle on it. This is impossible with the game.obj_list_vicinity method, because OF_OFF objects aren't detected by it. Instead, you can use the refHandle() and derefhandle() commands.

    derefHandle( obj ):
    Takes an object handle obj and outputs a serial. You can store this serial, and use it to retrieve the handle via refHandle( serial ).
    refHandle( serial ):
    Does the opposite.

    Example usage:

    Code:
    from co8Util.ObjHandling import *
    from co8Util.PersistentData import *
    
    def san_first_heartbeat( attachee, triggerer ):
    	a = derefHandle(attachee)
    	Co8PersistentData.setData("object_a_serial", a )
    
    This code stores the object's handle into a Co8 persistent variable called "object_a_serial".

    You can then retrieve it in another script and use it to get a handle:

    Code:
    from co8Util.ObjHandling import *
    from co8Util.PersistentData import *
    
    def san_heartbeat( attachee, triggerer ):
    	a = Co8PersistentData.getData("object_a_serial" )
    	a_handle = refHandle(a)
    
     
  15. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,038
    Likes Received:
    42
    Test tones

    Not exactly a secret here but a helpful addition: I've added five audio files to data/sound for testing purposes. They are simple A 440 Hz tones in groups of 1 to 5 beeps, so they are easily heard over sounds of battle, etc. They are defined in data/sound/snd_misc.mes as:

    {4171}{test_tone_1.mp3} - 1 beep
    {4172}{test_tone_2.mp3} - 2 beeps
    {4173}{test_tone_3.mp3} - 3 beeps
    {4174}{test_tone_4.mp3} - 4 beeps
    {4175}{test_tone_5.mp3} - 5 beeps

    These are useful for attaching to scripts temporarily to see (or hear, as it were) what the hell is really going on via the game.sound( xxxx, 1 ) command, where xxxx is the 417x number reflected in snd_misc.mes. For example, not sure what strategy your wacky test cleric is actually employing at any given time? (This is ToEE, so no one ever really knows.) Add the game.sound command to the strategy change bit and then you'll know. If he beeps three times, he's healing!, etc.

    Hope this helps. :)
     
Our Host!